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(); }
