MARMOTTA-462: Implemented support for LDP Implementation Models: LDPR and LDPC 
(default)


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

Branch: refs/heads/master
Commit: ca37361e46175be062fc94e658554a73e4343cc4
Parents: f171cc1
Author: Jakob Frank <[email protected]>
Authored: Thu Mar 27 16:28:23 2014 +0100
Committer: Jakob Frank <[email protected]>
Committed: Fri Mar 28 09:13:04 2014 +0100

----------------------------------------------------------------------
 .../marmotta/platform/ldp/api/LdpService.java   | 77 ++++++++++++++++++++
 .../InvalidInteractionModelException.java       | 48 ++++++++++++
 .../platform/ldp/services/LdpServiceImpl.java   | 72 ++++++++++++++++--
 .../platform/ldp/webservices/LdpWebService.java | 34 +++++++--
 .../ldp/webservices/LdpWebServiceTest.java      | 38 +++++++++-
 5 files changed, 254 insertions(+), 15 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/marmotta/blob/ca37361e/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 4795512..12cbbee 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
@@ -17,6 +17,8 @@
  */
 package org.apache.marmotta.platform.ldp.api;
 
+import org.apache.marmotta.commons.vocabulary.LDP;
+import 
org.apache.marmotta.platform.ldp.exceptions.InvalidInteractionModelException;
 import 
org.apache.marmotta.platform.ldp.exceptions.InvalidModificationException;
 import org.apache.marmotta.platform.ldp.patch.InvalidPatchDocumentException;
 import org.apache.marmotta.platform.ldp.patch.parser.ParseException;
@@ -29,6 +31,7 @@ import org.openrdf.rio.RDFHandlerException;
 import org.openrdf.rio.RDFParseException;
 
 import javax.ws.rs.core.EntityTag;
