MARMOTTA-463: Initial implementation for HTTP PUT in LDP

Project: http://git-wip-us.apache.org/repos/asf/marmotta/repo
Commit: http://git-wip-us.apache.org/repos/asf/marmotta/commit/4a188e64
Tree: http://git-wip-us.apache.org/repos/asf/marmotta/tree/4a188e64
Diff: http://git-wip-us.apache.org/repos/asf/marmotta/diff/4a188e64

Branch: refs/heads/ldp
Commit: 4a188e64c1c806cd5d57c4acce96ae4a5943316e
Parents: e25131f
Author: Jakob Frank <[email protected]>
Authored: Thu Jun 12 17:09:49 2014 +0200
Committer: Jakob Frank <[email protected]>
Committed: Thu Jun 12 17:09:49 2014 +0200

----------------------------------------------------------------------
 .../marmotta/platform/ldp/api/LdpService.java   |   9 +-
 .../IncompatibleResourceTypeException.java      |  59 ++++++++++
 .../InvalidInteractionModelException.java       |   2 +-
 .../InvalidModificationException.java           |   2 +-
 .../platform/ldp/exceptions/LDPException.java   |  56 +++++++++
 .../platform/ldp/services/LdpServiceImpl.java   |  79 ++++++++++++-
 .../platform/ldp/util/EntityTagUtils.java       |  19 +++
 .../platform/ldp/webservices/LdpWebService.java |  33 ++++--
 .../ldp/webservices/LdpWebServiceTest.java      | 115 ++++++++++++++++++-
 .../ldp/webservices/util/HeaderMatchers.java    |  13 ++-
 .../src/test/resources/test_update.ttl          |  27 +++++
 .../src/test/resources/test_update_invalid.ttl  |  25 ++++
 12 files changed, 418 insertions(+), 21 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/api/LdpService.java
----------------------------------------------------------------------
diff --git 
a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/api/LdpService.java
 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/api/LdpService.java
index 12cbbee..5a15d12 100644
--- 
a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/api/LdpService.java
+++ 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/api/LdpService.java
@@ -18,6 +18,7 @@
 package org.apache.marmotta.platform.ldp.api;
 
 import org.apache.marmotta.commons.vocabulary.LDP;
+import 
org.apache.marmotta.platform.ldp.exceptions.IncompatibleResourceTypeException;
 import 
org.apache.marmotta.platform.ldp.exceptions.InvalidInteractionModelException;
 import 
org.apache.marmotta.platform.ldp.exceptions.InvalidModificationException;
 import org.apache.marmotta.platform.ldp.patch.InvalidPatchDocumentException;
@@ -35,8 +36,7 @@ import javax.ws.rs.core.Link;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
-import java.util.Date;
-import java.util.List;
+import java.util.*;
 
 /**
  *  LDP Service
@@ -81,6 +81,8 @@ public interface LdpService {
 
     }
 
+    public static final Set<URI> SERVER_MANAGED_PROPERTIES = new 
HashSet<>(Arrays.asList(LDP.contains));
+
     boolean exists(RepositoryConnection connection, String resource) throws 
RepositoryException;
 
     boolean exists(RepositoryConnection connection, URI resource) throws 
RepositoryException;
@@ -149,6 +151,9 @@ public interface LdpService {
      */
     String addResource(RepositoryConnection connection, URI container, URI 
resource, InteractionModel interactionModel, String type, InputStream stream) 
throws RepositoryException, IOException, RDFParseException;
 
+    String updateResource(RepositoryConnection con, String resource, 
InputStream stream, String type) throws RepositoryException, 
IncompatibleResourceTypeException, RDFParseException, IOException, 
InvalidModificationException;
+
+    String updateResource(RepositoryConnection con, URI resource, InputStream 
stream, String type) throws RepositoryException, 
IncompatibleResourceTypeException, IOException, RDFParseException, 
InvalidModificationException;
 
     List<Statement> getLdpTypes(RepositoryConnection connection, String 
resource) throws RepositoryException;
 

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/IncompatibleResourceTypeException.java
----------------------------------------------------------------------
diff --git 
a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/IncompatibleResourceTypeException.java
 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/IncompatibleResourceTypeException.java
