MARMOTTA-440: continued LDP implementation
Project: http://git-wip-us.apache.org/repos/asf/marmotta/repo Commit: http://git-wip-us.apache.org/repos/asf/marmotta/commit/16bd0009 Tree: http://git-wip-us.apache.org/repos/asf/marmotta/tree/16bd0009 Diff: http://git-wip-us.apache.org/repos/asf/marmotta/diff/16bd0009 Branch: refs/heads/ldp Commit: 16bd00090037ceef088ae74c6fbf2760098a0425 Parents: 18005e1 Author: Jakob Frank <[email protected]> Authored: Mon Feb 24 10:11:21 2014 +0100 Committer: Jakob Frank <[email protected]> Committed: Mon Feb 24 10:11:21 2014 +0100 ---------------------------------------------------------------------- .../apache/marmotta/commons/vocabulary/LDP.java | 8 +- .../platform/ldp/services/LdpServiceImpl.java | 42 +++++ .../platform/ldp/util/LdpWebServiceUtils.java | 30 ++++ .../platform/ldp/webservices/LdpWebService.java | 179 ++++++++++++++----- 4 files changed, 210 insertions(+), 49 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/marmotta/blob/16bd0009/commons/marmotta-commons/src/main/java/org/apache/marmotta/commons/vocabulary/LDP.java ---------------------------------------------------------------------- diff --git a/commons/marmotta-commons/src/main/java/org/apache/marmotta/commons/vocabulary/LDP.java b/commons/marmotta-commons/src/main/java/org/apache/marmotta/commons/vocabulary/LDP.java index beae723..1599c02 100644 --- a/commons/marmotta-commons/src/main/java/org/apache/marmotta/commons/vocabulary/LDP.java +++ b/commons/marmotta-commons/src/main/java/org/apache/marmotta/commons/vocabulary/LDP.java @@ -78,7 +78,12 @@ public class LDP { */ public static final URI Resource; - /** + /** + * FIXME: Not yet part of the vocab, but used in the spec. (2014-02-24) + */ + public static final URI contains; + + /** * List of predicates that indicate the ascending order of the members in a page. */ public static final URI containerSortPredicates; @@ -117,6 +122,7 @@ public class LDP { Container = factory.createURI(LDP.NAMESPACE, "Container"); Page = factory.createURI(LDP.NAMESPACE, "Page"); Resource = factory.createURI(LDP.NAMESPACE, "Resource"); + contains = factory.createURI(LDP.NAMESPACE, "contains"); containerSortPredicates = factory.createURI(LDP.NAMESPACE, "containerSortPredicates"); member = factory.createURI(LDP.NAMESPACE, "member"); membershipPredicate = factory.createURI(LDP.NAMESPACE, "membershipPredicate"); http://git-wip-us.apache.org/repos/asf/marmotta/blob/16bd0009/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 new file mode 100644 index 0000000..a3f4095 --- /dev/null +++ b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/services/LdpServiceImpl.java @@ -0,0 +1,42 @@ +/* + * 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.services; + +import org.apache.marmotta.platform.core.api.config.ConfigurationService; +import org.apache.marmotta.platform.core.api.triplestore.SesameService; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.enterprise.context.ApplicationScoped; +import javax.inject.Inject; + +/** + * LDP Service default implementation + */ +@ApplicationScoped +public class LdpServiceImpl { + + private static final Logger log = LoggerFactory.getLogger(LdpServiceImpl.class); + + @Inject + private ConfigurationService configurationService; + + @Inject + private SesameService sesameService; + +} http://git-wip-us.apache.org/repos/asf/marmotta/blob/16bd0009/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/LdpWebServiceUtils.java ---------------------------------------------------------------------- diff --git a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/LdpWebServiceUtils.java b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/LdpWebServiceUtils.java index 4033666..f71dae3 100644 --- a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/LdpWebServiceUtils.java +++ b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/LdpWebServiceUtils.java @@ -1,5 +1,18 @@ package org.apache.marmotta.platform.ldp.util; +import info.aduna.iteration.CloseableIteration; +import info.aduna.iteration.Iteration; +import info.aduna.iteration.UnionIteration; +import org.apache.marmotta.commons.vocabulary.LDP; +import org.apache.marmotta.commons.vocabulary.XSD; +import org.openrdf.model.Statement; +import org.openrdf.model.URI; +import org.openrdf.model.vocabulary.DCTERMS; +import org.openrdf.model.vocabulary.RDF; +import org.openrdf.repository.RepositoryException; +import org.openrdf.rio.RDFHandlerException; +import org.openrdf.rio.RDFWriter; + /** * Created by jakob on 2/18/14. */ @@ -16,4 +29,21 @@ public class LdpWebServiceUtils { // Replace non-url chars with '-' .replaceAll("[^\\w]+", "-"); } + + public static void exportIteration(RDFWriter writer, URI subject, CloseableIteration<Statement, RepositoryException> iteration) throws RDFHandlerException, RepositoryException { + writer.startRDF(); + + writer.handleNamespace(LDP.PREFIX, LDP.NAMESPACE); + writer.handleNamespace(RDF.PREFIX, RDF.NAMESPACE); + writer.handleNamespace(XSD.PREFIX, XSD.NAMESPACE); + writer.handleNamespace(DCTERMS.PREFIX, DCTERMS.NAMESPACE); + + writer.handleNamespace("", subject.stringValue()); + + while (iteration.hasNext()) { + writer.handleStatement(iteration.next()); + } + + writer.endRDF(); + } } http://git-wip-us.apache.org/repos/asf/marmotta/blob/16bd0009/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 8673ce2..2e96207 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 @@ -17,11 +17,13 @@ */ package org.apache.marmotta.platform.ldp.webservices; +import info.aduna.iteration.UnionIteration; import org.apache.commons.lang3.StringUtils; import org.apache.marmotta.commons.vocabulary.DCTERMS; 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.services.LdpServiceImpl; import org.apache.marmotta.platform.ldp.util.EntityTagUtils; import org.apache.marmotta.platform.ldp.util.LdpWebServiceUtils; import org.openrdf.model.Literal; @@ -47,7 +49,9 @@ import java.lang.annotation.Annotation; import java.util.*; /** - * Linked Data Platform web services + * Linked Data Platform web services. + * + * FIXME: Try using less transactions, i.e. use a single RepositoryConnection per request * * @see <a href="http://www.w3.org/TR/ldp/">http://www.w3.org/TR/ldp/</a> * @@ -69,6 +73,9 @@ public class LdpWebService { @Inject private SesameService sesameService; + @Inject + private LdpServiceImpl ldpService; + @PostConstruct protected void initialize() { // TODO: basic initialisation @@ -77,20 +84,35 @@ public class LdpWebService { @GET public Response GET(@Context final UriInfo uriInfo, @Context Request r, @HeaderParam(HttpHeaders.ACCEPT) MediaType type) throws RepositoryException { + if (log.isDebugEnabled()) { + log.debug("GET to PDP-R <{}>", getResourceUri(uriInfo)); + } return doGetHead(uriInfo, r ,type).build(); } + @HEAD + public Response HEAD(@Context final UriInfo uriInfo, @Context Request r, @HeaderParam(HttpHeaders.ACCEPT) MediaType type) + throws RepositoryException { + if (log.isDebugEnabled()) { + log.debug("HEAD to PDP-R <{}>", getResourceUri(uriInfo)); + } + return doGetHead(uriInfo, r, type).entity(null).build(); + } + private Response.ResponseBuilder doGetHead(final UriInfo uriInfo, Request r, MediaType type) throws RepositoryException { final String subject = getResourceUri(uriInfo); - log.debug("GET to PDP-R <{}>", subject); - log.warn("NOT CHECKING EXISTENCE OF <{}>", subject); + + Response.ResponseBuilder rb404 = check404(subject); + if (rb404 != null) { + return createResponse(rb404, uriInfo); + } + // log.warn("NOT CHECKING EXISTENCE OF <{}>", subject); // TODO: Maybe this is a LDP-BR? // TODO: Proper content negotiation - r.evaluatePreconditions(); - final RDFFormat format = Rio.getWriterFormatForMIMEType(type.toString()); + final RDFFormat format = Rio.getWriterFormatForMIMEType(type.toString(), RDFFormat.TURTLE); if (format == null) { log.warn("GET to <{}> with unknown accept {}", subject, type); return createResponse(Response.Status.NOT_IMPLEMENTED, uriInfo); @@ -103,12 +125,20 @@ public class LdpWebService { RepositoryConnection con = sesameService.getConnection(); try { con.begin(); - URI s = con.getValueFactory().createURI(subject); + URI s = con.getValueFactory().createURI(subject), + ldpContext = con.getValueFactory().createURI(LDP.NAMESPACE); // TODO: this should be a little more sophisticated... - // TODO: non-membership triples flag + // TODO: non-membership triples flag / Prefer-header RDFWriter writer = Rio.createWriter(format, output); - con.exportStatements(s, null, null, false, writer); - + UnionIteration<Statement, RepositoryException> union = new UnionIteration<>( + con.getStatements(null, null, null, false, s), + con.getStatements(s, null, null, false, ldpContext) + ); + try { + LdpWebServiceUtils.exportIteration(writer, s, union); + } finally { + union.close(); + } con.commit(); } catch (RDFHandlerException e) { con.rollback(); @@ -121,7 +151,7 @@ public class LdpWebService { } } }; - return createResponse(Response.Status.OK, uriInfo).entity(entity).type(type); + return createResponse(Response.Status.OK, uriInfo).entity(entity).type(format.getDefaultMIMEType()); } } @@ -142,7 +172,7 @@ public class LdpWebService { final String container = getResourceUri(uriInfo); log.debug("POST to LDP-R <{}>", container); - // TODO: Check if resource exists + // TODO: Check if resource (container) exists log.warn("NOT CHECKING EXISTENCE OF <{}>", container); final String localName; @@ -156,26 +186,46 @@ public class LdpWebService { localName = LdpWebServiceUtils.urify(slug); log.trace("Slug-Header urified: {}", localName); } - final String newResource = uriInfo.getRequestUriBuilder().path(localName).build().toString(); - log.debug("POST to <{}> will create new LDP-R <{}>", container, newResource); - RepositoryConnection con = sesameService.getConnection(); + String newResource = uriInfo.getRequestUriBuilder().path(localName).build().toString(); + final RepositoryConnection con = sesameService.getConnection(); try { - // Add container triples (Sec. 6.4.3) + final URI c = con.getValueFactory().createURI(container); + final URI ldpContext = con.getValueFactory().createURI(LDP.NAMESPACE); con.begin(); + + URI s = con.getValueFactory().createURI(newResource); + log.trace("Checking possible name clash for new resource <{}>", newResource); + if (con.hasStatement(s, null, null, false, ldpContext)) { + int i = 0; + final String base = newResource; + do { + final String candidate = base + "-" + (++i); + log.trace("<{}> already exists, trying <{}>", s.stringValue(), candidate); + s = con.getValueFactory().createURI(candidate); + } while (con.hasStatement(s, null, null, false, ldpContext)); + newResource = s.stringValue(); + log.debug("resolved name clash, new resource will be <{}>", newResource); + } else { + log.debug("no name clash for <{}>", newResource); + } + + log.debug("POST to <{}> will create new LDP-R <{}>", container, newResource); + + // Add container triples (Sec. 6.4.3) // container and meta triples! - final URI c = con.getValueFactory().createURI(container); - final URI s = con.getValueFactory().createURI(newResource); Literal now = con.getValueFactory().createLiteral(new Date()); - con.add(c, RDF.TYPE, LDP.BasicContainer); - con.add(c, LDP.member, s); - con.remove(c, DCTERMS.modified, null); - con.add(c, DCTERMS.modified, now); + con.add(c, RDF.TYPE, LDP.BasicContainer, ldpContext); + con.add(c, LDP.contains, s, ldpContext); + con.remove(c, DCTERMS.modified, null, ldpContext); + con.add(c, DCTERMS.modified, now, ldpContext); + + con.add(s, RDF.TYPE, LDP.Resource, ldpContext); + con.add(s, DCTERMS.created, now, ldpContext); + con.add(s, DCTERMS.modified, now, ldpContext); - con.add(s, DCTERMS.created, now); - con.add(s, DCTERMS.modified, now); // LDP-BC for now! con.commit(); @@ -192,7 +242,7 @@ public class LdpWebService { con.begin(); // FIXME: We are (are we?) allowed to filter out server-managed properties here - con.add(postBody, newResource, rdfFormat); + con.add(postBody, newResource, rdfFormat, s); con.commit(); return createResponse(Response.Status.CREATED, uriInfo).location(java.net.URI.create(newResource)).build(); @@ -270,11 +320,12 @@ public class LdpWebService { try { con.begin(); - URI s = con.getValueFactory().createURI(resource); + URI s = con.getValueFactory().createURI(resource), + ldpContext = con.getValueFactory().createURI(LDP.NAMESPACE); Literal now = con.getValueFactory().createLiteral(new Date()); // Delete corresponding containment and membership triples (Sec. 6.6.1) - RepositoryResult<Statement> stmts = con.getStatements(null, LDP.member, s, false); + RepositoryResult<Statement> stmts = con.getStatements(null, LDP.member, s, false, ldpContext); try { while (stmts.hasNext()) { Statement st = stmts.next(); @@ -285,25 +336,19 @@ public class LdpWebService { } finally { stmts.close(); } - // Delete the resource - con.remove(s, null, null); + // Delete the resource meta + con.remove(s, null, null, ldpContext); + // Delete the resource data + con.clear(s); con.commit(); + return createResponse(Response.Status.NO_CONTENT, uriInfo).build(); } finally { con.close(); } - - return Response.status(Response.Status.NOT_IMPLEMENTED).build(); } - @HEAD - public Response HEAD(@Context final UriInfo uriInfo, @Context Request r, @HeaderParam(HttpHeaders.ACCEPT) MediaType type) - throws RepositoryException { - final Response.ResponseBuilder rb = doGetHead(uriInfo, r, type); - rb.entity(null); - return rb.build(); - } @PATCH public Response PATCH() { @@ -339,35 +384,71 @@ public class LdpWebService { return builder.build(); } + protected Response.ResponseBuilder check404(UriInfo uriInfo) throws RepositoryException { + return check404(getResourceUri(uriInfo)); + } + + protected Response.ResponseBuilder check404(String resourceUri) throws RepositoryException { + final RepositoryConnection con = sesameService.getConnection(); + try { + con.begin(); + final URI resource = con.getValueFactory().createURI(resourceUri), + ldpContext = con.getValueFactory().createURI(LDP.NAMESPACE); + if (con.hasStatement(resource, RDF.TYPE, null, true, ldpContext)) { + return null; + } else { + return Response.status(Response.Status.NOT_FOUND); + } + } finally { + con.commit(); + con.close(); + } + } + protected Response.ResponseBuilder createResponse(int status, UriInfo uriInfo) { - final String targetURI = uriInfo.getRequestUri().toString(); return createResponse(Response.status(status), uriInfo); } + /** + * Add all the default headers specified in LDP to the Response + * @param rb the ResponseBuilder + * @param uriInfo the uri-info to build the resource uri + * @return the provided ResponseBuilder for chaining + */ protected Response.ResponseBuilder createResponse(Response.ResponseBuilder rb, UriInfo uriInfo) { - // Link LDP-R rel='type' (Sec. 5.2.8) - rb.link(LDP.Resource.stringValue(), "type"); - // Link LDP-C rel='type' (Sec. 6.2.8) - rb.link(LDP.Container.stringValue(), "type"); - rb.link(LDP.BasicContainer.stringValue(), "type"); - // TODO: be more specific here? BasicContainer, DirectContainer, IndirectContainer // Link rel='describedby' (Sec. 5.2.11) rb.link("http://wiki.apache.org/marmotta/LDPImplementationReport", "describedby"); - // ETag (Sec. 5.2.7) final String rUri = getResourceUri(uriInfo); try { final RepositoryConnection con = sesameService.getConnection(); try { con.begin(); - rb.tag(generateETag(con, rUri)); + final URI uri = con.getValueFactory().createURI(rUri), + ldp = con.getValueFactory().createURI(LDP.NAMESPACE); + + // Link rel='type' Headers (Sec. 5.2.8, 6.2.8) + RepositoryResult<Statement> types = con.getStatements(uri, RDF.TYPE, null, false, ldp); + try { + while (types.hasNext()) { + Value o = types.next().getObject(); + if (o instanceof URI && o.stringValue().startsWith(LDP.NAMESPACE)) { + rb.link(o.stringValue(), "type"); + } + } + } finally { + types.close(); + } + + // ETag (Sec. 5.2.7) + rb.tag(generateETag(con, uri)); con.commit(); } finally { con.close(); } } catch (RepositoryException e) { - log.error("Could not build ETag", e); + log.error("Could not set ldp-response headers", e); } return new RB(rb); @@ -387,6 +468,7 @@ public class LdpWebService { } uriBuilder.path(PATH); uriBuilder.path(uriInfo.getPathParameters().getFirst("local")); +// uriBuilder.path(uriInfo.getPath().replaceFirst("/$", "")); String uri = uriBuilder.build().toString(); log.debug("RequestUri: {}", uri); @@ -399,7 +481,8 @@ public class LdpWebService { } protected static EntityTag generateETag(RepositoryConnection con, URI uri) throws RepositoryException { - RepositoryResult<Statement> stmts = con.getStatements(uri, DCTERMS.modified, null, true); + final URI ldpContext = con.getValueFactory().createURI(LDP.NAMESPACE); + final RepositoryResult<Statement> stmts = con.getStatements(uri, DCTERMS.modified, null, true, ldpContext); try { // TODO: ETag is the last-modified date (explicitly managed) thus only weak. Date latest = null;
