You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@marmotta.apache.org by wi...@apache.org on 2014/09/19 09:31:07 UTC
[41/50] git commit: MARMOTTA-461: Added initial support for the
Prefer header in LDP MARMOTTA-521: test preferContainmentTriples passes
MARMOTTA-514: test regression: implementing MARMOTTA-461 causes some tests to
fail...
MARMOTTA-461: Added initial support for the Prefer header in LDP
MARMOTTA-521: test preferContainmentTriples passes
MARMOTTA-514: test regression: implementing MARMOTTA-461 causes some tests to fail...
Project: http://git-wip-us.apache.org/repos/asf/marmotta/repo
Commit: http://git-wip-us.apache.org/repos/asf/marmotta/commit/0a791f42
Tree: http://git-wip-us.apache.org/repos/asf/marmotta/tree/0a791f42
Diff: http://git-wip-us.apache.org/repos/asf/marmotta/diff/0a791f42
Branch: refs/heads/develop
Commit: 0a791f4260f9cfd8ca74265c0ff0d6fa94f3b164
Parents: 87623b1
Author: Jakob Frank <ja...@apache.org>
Authored: Tue Sep 16 17:44:22 2014 +0200
Committer: Jakob Frank <ja...@apache.org>
Committed: Tue Sep 16 17:45:07 2014 +0200
----------------------------------------------------------------------
.../apache/marmotta/commons/vocabulary/LDP.java | 18 +
.../marmotta/platform/ldp/api/LdpService.java | 4 +
.../marmotta/platform/ldp/api/Preference.java | 219 ++++++++++++
.../platform/ldp/services/LdpServiceImpl.java | 53 ++-
.../marmotta/platform/ldp/util/LdpUtils.java | 37 +-
.../platform/ldp/webservices/LdpWebService.java | 54 ++-
.../platform/ldp/webservices/PreferHeader.java | 347 +++++++++++++++++++
7 files changed, 701 insertions(+), 31 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/marmotta/blob/0a791f42/commons/marmotta-sesame-tools/marmotta-model-vocabs/src/main/java/org/apache/marmotta/commons/vocabulary/LDP.java
----------------------------------------------------------------------
diff --git a/commons/marmotta-sesame-tools/marmotta-model-vocabs/src/main/java/org/apache/marmotta/commons/vocabulary/LDP.java b/commons/marmotta-sesame-tools/marmotta-model-vocabs/src/main/java/org/apache/marmotta/commons/vocabulary/LDP.java
index 1293c1b..fb7498f 100644
--- a/commons/marmotta-sesame-tools/marmotta-model-vocabs/src/main/java/org/apache/marmotta/commons/vocabulary/LDP.java
+++ b/commons/marmotta-sesame-tools/marmotta-model-vocabs/src/main/java/org/apache/marmotta/commons/vocabulary/LDP.java
@@ -221,10 +221,27 @@ public class LDP {
* written to automatically exclude those new classes of triples.
*
* @see <a href="http://www.w3.org/ns/ldp#PreferEmptyContainer">PreferEmptyContainer</a>
+ * @deprecated use {@link #PreferMinimalContainer} instead
*/
+ @Deprecated
public static final URI PreferEmptyContainer;
/**
+ * PreferMinimalContainer
+ * <p>
+ * {@code http://www.w3.org/ns/ldp#PreferMinimalContainer}.
+ * <p>
+ * URI identifying the subset of a LDPC's triples present in an empty
+ * LDPC, for example to allow clients to express interest in receiving
+ * them. Currently this excludes containment and membership triples, but
+ * in the future other exclusions might be added. This definition is
+ * written to automatically exclude those new classes of triples.
+ *
+ * @see <a href="http://www.w3.org/ns/ldp#PreferMinimalContainer">PreferMinimalContainer</a>
+ */
+ public static final URI PreferMinimalContainer;
+
+ /**
* PreferMembership
* <p>
* {@code http://www.w3.org/ns/ldp#PreferMembership}.
@@ -278,6 +295,7 @@ public class LDP {
PreferContainment = factory.createURI(LDP.NAMESPACE, "PreferContainment");
PreferEmptyContainer = factory.createURI(LDP.NAMESPACE, "PreferEmptyContainer");
PreferMembership = factory.createURI(LDP.NAMESPACE, "PreferMembership");
+ PreferMinimalContainer = factory.createURI(LDP.NAMESPACE, "PreferMinimalContainer");
RDFSource = factory.createURI(LDP.NAMESPACE, "RDFSource");
Resource = factory.createURI(LDP.NAMESPACE, "Resource");
}
http://git-wip-us.apache.org/repos/asf/marmotta/blob/0a791f42/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 f2ab2e0..20ee27d 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
@@ -270,6 +270,10 @@ public interface LdpService {
void exportResource(RepositoryConnection connection, URI resource, OutputStream output, RDFFormat format) throws RepositoryException, RDFHandlerException;
+ void exportResource(RepositoryConnection outputConn, String resource, OutputStream output, RDFFormat format, Preference preference) throws RDFHandlerException, RepositoryException;
+
+ void exportResource(RepositoryConnection outputConn, URI resource, OutputStream output, RDFFormat format, Preference preference) throws RepositoryException, RDFHandlerException;
+
void exportBinaryResource(RepositoryConnection connection, String resource, OutputStream out) throws RepositoryException, IOException;
void exportBinaryResource(RepositoryConnection connection, URI resource, OutputStream out) throws RepositoryException, IOException;
http://git-wip-us.apache.org/repos/asf/marmotta/blob/0a791f42/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/api/Preference.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/api/Preference.java b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/api/Preference.java
new file mode 100644
index 0000000..8a32168
--- /dev/null
+++ b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/api/Preference.java
@@ -0,0 +1,219 @@
+/*
+ * 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.api;
+
+import org.apache.marmotta.commons.vocabulary.LDP;
+
+/**
+ * Preferences, which triples to include in a response.
+ *
+ * @see <a href="http://www.w3.org/TR/ldp/#prefer-parameters">http://www.w3.org/TR/ldp/#prefer-parameters</a>
+ *
+ * @author Jakob Frank
+ */
+public class Preference {
+
+ private boolean content;
+ private boolean minimal;
+ private boolean membership;
+ private boolean containment;
+ private boolean minimalContainer;
+
+ private Preference(boolean minimal, boolean elements) {
+ this.content = !minimal;
+ this.minimal = minimal;
+ this.membership = elements;
+ this.containment = elements;
+ this.minimalContainer = elements;
+ }
+
+ /**
+ * Reflects a {@code Prefer: return="minimal"} header
+ * @return {@code true} if the minimal representation is preferred by the client
+ */
+ public boolean isMinimal() {
+ return minimal;
+ }
+
+ /**
+ * Should the LDP-RS content be included in the response?
+ * @return {@code true} if the content triples should be included in the response
+ */
+ public boolean includeContent() {
+ return content;
+ }
+
+ /**
+ * Should membership triples be included in the response?
+ *
+ * @return {@code true} if membership triples should be included in the response
+ * @see <a href="http://www.w3.org/TR/ldp/#dfn-membership-triples">http://www.w3.org/TR/ldp/#dfn-membership-triples</a>
+ */
+ public boolean includeMembership() {
+ return membership;
+ }
+
+ /**
+ * Should containment triples be included in the response?
+ *
+ * @return {@code true} if containment triples should be included in the response
+ * @see <a href="http://www.w3.org/TR/ldp/#dfn-containment-triples">http://www.w3.org/TR/ldp/#dfn-containment-triples</a>
+ */
+ public boolean includeContainment() {
+ return containment;
+ }
+
+ /**
+ * Should minimal container triples be included in the response?
+ *
+ * @return {@code true} if minimal container triples should be included in the response
+ * @see <a href="http://www.w3.org/TR/ldp/#dfn-minimal-container-triples">http://www.w3.org/TR/ldp/#dfn-minimal-container-triples</a>
+ */
+ public boolean includeMinimalContainer() {
+ return minimalContainer;
+ }
+
+ /**
+ * If minimal is set to {@code true}, all other settings will be set to false.
+ * @param minimal whether minimal representation is preferred.
+ */
+ public void setMinimal(boolean minimal) {
+ this.minimal = minimal;
+ if (minimal) {
+ this.content = this.membership = this.containment = this.minimalContainer = false;
+ }
+ }
+
+ /**
+ * Setting content to {@code true} will also set minimal to {@code false}
+ * @param content should the LDP-RS content be included in the response?
+ */
+ public void setContent(boolean content) {
+ this.content = content;
+ if (content) {
+ this.minimal = false;
+ }
+ }
+
+ /**
+ * Setting membership to {@code true} will also set minimal to {@code false}
+ *
+ * @param membership should membership triples be included in the response?
+ * @see <a href="http://www.w3.org/TR/ldp/#dfn-membership-triples">http://www.w3.org/TR/ldp/#dfn-membership-triples</a>
+ */
+ public void setMembership(boolean membership) {
+ this.membership = membership;
+ if (membership) {
+ this.minimal = false;
+ }
+ }
+
+ /**
+ * Setting containment to {@code true} will also set minimal to {@code false}
+ *
+ * @param containment should containment triples be included in the response?
+ * @see <a href="http://www.w3.org/TR/ldp/#dfn-containment-triples">http://www.w3.org/TR/ldp/#dfn-containment-triples</a>
+ */
+ public void setContainment(boolean containment) {
+ this.containment = containment;
+ if (containment) {
+ this.minimal = false;
+ }
+ }
+
+ /**
+ * Setting minimal container to {@code true} will also set minimal to {@code false}
+ *
+ * @param minimalContainer should minimal container triples be included in the response?
+ * @see <a href="http://www.w3.org/TR/ldp/#dfn-minimal-container-triples">http://www.w3.org/TR/ldp/#dfn-minimal-container-triples</a>
+ */
+ public void setMinimalContainer(boolean minimalContainer) {
+ this.minimalContainer = minimalContainer;
+ if (minimalContainer) {
+ this.minimal = false;
+ }
+ }
+
+ /**
+ * The default preference: not minimal, all included.
+ * @return the default preference.
+ */
+ public static Preference defaultPreference() {
+ return new Preference(false, true);
+ }
+
+ /**
+ * The minimal preference: minimal is {@code true}, nothing included.
+ * @return the minimal preference.
+ */
+ public static Preference minimalPreference() {
+ return new Preference(true, false);
+ }
+
+ /**
+ * Non-minimal preference, with only the args included.
+ *
+ * @param includes LDP-Preference URIs to include in the response
+ * @return a non-minimal preference, with only the provided parts included.
+ * @see <a href="http://www.w3.org/TR/ldp/#h5_prefer-uris">http://www.w3.org/TR/ldp/#h5_prefer-uris</a>
+ */
+ public static Preference includePreference(String... includes) {
+ final Preference pref = new Preference(false, false);
+ pref.content = false;
+ for (String i: includes) {
+ if (LDP.PreferContainment.stringValue().equals(i)) {
+ pref.setContainment(true);
+ } else if (LDP.PreferMembership.stringValue().equals(i)) {
+ pref.setMembership(true);
+ } else if (LDP.PreferMinimalContainer.stringValue().equals(i)) {
+ pref.setMinimalContainer(true);
+ } else if (LDP.PreferEmptyContainer.stringValue().equals(i)) {
+ pref.setMinimalContainer(true);
+ } else {
+ // ignore unknown includes
+ }
+ }
+ return pref;
+ }
+
+ /**
+ * Non-minimal preference, with all the provided args omitted.
+ *
+ * @param omits LDP-Preference URIs to omit in the response
+ * @return a non-minimal preference, with all provided parts excluded.
+ * @see <a href="http://www.w3.org/TR/ldp/#h5_prefer-uris">http://www.w3.org/TR/ldp/#h5_prefer-uris</a>
+ */
+ public static Preference omitPreference(String... omits) {
+ final Preference pref = new Preference(false, true);
+ pref.content = true;
+ for (String e: omits) {
+ if (LDP.PreferContainment.stringValue().equals(e)) {
+ pref.setContainment(false);
+ } else if (LDP.PreferMembership.stringValue().equals(e)) {
+ pref.setMembership(false);
+ } else if (LDP.PreferMinimalContainer.stringValue().equals(e)) {
+ pref.setMinimalContainer(false);
+ } else if (LDP.PreferEmptyContainer.stringValue().equals(e)) {
+ pref.setMinimalContainer(false);
+ } else {
+ // ignore unknown omits
+ }
+ }
+ return pref;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/marmotta/blob/0a791f42/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 a336c6b..624ff84 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
@@ -17,15 +17,14 @@
*/
package org.apache.marmotta.platform.ldp.services;
-import info.aduna.iteration.FilterIteration;
-import info.aduna.iteration.Iterations;
-import info.aduna.iteration.UnionIteration;
+import info.aduna.iteration.*;
import org.apache.commons.io.IOUtils;
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.ldp.api.LdpBinaryStoreService;
import org.apache.marmotta.platform.ldp.api.LdpService;
+import org.apache.marmotta.platform.ldp.api.Preference;
import org.apache.marmotta.platform.ldp.exceptions.IncompatibleResourceTypeException;
import org.apache.marmotta.platform.ldp.exceptions.InvalidInteractionModelException;
import org.apache.marmotta.platform.ldp.exceptions.InvalidModificationException;
@@ -252,17 +251,51 @@ public class LdpServiceImpl implements LdpService {
@Override
public void exportResource(RepositoryConnection connection, URI resource, OutputStream output, RDFFormat format) throws RepositoryException, RDFHandlerException {
+ exportResource(connection, resource, output, format, null);
+ }
+
+ @Override
+ public void exportResource(RepositoryConnection connection, String resource, OutputStream output, RDFFormat format, Preference preference) throws RDFHandlerException, RepositoryException {
+ exportResource(connection, buildURI(resource), output, format, preference);
+ }
+
+ @Override
+ public void exportResource(RepositoryConnection connection, final URI resource, OutputStream output, RDFFormat format, final Preference preference) throws RepositoryException, RDFHandlerException {
// TODO: this should be a little more sophisticated...
// TODO: non-membership triples flag / Prefer-header
- RDFWriter writer = Rio.createWriter(format, output);
- UnionIteration<Statement, RepositoryException> union = new UnionIteration<>(
- connection.getStatements(resource, null, null, false, ldpContext),
- connection.getStatements(null, null, null, false, resource)
- );
+ final RDFWriter writer = Rio.createWriter(format, output);
+ final CloseableIteration<Statement, RepositoryException> contentStatements;
+ if (preference == null || preference.includeContent()) {
+ contentStatements = connection.getStatements(null, null, null, false, resource);
+ } else {
+ contentStatements = new EmptyIteration<>();
+ }
try {
- LdpUtils.exportIteration(writer, resource, union);
+ CloseableIteration<Statement, RepositoryException> ldpStatements = connection.getStatements(resource, null, null, false, ldpContext);
+ if (preference != null) {
+ // FIXME: Get the membership predicate from the container. See http://www.w3.org/TR/ldp/#h5_ldpdc-containtriples
+ final URI membershipPred = null;
+ ldpStatements = new FilterIteration<Statement, RepositoryException>(ldpStatements) {
+ @Override
+ protected boolean accept(Statement stmt) throws RepositoryException {
+ final URI p = stmt.getPredicate();
+ final Resource s = stmt.getSubject();
+ final Value o = stmt.getObject();
+
+
+ if (p.equals(LDP.contains)) return preference.includeContainment();
+ if (p.equals(membershipPred)) return preference.includeMembership();
+
+ return preference.includeMinimalContainer();
+ }
+ };
+ }
+ final CloseableIteration<Statement, RepositoryException> statements = new UnionIteration<>(
+ ldpStatements, contentStatements
+ );
+ LdpUtils.exportIteration(writer, resource, statements);
} finally {
- union.close();
+ contentStatements.close();
}
}
http://git-wip-us.apache.org/repos/asf/marmotta/blob/0a791f42/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/LdpUtils.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/LdpUtils.java b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/LdpUtils.java
index 9a81086..e316d18 100644
--- a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/LdpUtils.java
+++ b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/LdpUtils.java
@@ -21,6 +21,8 @@ import info.aduna.iteration.CloseableIteration;
import org.apache.commons.lang3.StringUtils;
import org.apache.marmotta.commons.vocabulary.LDP;
import org.apache.marmotta.commons.vocabulary.XSD;
+import org.apache.marmotta.platform.ldp.api.Preference;
+import org.apache.marmotta.platform.ldp.webservices.PreferHeader;
import org.apache.tika.mime.MimeTypeException;
import org.apache.tika.mime.MimeTypes;
import org.openrdf.model.Statement;
@@ -36,12 +38,8 @@ import org.openrdf.rio.RDFWriter;
import org.slf4j.Logger;
import javax.ws.rs.core.MediaType;
-import java.io.File;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
-import java.net.URL;
-import java.nio.file.Paths;
-import java.util.Iterator;
import java.util.Set;
/**
@@ -157,6 +155,37 @@ public class LdpUtils {
return new URIImpl(resource.getNamespace());
}
+ /**
+ * Convert a PreferHeader into a LDP Preference.
+ * @param prefer the PreferHeader to parse
+ * @return the Preference
+ */
+ public static Preference parsePreferHeader(PreferHeader prefer) {
+ if (prefer == null) return null;
+ // we only support "return"-prefers
+ if (!PreferHeader.PREFERENCE_RETURN.equals(prefer.getPreference())) {
+ return null;
+ }
+ if (PreferHeader.RETURN_MINIMAL.equals(prefer.getPreferenceValue())) {
+ return Preference.minimalPreference();
+ }
+
+ if (PreferHeader.RETURN_REPRESENTATION.equals(prefer.getPreferenceValue())) {
+ final String include = prefer.getParamValue(PreferHeader.RETURN_PARAM_INCLUDE);
+ final String omit = prefer.getParamValue(PreferHeader.RETURN_PARAM_OMIT);
+ if (StringUtils.isNotBlank(include) && StringUtils.isBlank(omit)) {
+ return Preference.includePreference(include.split("\\s+"));
+ } else if (StringUtils.isNotBlank(omit) && StringUtils.isBlank(include)) {
+ return Preference.omitPreference(omit.split("\\s+"));
+ } else if (StringUtils.isBlank(include) && StringUtils.isBlank(omit)) {
+ return Preference.defaultPreference();
+ } else {
+ return null;
+ }
+ }
+ return null;
+ }
+
private LdpUtils() {
// Static access only
}
http://git-wip-us.apache.org/repos/asf/marmotta/blob/0a791f42/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 08599e0..eb32545 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
@@ -26,6 +26,7 @@ import org.apache.marmotta.platform.core.api.exporter.ExportService;
import org.apache.marmotta.platform.core.api.triplestore.SesameService;
import org.apache.marmotta.platform.core.events.SesameStartupEvent;
import org.apache.marmotta.platform.ldp.api.LdpService;
+import org.apache.marmotta.platform.ldp.api.Preference;
import org.apache.marmotta.platform.ldp.exceptions.IncompatibleResourceTypeException;
import org.apache.marmotta.platform.ldp.exceptions.InvalidInteractionModelException;
import org.apache.marmotta.platform.ldp.exceptions.InvalidModificationException;
@@ -81,6 +82,8 @@ public class LdpWebService {
static final String HTTP_HEADER_SLUG = "Slug";
static final String HTTP_HEADER_ACCEPT_POST = "Accept-Post";
static final String HTTP_HEADER_ACCEPT_PATCH = "Accept-Patch";
+ static final String HTTP_HEADER_PREFER = "Prefer";
+ static final String HTTP_HEADER_PREFERENCE_APPLIED = "Preference-Applied";
static final String HTTP_METHOD_PATCH = "PATCH";
private Logger log = org.slf4j.LoggerFactory.getLogger(this.getClass());
@@ -149,24 +152,26 @@ public class LdpWebService {
}
@GET
- public Response GET(@Context final UriInfo uriInfo, @Context Request r,
- @HeaderParam(HttpHeaders.ACCEPT) @DefaultValue(MediaType.WILDCARD) String type)
+ public Response GET(@Context final UriInfo uriInfo,
+ @HeaderParam(HttpHeaders.ACCEPT) @DefaultValue(MediaType.WILDCARD) String type,
+ @HeaderParam(HTTP_HEADER_PREFER) PreferHeader preferHeader)
throws RepositoryException {
final String resource = ldpService.getResourceUri(uriInfo);
log.debug("GET to LDPR <{}>", resource);
- return buildGetResponse(resource, r, MarmottaHttpUtils.parseAcceptHeader(type)).build();
+ return buildGetResponse(resource, MarmottaHttpUtils.parseAcceptHeader(type), preferHeader).build();
}
@HEAD
- public Response HEAD(@Context final UriInfo uriInfo, @Context Request r,
- @HeaderParam(HttpHeaders.ACCEPT) @DefaultValue(MediaType.WILDCARD) String type)
+ public Response HEAD(@Context final UriInfo uriInfo,
+ @HeaderParam(HttpHeaders.ACCEPT) @DefaultValue(MediaType.WILDCARD) String type,
+ @HeaderParam(HTTP_HEADER_PREFER) PreferHeader preferHeader)
throws RepositoryException {
final String resource = ldpService.getResourceUri(uriInfo);
log.debug("HEAD to LDPR <{}>", resource);
- return buildGetResponse(resource, r, MarmottaHttpUtils.parseAcceptHeader(type)).entity(null).build();
+ return buildGetResponse(resource, MarmottaHttpUtils.parseAcceptHeader(type), preferHeader).entity(null).build();
}
- private Response.ResponseBuilder buildGetResponse(final String resource, Request r, List<ContentType> acceptedContentTypes) throws RepositoryException {
+ private Response.ResponseBuilder buildGetResponse(final String resource, List<ContentType> acceptedContentTypes, PreferHeader preferHeader) throws RepositoryException {
log.trace("LDPR requested media type {}", acceptedContentTypes);
final RepositoryConnection conn = sesameService.getConnection();
try {
@@ -198,7 +203,7 @@ public class LdpWebService {
if (MarmottaHttpUtils.bestContentType(MarmottaHttpUtils.parseAcceptHeader("*/*"), acceptedContentTypes) != null) {
log.trace("Unknown type of LDP-NR <{}> is compatible with wildcard - sending back LDP-NR without Content-Type", resource);
// Client will accept anything, send back LDP-NR
- final Response.ResponseBuilder resp = buildGetResponseBinaryResource(conn, resource);
+ final Response.ResponseBuilder resp = buildGetResponseBinaryResource(conn, resource, preferHeader);
conn.commit();
return resp;
} else if (rdfContentType == null) {
@@ -210,7 +215,7 @@ public class LdpWebService {
return resp;
} else {
log.debug("Client is asking for a RDF-Serialisation of LDP-NS <{}>, sending meta-data", resource);
- final Response.ResponseBuilder resp = buildGetResponseSourceResource(conn, resource, Rio.getWriterFormatForMIMEType(rdfContentType.getMime(), RDFFormat.TURTLE));
+ final Response.ResponseBuilder resp = buildGetResponseSourceResource(conn, resource, Rio.getWriterFormatForMIMEType(rdfContentType.getMime(), RDFFormat.TURTLE), preferHeader);
conn.commit();
return resp;
}
@@ -225,12 +230,12 @@ public class LdpWebService {
return resp;
} else {
log.debug("Client is asking for a RDF-Serialisation of LDP-NS <{}>, sending meta-data", resource);
- final Response.ResponseBuilder resp = buildGetResponseSourceResource(conn, resource, Rio.getWriterFormatForMIMEType(rdfContentType.getMime(), RDFFormat.TURTLE));
+ final Response.ResponseBuilder resp = buildGetResponseSourceResource(conn, resource, Rio.getWriterFormatForMIMEType(rdfContentType.getMime(), RDFFormat.TURTLE), preferHeader);
conn.commit();
return resp;
}
} else {
- final Response.ResponseBuilder resp = buildGetResponseBinaryResource(conn, resource);
+ final Response.ResponseBuilder resp = buildGetResponseBinaryResource(conn, resource, preferHeader);
conn.commit();
return resp;
}
@@ -243,7 +248,7 @@ public class LdpWebService {
conn.commit();
return resp;
} else {
- final Response.ResponseBuilder resp = buildGetResponseSourceResource(conn, resource, Rio.getWriterFormatForMIMEType(bestType.getMime(), RDFFormat.TURTLE));
+ final Response.ResponseBuilder resp = buildGetResponseSourceResource(conn, resource, Rio.getWriterFormatForMIMEType(bestType.getMime(), RDFFormat.TURTLE), preferHeader);
conn.commit();
return resp;
}
@@ -267,9 +272,10 @@ public class LdpWebService {
return addOptionsHeader(connection, resource, response);
}
- private Response.ResponseBuilder buildGetResponseBinaryResource(RepositoryConnection connection, final String resource) throws RepositoryException {
+ private Response.ResponseBuilder buildGetResponseBinaryResource(RepositoryConnection connection, final String resource, PreferHeader preferHeader) throws RepositoryException {
final String realType = ldpService.getMimeType(connection, resource);
log.debug("Building response for LDP-NR <{}> with format {}", resource, realType);
+ final Preference preference = LdpUtils.parsePreferHeader(preferHeader);
final StreamingOutput entity = new StreamingOutput() {
@Override
public void write(OutputStream out) throws IOException, WebApplicationException {
@@ -291,12 +297,19 @@ public class LdpWebService {
}
};
// Sec. 4.2.2.2
- return addOptionsHeader(connection, resource, createResponse(connection, Response.Status.OK, resource).entity(entity).type(realType));
+ final Response.ResponseBuilder resp = addOptionsHeader(connection, resource, createResponse(connection, Response.Status.OK, resource).entity(entity).type(realType));
+ if (preferHeader != null) {
+ if (preference.isMinimal()) {
+ resp.status(Response.Status.NO_CONTENT).entity(null).header(HTTP_HEADER_PREFERENCE_APPLIED, PreferHeader.fromPrefer(preferHeader).parameters(null).build());
+ }
+ }
+ return resp;
}
- private Response.ResponseBuilder buildGetResponseSourceResource(RepositoryConnection conn, final String resource, final RDFFormat format) throws RepositoryException {
+ private Response.ResponseBuilder buildGetResponseSourceResource(RepositoryConnection conn, final String resource, final RDFFormat format, final PreferHeader preferHeader) throws RepositoryException {
// Deliver all triples from the <subject> context.
log.debug("Building response for LDP-RS <{}> with RDF format {}", resource, format.getDefaultMIMEType());
+ final Preference preference = LdpUtils.parsePreferHeader(preferHeader);
final StreamingOutput entity = new StreamingOutput() {
@Override
public void write(OutputStream output) throws IOException, WebApplicationException {
@@ -304,7 +317,7 @@ public class LdpWebService {
final RepositoryConnection outputConn = sesameService.getConnection();
try {
outputConn.begin();
- ldpService.exportResource(outputConn, resource, output, format);
+ ldpService.exportResource(outputConn, resource, output, format, preference);
outputConn.commit();
} catch (RDFHandlerException e) {
outputConn.rollback();
@@ -321,7 +334,14 @@ public class LdpWebService {
}
};
// Sec. 4.2.2.2
- return addOptionsHeader(conn, resource, createResponse(conn, Response.Status.OK, resource).entity(entity).type(format.getDefaultMIMEType()));
+ final Response.ResponseBuilder resp = addOptionsHeader(conn, resource, createResponse(conn, Response.Status.OK, resource).entity(entity).type(format.getDefaultMIMEType()));
+ if (preference != null) {
+ if (preference.isMinimal()) {
+ resp.status(Response.Status.NO_CONTENT).entity(null);
+ }
+ resp.header(HTTP_HEADER_PREFERENCE_APPLIED, PreferHeader.fromPrefer(preferHeader).parameters(null).build());
+ }
+ return resp;
}
/**
http://git-wip-us.apache.org/repos/asf/marmotta/blob/0a791f42/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/webservices/PreferHeader.java
----------------------------------------------------------------------
diff --git a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/webservices/PreferHeader.java b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/webservices/PreferHeader.java
new file mode 100644
index 0000000..7406c81
--- /dev/null
+++ b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/webservices/PreferHeader.java
@@ -0,0 +1,347 @@
+/*
+ * 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.webservices;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.marmotta.platform.core.exception.InvalidArgumentException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * HTML Prefer Header.
+ *
+ * @author Jakob Frank
+ * @see <a href="http://www.ietf.org/rfc/rfc7240.txt">http://www.ietf.org/rfc/rfc7240.txt</a>
+ */
+public class PreferHeader {
+
+ public static final String PREFERENCE_RESPOND_ASYNC ="respond-async";
+ public static final String PREFERENCE_RETURN = "return";
+ public static final String RETURN_REPRESENTATION = "representation";
+ public static final String RETURN_MINIMAL = "minimal";
+ public static final String PREFERENCE_WAIT = "wait";
+ public static final String PREFERENCE_HANDLING = "handling";
+ public static final String HANDLING_STRICT = "strict";
+ public static final String HANDLING_LENIENT = "lenient";
+
+ public static final String RETURN_PARAM_INCLUDE = "include";
+ public static final String RETURN_PARAM_OMIT = "omit";
+
+ public static Logger log = LoggerFactory.getLogger(PreferHeader.class);
+
+ private String preference, preferenceValue;
+
+ private Map<String, String> params;
+
+ private PreferHeader(String preference) {
+ this.preference = preference;
+ this.params = new LinkedHashMap<>();
+ }
+
+ /**
+ * Get the preference,
+ * e.g. {@code foo} from {@code Prefer: foo="bar"}
+ *
+ * @return the preference of the prefer-header
+ */
+ public String getPreference() {
+ return preference;
+ }
+
+ /**
+ * Get the value of the preference,
+ * e.g. {@code bar} from {@code Prefer: foo="bar"}.
+ * @return the preference value of the prefer-header
+ */
+ public String getPreferenceValue() {
+ return preferenceValue;
+ }
+
+ /**
+ * Get the parameters of the prefer-header.
+ * @return the prefer-parameters
+ */
+ public Map<String, String> getParams() {
+ return Collections.unmodifiableMap(params);
+ }
+
+ /**
+ * Get the parameter value of the prefer-header,
+ * e,g, {@code val2} from {@code Prefer: foo="bar"; a1="val1"; a2="val2"} for {@code header.getParamValue("a2")}.
+ * @param param the param to get the value of
+ * @return the value of the requested parameter, or {@code null}
+ */
+ public String getParamValue(String param) {
+ return params.get(param);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder(preference);
+ if (StringUtils.isNotBlank(preferenceValue)) {
+ sb.append("=\"").append(preferenceValue).append("\"");
+ }
+ for (String param: params.keySet()) {
+ sb.append("; ").append(param);
+ final String value = params.get(param);
+ if (StringUtils.isNotBlank(value)) {
+ sb.append("=\"").append(value).append("\"");
+ }
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Parse a PreferHeader.
+ * @param headerValue the header value to parse
+ * @return the parsed PreferHeader
+ */
+ public static PreferHeader valueOf(String headerValue) {
+ if (StringUtils.isBlank(headerValue)) {
+ log.error("Empty Prefer-Header - what should I do now?");
+ throw new InvalidArgumentException();
+ }
+
+ String pref = null, val = null;
+ final Map<String, String> params = new LinkedHashMap<>();
+ final String[] parts = headerValue.split("\\s*;\\s");
+ for (int i = 0; i < parts.length; i++) {
+ final String part = parts[i];
+ final String[] kv = part.split("\\s*=\\s*", 2);
+ if (i == 0) {
+ pref = StringUtils.trimToNull(kv[0]);
+ if (kv.length > 1) {
+ val = StringUtils.trimToNull(StringUtils.removeStart(StringUtils.removeEnd(kv[1], "\""), "\""));
+ }
+ } else {
+ String p, pval = null;
+ p = StringUtils.trimToNull(kv[0]);
+ if (kv.length > 1) {
+ pval = StringUtils.trimToNull(StringUtils.removeStart(StringUtils.removeEnd(kv[1], "\""), "\""));
+ }
+ params.put(p, pval);
+ }
+ }
+
+ final PreferHeader header = new PreferHeader(pref);
+ header.preferenceValue = val;
+ header.params = params;
+
+ return header;
+ }
+
+ /**
+ * Create and initialize a PreferBuilder for a {@code Prefer: respond-async} header.
+ * @return initialized PreferBuilder
+ */
+ public static PreferBuilder preferRespondAsync() {
+ return new PreferBuilder(PREFERENCE_RESPOND_ASYNC);
+ }
+
+ /**
+ * Create and initialize a PreferBuilder for a {@code Prefer: return="representation"} header.
+ * @return initialized PreferBuilder
+ */
+ public static PreferBuilder preferReturnRepresentation() {
+ return new PreferBuilder(PREFERENCE_RETURN, RETURN_REPRESENTATION);
+ }
+
+ /**
+ * Create and initialize a PreferBuilder for a {@code Prefer: return="minimal"} header.
+ * @return initialized PreferBuilder
+ */
+ public static PreferBuilder preferReturnMinimal() {
+ return new PreferBuilder(PREFERENCE_RETURN, RETURN_MINIMAL);
+ }
+
+ /**
+ * Create and initialize a PreferBuilder for a {@code Prefer: wait="X"} header.
+ * @param seconds seconds to wait, the <em>X</em> in the example.
+ * @return initialized PreferBuilder
+ */
+ public static PreferBuilder preferWait(int seconds) {
+ return new PreferBuilder(PREFERENCE_WAIT, String.valueOf(seconds));
+ }
+
+ /**
+ * Create and initialize a PreferBuilder for a {@code Prefer: handling="strict"} header.
+ * @return initialized PreferBuilder
+ */
+ public static PreferBuilder preferHandlingStrict() {
+ return new PreferBuilder(PREFERENCE_HANDLING, HANDLING_STRICT);
+ }
+
+ /**
+ * Create and initialize a PreferBuilder for a {@code Prefer: handling="lenient"} header.
+ * @return initialized PreferBuilder
+ */
+ public static PreferBuilder preferHandlingLenient() {
+ return new PreferBuilder(PREFERENCE_HANDLING, HANDLING_LENIENT);
+ }
+
+ /**
+ * Create a PreferBuilder initialized with the provided PreferHeader.
+ * @param prefer the PreferHeader used for initialisation
+ * @return initialized PreferBuilder
+ */
+ public static PreferBuilder fromPrefer(PreferHeader prefer) {
+ final PreferBuilder builder = new PreferBuilder(prefer.preference, prefer.preferenceValue);
+ builder.params.putAll(prefer.params);
+ return builder;
+ }
+
+ /**
+ * Create a PreferBuilder for an arbitrary preference
+ * @param preference the preference
+ * @return initialized PreferBuilder
+ */
+ public static PreferBuilder prefer(String preference) {
+ return prefer(preference, null);
+ }
+
+ /**
+ * Create a PreferBuilder for an arbitrary preference
+ * @param preference the preference
+ * @param value the value of the preference
+ * @return initialized PreferBuilder
+ */
+ private static PreferBuilder prefer(String preference, String value) {
+ return new PreferBuilder(preference, value);
+ }
+
+ /**
+ * Builder for PreferHeader
+ */
+ public static class PreferBuilder {
+
+ private String preference;
+ private String preferenceValue;
+ private Map<String, String> params;
+
+ private PreferBuilder(String preference) {
+ this(preference, null);
+ }
+
+ private PreferBuilder(String preference, String preferenceValue) {
+ this.preference = preference;
+ this.preferenceValue = preferenceValue;
+ this.params = new HashMap<>();
+ }
+
+ private PreferBuilder preference(String preference) {
+ return preference(preference, null);
+ }
+
+ private PreferBuilder preference(String preference, String value) {
+ this.preference = preference;
+ this.preferenceValue = value;
+ return this;
+ }
+
+ /**
+ * Add a parameter (without value)
+ * @param parameter the parameter to add
+ * @return the PreferBuilder for chaining
+ */
+ public PreferBuilder parameter(String parameter) {
+ this.params.put(parameter, null);
+ return this;
+ }
+
+ /**
+ * Add a parameter with the provided value. If the value is {@code null}, the parameter is removed.
+ * @param parameter the parameter to add (or remove)
+ * @param value the parameter value (or {@code null} to remove
+ * @return the PreferBuilder for chaining
+ */
+ public PreferBuilder parameter(String parameter, String value) {
+ if (value == null) {
+ this.params.remove(parameter);
+ } else {
+ this.params.put(parameter, value);
+ }
+ return this;
+ }
+
+ /**
+ * Add the provided parameters and their values. If the argument is {@code null}, all parameters will be removed.
+ * @param params the parameters to add (or {@code null} to remove all parameters)
+ * @return the PreferBuilder for chaining
+ */
+ public PreferBuilder parameters(Map<String, String> params) {
+ if (params == null) {
+ this.params.clear();
+ } else {
+ this.params.putAll(params);
+ }
+ return this;
+ }
+
+ /**
+ * <strong>LDP specific:</strong> Add a "include" parameter for the given URIs.
+ * @param ldpPreferUri the URIs to "include"
+ * @return the PreferBuilder for chaining
+ */
+ public PreferBuilder include(String... ldpPreferUri) {
+ return _ldp(RETURN_PARAM_INCLUDE, ldpPreferUri);
+ }
+
+ /**
+ * <strong>LDP specific:</strong> Add a "omit" parameter for the given URIs.
+ * @param ldpPreferUri the URIs to "omit"
+ * @return the PreferBuilder for chaining
+ */
+ public PreferBuilder omit(String... ldpPreferUri) {
+ return _ldp(RETURN_PARAM_OMIT, ldpPreferUri);
+ }
+
+ private PreferBuilder _ldp(String param, String... values) {
+ if (values == null) {
+ this.params.remove(param);
+ } else {
+ final StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < values.length; i++) {
+ if (i > 0) {
+ sb.append(" ");
+ }
+ sb.append(values[i]);
+ }
+ this.params.put(param, sb.toString());
+ }
+ return this;
+ }
+
+ /**
+ * Create the PreferHeader. The builder can be reused.
+ * @return the PreferHeader
+ */
+ public PreferHeader build() {
+ final PreferHeader header = new PreferHeader(this.preference);
+ header.preferenceValue = preferenceValue;
+ header.params.putAll(params);
+ return header;
+
+ }
+
+ }
+}