Repository: marmotta
Updated Branches:
  refs/heads/ldp b5a90a928 -> 0a791f426


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/ldp
Commit: 0a791f4260f9cfd8ca74265c0ff0d6fa94f3b164
Parents: 87623b1
Author: Jakob Frank <[email protected]>
Authored: Tue Sep 16 17:44:22 2014 +0200
Committer: Jakob Frank <[email protected]>
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;
+
+        }
+
+    }
+}

Reply via email to