+import javax.ws.rs.core.Link;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -43,6 +46,41 @@ import java.util.List;
  */
 public interface LdpService {
 
+
+
+    public static enum InteractionModel {
+        LDPR(LDP.Resource),
+        LDPC(LDP.Container);
+
+        private final URI uri;
+
+        InteractionModel(URI uri) {
+            this.uri = uri;
+        }
+
+        public URI getUri() {
+            return uri;
+        }
+
+        public static InteractionModel fromURI(String uri) {
+            if (LDP.Resource.stringValue().equals(uri)) {
+                return LDPR;
+            } else if (LDP.Container.stringValue().equals(uri)) {
+                return LDPC;
+            }
+            throw new IllegalArgumentException("Invalid Interaction Model URI: 
" + uri);
+        }
+
+        public static InteractionModel fromURI(URI uri){
+            if (uri == null) {
+                throw new IllegalArgumentException("Invalid Interaction Model: 
null");
+            } else {
+                return fromURI(uri.stringValue());
+            }
+        }
+
+    }
+
     boolean exists(RepositoryConnection connection, String resource) throws 
RepositoryException;
 
     boolean exists(RepositoryConnection connection, URI resource) throws 
RepositoryException;
@@ -79,6 +117,39 @@ public interface LdpService {
      */
     String addResource(RepositoryConnection connection, URI container, URI 
resource, String type, InputStream stream) throws RepositoryException, 
IOException, RDFParseException;
 
+    /**
+     * Add a LDP resource
+     *
+     * @param connection repository connection
+     * @param container container where add the resource
+     * @param resource resource to add
+     * @param interactionModel the ldp interaction model
+     * @param type mimetype of the posted resource
+     * @param stream stream from where read the resource representation
+     * @return resource location
+     * @throws RepositoryException
+     * @throws IOException
+     * @throws RDFParseException
+     */
+    String addResource(RepositoryConnection connection, String container, 
String resource, InteractionModel interactionModel, String type, InputStream 
stream) throws RepositoryException, IOException, RDFParseException;
+
+    /**
+     * Add a LDP resource
+     *
+     * @param connection repository connection
+     * @param container container where add the resource
+     * @param resource resource to add
+     * @param interactionModel the ldp interaction model
+     * @param type mimetype of the posted resource
+     * @param stream stream from where read the resource representation
+     * @return resource location
+     * @throws RepositoryException
+     * @throws IOException
+     * @throws RDFParseException
+     */
+    String addResource(RepositoryConnection connection, URI container, URI 
resource, InteractionModel interactionModel, String type, InputStream stream) 
throws RepositoryException, IOException, RDFParseException;
+
+
     List<Statement> getLdpTypes(RepositoryConnection connection, String 
resource) throws RepositoryException;
 
     List<Statement> getLdpTypes(RepositoryConnection conn1, URI resource) 
throws RepositoryException;
@@ -125,4 +196,10 @@ public interface LdpService {
 
     URI getNonRdfSourceForRdfSource(RepositoryConnection connection, String 
resource) throws RepositoryException;
     URI getNonRdfSourceForRdfSource(RepositoryConnection connection, URI uri) 
throws RepositoryException;
+
+    InteractionModel getInteractionModel(List<Link> linkHeaders) throws 
InvalidInteractionModelException;
+
+    InteractionModel getInteractionModel(RepositoryConnection connection, 
String resource) throws RepositoryException;
+    InteractionModel getInteractionModel(RepositoryConnection connection, URI 
uri) throws RepositoryException;
+
 }

http://git-wip-us.apache.org/repos/asf/marmotta/blob/ca37361e/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
new file mode 100644
index 0000000..c083d48
--- /dev/null
+++ 
b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/exceptions/InvalidInteractionModelException.java
@@ -0,0 +1,48 @@
+/*
+ * 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;
+
+
+/**
+ * Invalid or unknown LDP Client Interaction Model Exception
+ *
+ * @see <a href="http://www.w3.org/TR/ldp/#h5_ldpc-post-createrdf";>LDP Spec</a>
+ *
+ * @author Jakob Frank ([email protected])
+ */
+public class InvalidInteractionModelException extends Exception {
+
+    private final String href;
+
+    public InvalidInteractionModelException(String href) {
+        super(String.format("Invalid LDP Client Interaction Model: <%s>", 
href));
+        this.href = href;
+    }
+
+    public InvalidInteractionModelException(java.net.URI uri) {
+        this(uri.toASCIIString());
+    }
+
+    public InvalidInteractionModelException(org.openrdf.model.URI uri) {
+        this(uri.stringValue());
+    }
+
+    public String getHref() {
+        return href;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/marmotta/blob/ca37361e/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 821436e..da4a302 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.InvalidInteractionModelException;
 import 
org.apache.marmotta.platform.ldp.exceptions.InvalidModificationException;
 import org.apache.marmotta.platform.ldp.patch.InvalidPatchDocumentException;
 import org.apache.marmotta.platform.ldp.patch.RdfPatchUtil;
@@ -46,6 +47,7 @@ import org.slf4j.LoggerFactory;
 import javax.enterprise.context.ApplicationScoped;
 import javax.inject.Inject;
 import javax.ws.rs.core.EntityTag;
+import javax.ws.rs.core.Link;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
@@ -69,10 +71,11 @@ public class LdpServiceImpl implements LdpService {
     @Inject
     private LdpBinaryStoreService binaryStore;
 
-    private final URI ldpContext;
+    private final URI ldpContext, ldpInteractionModelProperty;
 
     public LdpServiceImpl() {
         ldpContext = ValueFactoryImpl.getInstance().createURI(LDP.NAMESPACE);
+        ldpInteractionModelProperty = 
ValueFactoryImpl.getInstance().createURI(LDP.NAMESPACE, "interactionModel");
     }
 
     private URI buildURI(String resource) {
@@ -194,8 +197,8 @@ public class LdpServiceImpl implements LdpService {
         // TODO: non-membership triples flag / Prefer-header
         RDFWriter writer = Rio.createWriter(format, output);
         UnionIteration<Statement, RepositoryException> union = new 
UnionIteration<>(
-                connection.getStatements(null, null, null, false, resource),
-                connection.getStatements(resource, null, null, false, 
ldpContext)
+                connection.getStatements(resource, null, null, false, 
ldpContext),
+                connection.getStatements(null, null, null, false, resource)
         );
         try {
             LdpUtils.exportIteration(writer, resource, union);
@@ -237,14 +240,23 @@ public class LdpServiceImpl implements LdpService {
         }
         return null;
     }
-
     @Override
     public String addResource(RepositoryConnection connection, String 
container, String resource, String type, InputStream stream) throws 
RepositoryException, IOException, RDFParseException {
-        return addResource(connection, buildURI(container), 
buildURI(resource), type, stream);
+        return addResource(connection, buildURI(container), 
buildURI(resource), InteractionModel.LDPC, type, stream);
     }
 
     @Override
     public String addResource(RepositoryConnection connection, URI container, 
URI resource, String type, InputStream stream) throws RepositoryException, 
IOException, RDFParseException {
+        return addResource(connection, container, resource, 
InteractionModel.LDPC, type, stream);
+    }
+
+    @Override
+    public String addResource(RepositoryConnection connection, String 
container, String resource, InteractionModel interactionModel, String type, 
InputStream stream) throws RepositoryException, IOException, RDFParseException {
+        return addResource(connection, buildURI(container), 
buildURI(resource), interactionModel, type, stream);
+    }
+
+    @Override
+    public String addResource(RepositoryConnection connection, URI container, 
URI resource, InteractionModel interactionModel, String type, InputStream 
stream) throws RepositoryException, IOException, RDFParseException {
         ValueFactory valueFactory = connection.getValueFactory();
 
         // Add container triples (Sec. 5.2.3.2)
@@ -265,6 +277,7 @@ public class LdpServiceImpl implements LdpService {
 
         connection.add(resource, RDF.TYPE, LDP.Resource, ldpContext);
         connection.add(resource, RDF.TYPE, LDP.RDFSource, ldpContext);
+        connection.add(resource, ldpInteractionModelProperty, 
interactionModel.getUri(), ldpContext);
         connection.add(resource, DCTERMS.created, now, ldpContext);
         connection.add(resource, DCTERMS.modified, now, ldpContext);
 
@@ -272,8 +285,8 @@ public class LdpServiceImpl implements LdpService {
         final RDFFormat rdfFormat = Rio.getParserFormatForMIMEType(type);
         if (rdfFormat == null) {
             log.debug("POST creates new LDP-NR, because no suitable RDF parser 
found for type {}", type);
-            Literal format = valueFactory.createLiteral(type);
-            URI binaryResource = valueFactory.createURI(resource.stringValue() 
+ LdpUtils.getExtension(type));
+            final Literal format = valueFactory.createLiteral(type);
+            final URI binaryResource = 
valueFactory.createURI(resource.stringValue() + LdpUtils.getExtension(type));
 
             connection.add(container, LDP.contains, binaryResource, 
ldpContext);
 
@@ -447,4 +460,49 @@ public class LdpServiceImpl implements LdpService {
         return true;
     }
 
+    @Override
+    public InteractionModel getInteractionModel(List<Link> linkHeaders) throws 
InvalidInteractionModelException {
+        if (log.isTraceEnabled()) {
+            log.trace("Checking Link-Headers for LDP Interaction Models");
+            for (Link link: linkHeaders) {
+                log.trace(" - {}", link);
+            }
+        }
+        for (Link link: linkHeaders) {
+            if ("type".equalsIgnoreCase(link.getRel())) {
+                final String href = link.getUri().toASCIIString();
+                if (LDP.Resource.stringValue().equals(href)) {
+                    log.debug("LDPR Interaction Model detected");
+                    return InteractionModel.LDPR;
+                } else if (LDP.Resource.stringValue().equals(href)) {
+                    log.debug("LDPC Interaction Model detected");
+                    return InteractionModel.LDPC;
+                } else {
+                    log.debug("Invalid/Unknown LDP Interaction Model: {}", 
href);
+                    throw new InvalidInteractionModelException(href);
+                }
+            }
+        }
+        log.debug("No LDP Interaction Model specified, defaulting to {}", 
InteractionModel.LDPC);
+        // Default Interaction Model is LDPC
+        return InteractionModel.LDPC;
+    }
+
+    @Override
+    public InteractionModel getInteractionModel(RepositoryConnection 
connection, String resource) throws RepositoryException {
+        return getInteractionModel(connection, buildURI(resource));
+    }
+
+    @Override
+    public InteractionModel getInteractionModel(RepositoryConnection 
connection, URI uri) throws RepositoryException {
+        if (connection.hasStatement(uri, ldpInteractionModelProperty, 
InteractionModel.LDPC.getUri(), true, ldpContext)) {
+            return InteractionModel.LDPC;
+        } else if (connection.hasStatement(uri, ldpInteractionModelProperty, 
InteractionModel.LDPR.getUri(), true, ldpContext)) {
+            return InteractionModel.LDPR;
+        }
+
+        log.info("No LDP Interaction Model specified for <{}>, defaulting to 
{}", uri.stringValue(), InteractionModel.LDPC);
+        // Default Interaction Model is LDPC
+        return InteractionModel.LDPC;
+    }
 }

http://git-wip-us.apache.org/repos/asf/marmotta/blob/ca37361e/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 66516fd..81dedf1 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.InvalidInteractionModelException;
 import 
org.apache.marmotta.platform.ldp.exceptions.InvalidModificationException;
 import org.apache.marmotta.platform.ldp.patch.InvalidPatchDocumentException;
 import org.apache.marmotta.platform.ldp.patch.parser.ParseException;
@@ -194,6 +195,7 @@ public class LdpWebService {
      */
     @POST
     public Response POST(@Context UriInfo uriInfo, @HeaderParam("Slug") String 
slug,
+                         @HeaderParam("Link") List<Link> linkHeaders,
                          InputStream postBody, 
@HeaderParam(HttpHeaders.CONTENT_TYPE) MediaType type)
             throws RepositoryException {
 
@@ -204,7 +206,16 @@ public class LdpWebService {
         try {
             conn.begin();
 
-            // TODO: Check the LDP-Interaction Model (Sec. 5.2.3.4 and Sec. 
4.2.1.4)
+            // Check that the target container supports the LDPC Interaction 
Model
+            final LdpService.InteractionModel containerModel = 
ldpService.getInteractionModel(conn, container);
+            if (containerModel != LdpService.InteractionModel.LDPC) {
+                final Response.ResponseBuilder response = createResponse(conn, 
Response.Status.METHOD_NOT_ALLOWED, container);
+                conn.commit();
+                return response.entity(String.format("%s only supports %s 
Interaction Model", container, containerModel)).build();
+            }
+
+            // Get the LDP-Interaction Model (Sec. 5.2.3.4 and Sec. 4.2.1.4)
+            final LdpService.InteractionModel ldpInteractionModel = 
ldpService.getInteractionModel(linkHeaders);
 
             final String localName;
             if (StringUtils.isBlank(slug)) {
@@ -246,7 +257,7 @@ public class LdpWebService {
             final String mimeType = LdpUtils.getMimeType(type);
             //checking if resource (container) exists is done later in the 
service
             try {
-                String location = ldpService.addResource(conn, container, 
newResource, mimeType, postBody);
+                String location = ldpService.addResource(conn, container, 
newResource, ldpInteractionModel, mimeType, postBody);
                 final Response.ResponseBuilder response = createResponse(conn, 
Response.Status.CREATED, container).location(java.net.URI.create(location));
                 if (newResource.compareTo(location) != 0) {
                     response.link(newResource, "describedby"); //FIXME: Sec. 
5.2.3.12, see also http://www.w3.org/2012/ldp/track/issues/15
@@ -262,6 +273,11 @@ public class LdpWebService {
                 conn.rollback();
                 return resp.build();
             }
+        } catch (InvalidInteractionModelException e) {
+            log.debug("POST with invalid interaction model <{}> to <{}>", 
e.getHref(), container);
+            final Response.ResponseBuilder response = createResponse(conn, 
Response.Status.BAD_REQUEST, container);
+            conn.commit();
+            return response.entity(e.getMessage()).build();
         } catch (final Throwable t) {
             conn.rollback();
             throw t;
@@ -438,11 +454,15 @@ public class LdpWebService {
                 // Sec. 4.2.8.2
                 builder.allow("GET", "HEAD", "OPTIONS");
             } else if (ldpService.isRdfSourceResource(con, resource)) {
-                // Sec. 4.2.8.2
-                builder.allow("GET", "HEAD", "POST", "PATCH", "OPTIONS");
-                // Sec. 4.2.3 / Sec. 5.2.3
-                // TODO: LDP Interaction Model!
-                builder.header("Accept-Post", 
LdpUtils.getAcceptPostHeader("*/*"));
+                if (ldpService.getInteractionModel(con, resource) == 
LdpService.InteractionModel.LDPR) {
+                    // Sec. 4.2.8.2
+                    builder.allow("GET", "HEAD", "PATCH", "OPTIONS");
+                } else {
+                    // Sec. 4.2.8.2
+                    builder.allow("GET", "HEAD", "POST", "PATCH", "OPTIONS");
+                    // Sec. 4.2.3 / Sec. 5.2.3
+                    builder.header("Accept-Post", 
LdpUtils.getAcceptPostHeader("*/*"));
+                }
                 // Sec. 4.2.7.1
                 builder.header("Accept-Patch", RdfPatchParser.MIME_TYPE);
             }

http://git-wip-us.apache.org/repos/asf/marmotta/blob/ca37361e/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 f110946..47ecd8e 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
@@ -37,6 +37,7 @@ import org.openrdf.rio.RDFFormat;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import javax.ws.rs.core.Link;
 import javax.ws.rs.core.UriBuilder;
 import java.io.IOException;
 import java.net.URISyntaxException;
@@ -273,7 +274,42 @@ public class LdpWebServiceTest {
         assertEquals("md5sum",expectedMD5, HashUtils.md5sum(data));
     }
 
-   @AfterClass
+    @Test
+    public void testInteractionModel() throws Exception {
+        final String container = baseUrl+LdpWebService.PATH + "/iam";
+
+        // Try LDPR
+        final String ldpr = RestAssured
+            .given()
+                .header("Link", 
Link.fromUri(LDP.Resource.stringValue()).rel("type").build().toString())
+                .body(testResourceTTL.getBytes())
+                .contentType(RDFFormat.TURTLE.getDefaultMIMEType())
+            .expect()
+                .statusCode(201)
+            .post(container)
+                .getHeader("Location");
+
+        // Now POSTing to the ldpr should fail
+        RestAssured
+            .given()
+                .body(testResourceTTL.getBytes())
+                .contentType(RDFFormat.TURTLE.getDefaultMIMEType())
+            .expect()
+                .statusCode(405)
+            .post(ldpr);
+
+        // Try an invalid interaction model
+        RestAssured
+            .given()
+                .header("Link", 
Link.fromUri(baseUrl).rel("type").build().toString())
+                .body(testResourceTTL.getBytes())
+                .contentType(RDFFormat.TURTLE.getDefaultMIMEType())
+            .expect()
+                .statusCode(400)
+            .post(container);
+    }
+
+    @AfterClass
     public static void tearDown() {
         marmotta.shutdown();
     }

Reply via email to