new file mode 100644
index 0000000..c9270ac
--- /dev/null
+++ 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/IncompatibleResourceTypeException.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.marmotta.platform.ldp.exceptions;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * Exception to be thrown if a RDFS-Resource is available where a 
Non-RDF-Resource is expected, and vice versa.
+ */
+public class IncompatibleResourceTypeException extends LDPException {
+
+    public IncompatibleResourceTypeException(String expected, String 
available) {
+        super(createMessage(null, expected, available));
+    }
+
+    public IncompatibleResourceTypeException(String message, String expected, 
String available) {
+        super(createMessage(message, expected, available));
+    }
+
+    private static String createMessage(String message, String expected, 
String available) {
+        if (StringUtils.isBlank(message)) {
+            return String.format("Expected %s but %s was provided", expected, 
available);
+        } else {
+            return String.format("%s: expected %s but %s was provided", 
message, expected, available);
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidInteractionModelException.java
----------------------------------------------------------------------
diff --git 
a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidInteractionModelException.java
 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidInteractionModelException.java
index c083d48..7c87041 100644
--- 
a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidInteractionModelException.java
+++ 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidInteractionModelException.java
@@ -24,7 +24,7 @@ package org.apache.marmotta.platform.ldp.exceptions;
  *
  * @author Jakob Frank ([email protected])
  */
-public class InvalidInteractionModelException extends Exception {
+public class InvalidInteractionModelException extends LDPException {
 
     private final String href;
 

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidModificationException.java
----------------------------------------------------------------------
diff --git 
a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidModificationException.java
 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidModificationException.java
index e529ae8..f255fc9 100644
--- 
a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidModificationException.java
+++ 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidModificationException.java
@@ -24,7 +24,7 @@ package org.apache.marmotta.platform.ldp.exceptions;
  *
  * @see <a 
href="https://dvcs.w3.org/hg/ldpwg/raw-file/default/ldp.html#ldpr-put-servermanagedprops";>LDP
 Spec, Sec. 5.5.2</a>
  */
-public class InvalidModificationException extends Exception {
+public class InvalidModificationException extends LDPException {
 
     public InvalidModificationException() {
         super();

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/LDPException.java
----------------------------------------------------------------------
diff --git 
a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/LDPException.java
 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/LDPException.java
new file mode 100644
index 0000000..a34e650
--- /dev/null
+++ 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/LDPException.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.marmotta.platform.ldp.exceptions;
+
+/**
+ * Created by jakob on 12.06.14.
+ */
+public class LDPException extends Exception {
+    public LDPException() {
+        super();
+    }
+
+    public LDPException(String message) {
+        super(message);
+    }
+
+    public LDPException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public LDPException(Throwable cause) {
+        super(cause);
+    }
+}

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/services/LdpServiceImpl.java
----------------------------------------------------------------------
diff --git 
a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/services/LdpServiceImpl.java
 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/services/LdpServiceImpl.java
index 8d28aae..d4e4e64 100644
--- 
a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/services/LdpServiceImpl.java
+++ 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/services/LdpServiceImpl.java
@@ -26,6 +26,7 @@ import org.apache.marmotta.commons.vocabulary.LDP;
 import org.apache.marmotta.platform.core.api.config.ConfigurationService;
 import org.apache.marmotta.platform.ldp.api.LdpBinaryStoreService;
 import org.apache.marmotta.platform.ldp.api.LdpService;
+import 
org.apache.marmotta.platform.ldp.exceptions.IncompatibleResourceTypeException;
 import 
org.apache.marmotta.platform.ldp.exceptions.InvalidInteractionModelException;
 import 
org.apache.marmotta.platform.ldp.exceptions.InvalidModificationException;
 import org.apache.marmotta.platform.ldp.patch.InvalidPatchDocumentException;
@@ -40,11 +41,12 @@ import org.openrdf.model.vocabulary.RDF;
 import org.openrdf.repository.RepositoryConnection;
 import org.openrdf.repository.RepositoryException;
 import org.openrdf.repository.RepositoryResult;
+import 
org.openrdf.repository.event.base.InterceptingRepositoryConnectionWrapper;
+import 
org.openrdf.repository.event.base.RepositoryConnectionInterceptorAdapter;
 import org.openrdf.rio.*;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import javax.annotation.PostConstruct;
 import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Inject;
 import javax.ws.rs.core.EntityTag;
@@ -53,7 +55,9 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.Date;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 /**
  * LDP Service default implementation
@@ -319,6 +323,79 @@ public class LdpServiceImpl implements LdpService {
     }
 
     @Override
+    public String updateResource(RepositoryConnection con, String resource, 
InputStream stream, String type) throws RepositoryException, 
IncompatibleResourceTypeException, RDFParseException, IOException, 
InvalidModificationException {
+        return updateResource(con, buildURI(resource), stream, type);
+    }
+
+    @Override
+    public String updateResource(final RepositoryConnection con, final URI 
resource, InputStream stream, String type) throws RepositoryException, 
IncompatibleResourceTypeException, IOException, RDFParseException, 
InvalidModificationException {
+        final ValueFactory valueFactory = con.getValueFactory();
+        final Literal now = valueFactory.createLiteral(new Date());
+
+        con.remove(resource, DCTERMS.modified, null, ldpContext);
+        con.add(resource, DCTERMS.modified, now, ldpContext);
+
+        final RDFFormat rdfFormat = Rio.getParserFormatForMIMEType(type);
+        // Check submitted format vs. real resource type (RDF-S vs. Non-RDF)
+        if (rdfFormat == null && isNonRdfSourceResource(con, resource)) {
+            log.debug("Updating <{}> as LDP-NR (binary) - {}", resource, type);
+
+            final Literal format = valueFactory.createLiteral(type);
+
+            con.remove(resource, DCTERMS.format, null, ldpContext);
+            con.add(resource, DCTERMS.format, format, ldpContext); 
//nie:mimeType ?
+
+            final URI ldp_rs = getRdfSourceForNonRdfSource(con, resource);
+            if (ldp_rs != null) {
+                con.remove(ldp_rs, DCTERMS.modified, null, ldpContext);
+                con.add(ldp_rs, DCTERMS.modified, now, ldpContext);
+                log.trace("Updated Meta-Data of LDP-RS <{}> for LDP-NR <{}>; 
Modified: {}", ldp_rs, resource, now);
+            } else {
+                log.debug("LDP-RS for LDP-NR <{}> not found", resource);
+            }
+            log.trace("Meta-Data for <{}> updated; Format: {}, Modified: {}", 
resource, format, now);
+
+            binaryStore.store(resource, stream);//TODO: exceptions control
+
+            log.trace("LDP-NR <{}> updated", resource);
+            return resource.stringValue();
+        } else if (rdfFormat != null && isRdfSourceResource(con, resource)) {
+            log.debug("Updating <{}> as LDP-RS - {}", resource, 
rdfFormat.getDefaultMIMEType());
+
+            con.clear(resource);
+            final InterceptingRepositoryConnectionWrapper filtered = new 
InterceptingRepositoryConnectionWrapper(con.getRepository(), con);
+            final Set<URI> deniedProperties = new HashSet<>();
+            filtered.addRepositoryConnectionInterceptor(new 
RepositoryConnectionInterceptorAdapter() {
+                @Override
+                public boolean add(RepositoryConnection conn, Resource 
subject, URI predicate, Value object, Resource... contexts) {
+                    if (resource.equals(subject) && 
SERVER_MANAGED_PROPERTIES.contains(predicate)) {
+                        deniedProperties.add(predicate);
+                        return true;
+                    }
+                    return false;
+                }
+            });
+
+            filtered.add(stream, resource.stringValue(), rdfFormat, resource);
+
+            if (!deniedProperties.isEmpty()) {
+                final URI prop = deniedProperties.iterator().next();
+                log.debug("Invalid property modification in update: <{}> is a 
server controlled property", prop);
+                throw new InvalidModificationException(String.format("Must not 
update <%s> using PUT", prop));
+            }
+            log.trace("LDP-RS <{}> updated", resource);
+            return resource.stringValue();
+        } else if (rdfFormat == null) {
+            final String mimeType = getMimeType(con, resource);
+            log.debug("Incompatible replacement: Can't replace {} with {}", 
mimeType, type);
+            throw new IncompatibleResourceTypeException(mimeType, type);
+        } else {
+            log.debug("Incompatible replacement: Can't replace a LDP-RS with 
{}", type);
+            throw new IncompatibleResourceTypeException("RDF", type);
+        }
+    }
+
+    @Override
     public EntityTag generateETag(RepositoryConnection connection, String 
resource) throws RepositoryException {
         return generateETag(connection, buildURI(resource));
     }

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/EntityTagUtils.java
----------------------------------------------------------------------
diff --git 
a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/EntityTagUtils.java
 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/EntityTagUtils.java
index bc58a60..a695096 100644
--- 
a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/EntityTagUtils.java
+++ 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/EntityTagUtils.java
@@ -50,4 +50,23 @@ public class EntityTagUtils {
         }
     }
 
+    /**
+     * This is a workaround for <a 
href="https://issues.jboss.org/browse/RESTEASY-1019";>RESTEASY-1019</a>
+     *
+     * @see javax.ws.rs.core.EntityTag#equals(Object)
+     * @deprecated use {@link javax.ws.rs.core.EntityTag#valueOf(String)} when 
RESTEASY-1019 is fixed.
+     */
+    @Deprecated
+    public static EntityTag parseEntityTag(String headerValue) {
+        boolean weak = false;
+        if (headerValue.startsWith("W/")) {
+            weak = true;
+            headerValue = headerValue.substring(2);
+        }
+        if (headerValue.startsWith("\"") && headerValue.endsWith("\"")) {
+            headerValue = headerValue.substring(1, headerValue.length() -1);
+        }
+        return new EntityTag(headerValue,weak);
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/webservices/LdpWebService.java
----------------------------------------------------------------------
diff --git 
a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/webservices/LdpWebService.java
 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/webservices/LdpWebService.java
index 81dedf1..0a24fcf 100644
--- 
a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/webservices/LdpWebService.java
+++ 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/webservices/LdpWebService.java
@@ -22,6 +22,7 @@ import org.apache.marmotta.commons.vocabulary.LDP;
 import org.apache.marmotta.platform.core.api.config.ConfigurationService;
 import org.apache.marmotta.platform.core.api.triplestore.SesameService;
 import org.apache.marmotta.platform.ldp.api.LdpService;
+import 
org.apache.marmotta.platform.ldp.exceptions.IncompatibleResourceTypeException;
 import 
org.apache.marmotta.platform.ldp.exceptions.InvalidInteractionModelException;
 import 
org.apache.marmotta.platform.ldp.exceptions.InvalidModificationException;
 import org.apache.marmotta.platform.ldp.patch.InvalidPatchDocumentException;
@@ -47,6 +48,7 @@ import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.List;
+import java.util.Set;
 import java.util.UUID;
 
 /**
@@ -302,6 +304,7 @@ public class LdpWebService {
             con.begin();
 
             if (!ldpService.exists(con, resource)) {
+                log.trace("Resource does not exists: {}", resource);
                 final Response.ResponseBuilder resp = createResponse(con, 
Response.Status.NOT_FOUND, resource);
                 con.rollback();
                 return resp.build();
@@ -325,14 +328,28 @@ public class LdpWebService {
                 }
             }
 
-            /*
-             * TODO: PUT implementation
-             *
-             * clients should not be allowed to update LDPC-membership triples 
-> 409 Conflict (Sec. 4.2.4.3)
-             *
-             * if the target resource exists, replace ALL data of the target.
-             */
-            final Response.ResponseBuilder resp = createResponse(con, 
Response.Status.NOT_IMPLEMENTED, resource);
+            final String mimeType = LdpUtils.getMimeType(type);
+            log.trace("updating resource <{}>", resource);
+            // NOTE: newResource == resource for now, this might change in the 
future.
+            final String newResource = ldpService.updateResource(con, 
resource, postBody, mimeType);
+
+            final Response.ResponseBuilder resp;
+            if (resource.equals(newResource)) {
+                log.trace("PUT update for <{}> successful", resource);
+                resp = createResponse(con, Response.Status.OK, resource);
+            } else {
+                log.trace("PUT on <{}> created new resource <{}>", resource, 
newResource);
+                resp = createResponse(con, Response.Status.CREATED, 
resource).location(java.net.URI.create(newResource));
+            }
+            con.commit();
+
+            return resp.build();
+        } catch (IOException | RDFParseException e) {
+            final Response.ResponseBuilder resp = createResponse(con, 
Response.Status.BAD_REQUEST, resource).entity(e.getClass().getSimpleName() + ": 
" + e.getMessage());
+            con.rollback();
+            return resp.build();
+        } catch (InvalidModificationException | 
IncompatibleResourceTypeException e) {
+            final Response.ResponseBuilder resp = createResponse(con, 
Response.Status.CONFLICT, resource).entity(e.getClass().getSimpleName() + ": " 
+ e.getMessage());
             con.rollback();
             return resp.build();
         } catch (final Throwable t) {

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/LdpWebServiceTest.java
----------------------------------------------------------------------
diff --git 
a/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/LdpWebServiceTest.java
 
b/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/LdpWebServiceTest.java
index 8cd5c69..214926e 100644
--- 
a/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/LdpWebServiceTest.java
+++ 
b/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/LdpWebServiceTest.java
@@ -20,23 +20,28 @@ package org.apache.marmotta.platform.ldp.webservices;
 import com.jayway.restassured.RestAssured;
 import org.apache.commons.io.IOUtils;
 import org.apache.marmotta.commons.sesame.test.SesameMatchers;
+import org.apache.marmotta.commons.sesame.test.base.SesameMatcher;
 import org.apache.marmotta.commons.util.HashUtils;
 import org.apache.marmotta.commons.vocabulary.LDP;
 import org.apache.marmotta.platform.core.exception.io.MarmottaImportException;
 import org.apache.marmotta.platform.core.test.base.JettyMarmotta;
+import org.apache.marmotta.platform.ldp.util.EntityTagUtils;
 import org.apache.marmotta.platform.ldp.webservices.util.HeaderMatchers;
 import org.hamcrest.CoreMatchers;
 import org.junit.AfterClass;
 import org.junit.BeforeClass;
 import org.junit.Test;
+import org.openrdf.model.URI;
 import org.openrdf.model.impl.LiteralImpl;
 import org.openrdf.model.impl.URIImpl;
 import org.openrdf.model.vocabulary.DCTERMS;
 import org.openrdf.model.vocabulary.RDF;
+import org.openrdf.model.vocabulary.RDFS;
 import org.openrdf.rio.RDFFormat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.ws.rs.core.EntityTag;
 import javax.ws.rs.core.Link;
 import javax.ws.rs.core.UriBuilder;
 import java.io.IOException;
@@ -234,9 +239,9 @@ public class LdpWebServiceTest {
 
         // now the resource hasType
         RestAssured
-                .given()
+            .given()
                 .header("Accept", RDFFormat.TURTLE.getDefaultMIMEType())
-                .expect()
+            .expect()
                 .statusCode(200)
                 .header("Link", CoreMatchers.anyOf( //TODO: RestAssured only 
checks the FIRST header...
                         
HeaderMatchers.isLink(LdpWebService.LDP_SERVER_CONSTRAINTS, "describedby"),
@@ -252,7 +257,7 @@ public class LdpWebServiceTest {
                         SesameMatchers.hasStatement(new URIImpl(baseUrl + 
newResource+".png"), DCTERMS.FORMAT, new LiteralImpl(mimeType)),
                         SesameMatchers.hasStatement(new URIImpl(baseUrl + 
newResource+".png"), DCTERMS.IS_FORMAT_OF, new URIImpl(baseUrl + newResource))
                 ))
-                .get(newResource + ".png");
+            .get(newResource + ".png");
 
         // now check that the data is really there
         final String expectedMD5 = 
HashUtils.md5sum(LdpWebServiceTest.class.getResourceAsStream("/test.png"));
@@ -309,6 +314,110 @@ public class LdpWebServiceTest {
             .post(container);
     }
 
+    @Test
+    public void testPUT() throws Exception {
+        final String container = baseUrl+LdpWebService.PATH + "/test";
+
+        final String put_valid = 
IOUtils.toString(LdpWebServiceTest.class.getResourceAsStream("/test_update.ttl"),
 "utf8");
+        final String put_invalid = 
IOUtils.toString(LdpWebServiceTest.class.getResourceAsStream("/test_update_invalid.ttl"),
 "utf8");
+
+
+        // Create a resource
+        final String resource = RestAssured
+            .given()
+                .header("Slug", "PUT")
+                .contentType(RDFFormat.TURTLE.getDefaultMIMEType())
+                .body(testResourceTTL.getBytes())
+            .expect()
+                .statusCode(201)
+            .post(container)
+                .getHeader("Location");
+        final URI uri = new URIImpl(resource);
+
+        // Check the data is there
+        EntityTag etag = EntityTagUtils.parseEntityTag(RestAssured
+                .given()
+                .header("Accept", RDFFormat.RDFXML.getDefaultMIMEType())
+                .expect()
+                .contentType(RDFFormat.RDFXML.getDefaultMIMEType())
+                .body(SesameMatchers.rdfStringMatches(RDFFormat.RDFXML, 
resource,
+                        SesameMatchers.hasStatement(uri, RDF.TYPE, new 
URIImpl("http://example.com/Example";)),
+                        CoreMatchers.not(SesameMatchers.hasStatement(uri, 
RDFS.LABEL, null)),
+                        CoreMatchers.not(SesameMatchers.hasStatement(uri, 
LDP.contains, uri))
+                ))
+                .get(resource)
+                .getHeader("ETag"));
+        log.debug("ETag for <{}>: {}", resource, etag);
+
+        // Try a Put without if-match header
+        RestAssured
+            .given()
+                .contentType(RDFFormat.TURTLE.getDefaultMIMEType())
+                .body(put_valid.getBytes())
+            .expect()
+                .statusCode(428)
+            .put(resource);
+
+        // Try a Put with wrong if-match header
+        RestAssured
+            .given()
+                .header("If-Match", new EntityTag("invalid").toString())
+                .contentType(RDFFormat.TURTLE.getDefaultMIMEType())
+                .body(put_valid.getBytes())
+            .expect()
+                .statusCode(412)
+            .put(resource);
+
+        // Try a Put
+        RestAssured
+            .given()
+                .header("If-Match", etag.toString())
+                .contentType(RDFFormat.TURTLE.getDefaultMIMEType())
+                .body(put_valid.getBytes())
+            .expect()
+                .statusCode(200)
+            .put(resource);
+
+        // Check the new data is there
+        etag = EntityTagUtils.parseEntityTag(RestAssured
+            .given()
+                .header("Accept", RDFFormat.RDFXML.getDefaultMIMEType())
+            .expect()
+                .contentType(RDFFormat.RDFXML.getDefaultMIMEType())
+                .body(SesameMatchers.rdfStringMatches(RDFFormat.RDFXML, 
resource,
+                        SesameMatchers.hasStatement(uri, RDF.TYPE, new 
URIImpl("http://example.com/Example";)),
+                        SesameMatchers.hasStatement(uri, RDFS.LABEL, null),
+                        CoreMatchers.not(SesameMatchers.hasStatement(uri, 
LDP.contains, uri))
+                ))
+            .get(resource)
+                .header("ETag"));
+
+        // Try an invalid PUT (server-controlled property)
+        // Try a Put
+        RestAssured
+            .given()
+                .header("If-Match", etag.toString())
+                .contentType(RDFFormat.TURTLE.getDefaultMIMEType())
+                .body(put_invalid.getBytes())
+            .expect()
+                .statusCode(409)
+            .put(resource);
+
+        // Check the data is still there
+        RestAssured
+            .given()
+                .header("Accept", RDFFormat.RDFXML.getDefaultMIMEType())
+            .expect()
+                .contentType(RDFFormat.RDFXML.getDefaultMIMEType())
+                .header("ETag", HeaderMatchers.hasEntityTag(etag))
+                .body(SesameMatchers.rdfStringMatches(RDFFormat.RDFXML, 
resource,
+                        SesameMatchers.hasStatement(uri, RDF.TYPE, new 
URIImpl("http://example.com/Example";)),
+                        SesameMatchers.hasStatement(uri, RDFS.LABEL, null),
+                        CoreMatchers.not(SesameMatchers.hasStatement(uri, 
LDP.contains, uri))
+                ))
+            .get(resource);
+    }
+
     @AfterClass
     public static void tearDown() {
         marmotta.shutdown();

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/util/HeaderMatchers.java
----------------------------------------------------------------------
diff --git 
a/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/util/HeaderMatchers.java
 
b/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/util/HeaderMatchers.java
index a109c3d..80f4026 100644
--- 
a/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/util/HeaderMatchers.java
+++ 
b/platform/marmotta-ldp/src/test/java/org/apache/marmotta/platform/ldp/webservices/util/HeaderMatchers.java
@@ -79,7 +79,14 @@ public class HeaderMatchers {
     }
 
     public static Matcher<String> hasEntityTag(String value, boolean weakTag) {
-        final EntityTag expected = new EntityTag(value, weakTag);
+        return hasEntityTag(new EntityTag(value, weakTag));
+    }
+      
+    public static Matcher<String> hasEntityTag(String value) {
+        return hasEntityTag(value, false);
+    }
+
+    public static Matcher<String> hasEntityTag(final EntityTag expected) {
         return new CustomTypeSafeMatcher<String>(String.format("an EntityTag 
%s", expected)) {
             @Override
             protected boolean matchesSafely(String item) {
@@ -87,8 +94,4 @@ public class HeaderMatchers {
             }
         };
     }
-      
-    public static Matcher<String> hasEntityTag(String value) {
-        return hasEntityTag(value, false);
-    }
 }

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/test/resources/test_update.ttl
----------------------------------------------------------------------
diff --git a/platform/marmotta-ldp/src/test/resources/test_update.ttl 
b/platform/marmotta-ldp/src/test/resources/test_update.ttl
new file mode 100644
index 0000000..29749f5
--- /dev/null
+++ b/platform/marmotta-ldp/src/test/resources/test_update.ttl
@@ -0,0 +1,27 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
+@prefix dct:  <http://purl.org/dc/terms/>.
+@prefix skos: <http://www.w3.org/2004/02/skos/core#>.
+@prefix ldp:  <http://www.w3.org/ns/ldp#>.
+
+<> a <http://example.com/Example> ;
+       dct:title "Example"@en , "Beispiel"@de ;
+       dct:description "Just an example"@en, "Einfach ein Beispiel"@de .
+
+<> rdfs:label "LDP PUT Test".

http://git-wip-us.apache.org/repos/asf/marmotta/blob/4a188e64/platform/marmotta-ldp/src/test/resources/test_update_invalid.ttl
----------------------------------------------------------------------
diff --git a/platform/marmotta-ldp/src/test/resources/test_update_invalid.ttl 
b/platform/marmotta-ldp/src/test/resources/test_update_invalid.ttl
new file mode 100644
index 0000000..bbb4baa
--- /dev/null
+++ b/platform/marmotta-ldp/src/test/resources/test_update_invalid.ttl
@@ -0,0 +1,25 @@
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+@prefix rdf:  <http://www.w3.org/1999/02/22-rdf-syntax-ns#>.
+@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#>.
+@prefix dct:  <http://purl.org/dc/terms/>.
+@prefix skos: <http://www.w3.org/2004/02/skos/core#>.
+@prefix ldp:  <http://www.w3.org/ns/ldp#>.
+
+<> a <http://example.com/Example> ;
+       dct:title "Example"@en , "Beispiel"@de ;
+       ldp:contains <> .

Reply via email to