Merge branch 'develop' of https://git-wip-us.apache.org/repos/asf/marmotta into develop
diff --git a/platform/marmotta-core/src/test/java/org/apache/marmotta/platform/core/test/ld/LinkedDataTest.java b/platform/marmotta-core/src/test/java/org/apache/marmotta/platform/core/test/ld/LinkedDataTest.java
index ce50ab2..cd62f51 100644
--- a/platform/marmotta-core/src/test/java/org/apache/marmotta/platform/core/test/ld/LinkedDataTest.java
+++ b/platform/marmotta-core/src/test/java/org/apache/marmotta/platform/core/test/ld/LinkedDataTest.java
@@ -17,14 +17,8 @@
package org.apache.marmotta.platform.core.test.ld;
-import static com.jayway.restassured.RestAssured.expect;
-import static com.jayway.restassured.RestAssured.given;
-
-import java.io.IOException;
-import java.io.StringReader;
-import java.io.StringWriter;
-import java.util.Random;
-
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.jayway.restassured.RestAssured;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.RandomStringUtils;
import org.apache.marmotta.platform.core.api.triplestore.ContextService;
@@ -33,11 +27,7 @@
import org.apache.marmotta.platform.core.webservices.resource.ContentWebService;
import org.apache.marmotta.platform.core.webservices.resource.MetaWebService;
import org.apache.marmotta.platform.core.webservices.resource.ResourceWebService;
-import org.junit.AfterClass;
-import org.junit.Assert;
-import org.junit.BeforeClass;
-import org.junit.Ignore;
-import org.junit.Test;
+import org.junit.*;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
import org.openrdf.repository.Repository;
@@ -50,8 +40,13 @@
import org.openrdf.rio.Rio;
import org.openrdf.sail.memory.MemoryStore;
-import com.fasterxml.jackson.databind.ObjectMapper;
-import com.jayway.restassured.RestAssured;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.Random;
+
+import static com.jayway.restassured.RestAssured.expect;
+import static com.jayway.restassured.RestAssured.given;
/**
* This test verifies the functionality of the Linked Data endpoint
@@ -228,6 +223,7 @@
String body = data.toString();
given()
+ .log().ifValidationFails()
.header("Content-Type", format.getDefaultMIMEType())
.body(body.getBytes())
.expect()
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 306cfa3..8080106 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
@@ -25,6 +25,7 @@
import org.apache.marmotta.platform.ldp.patch.parser.ParseException;
import org.openrdf.model.Statement;
import org.openrdf.model.URI;
+import org.openrdf.model.vocabulary.DCTERMS;
import org.openrdf.repository.RepositoryConnection;
import org.openrdf.repository.RepositoryException;
import org.openrdf.rio.RDFFormat;
@@ -48,7 +49,12 @@
*/
public interface LdpService {
- public static final Set<URI> SERVER_MANAGED_PROPERTIES = new HashSet<>(Arrays.asList(LDP.contains));
+ public static final Set<URI> SERVER_MANAGED_PROPERTIES = new HashSet<>(Arrays.asList(
+ LDP.contains, DCTERMS.CREATED, DCTERMS.MODIFIED
+ ));
+ public static final List<RDFFormat> SERVER_PREFERED_RDF_FORMATS = Arrays.asList(
+ RDFFormat.TURTLE, RDFFormat.JSONLD, RDFFormat.RDFXML, RDFFormat.N3, RDFFormat.NTRIPLES
+ );
public static enum InteractionModel {
LDPR(LDP.Resource),
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 80ddc25..bfdb07a 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
@@ -34,6 +34,7 @@
import org.apache.marmotta.platform.ldp.patch.parser.ParseException;
import org.apache.marmotta.platform.ldp.patch.parser.RdfPatchParserImpl;
import org.apache.marmotta.platform.ldp.util.LdpUtils;
+import org.apache.marmotta.platform.ldp.util.ServerManagedPropertiesInterceptor;
import org.apache.marmotta.platform.ldp.webservices.LdpWebService;
import org.openrdf.model.*;
import org.openrdf.model.impl.ValueFactoryImpl;
@@ -43,7 +44,6 @@
import org.openrdf.repository.RepositoryException;
import org.openrdf.repository.RepositoryResult;
import org.openrdf.repository.event.base.InterceptingRepositoryConnectionWrapper;
-import org.openrdf.repository.event.base.RepositoryConnectionInterceptorAdapter;
import org.openrdf.rio.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -58,7 +58,6 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -380,8 +379,7 @@
connection.add(resource, DCTERMS.modified, now, ldpContext);
// Add the bodyContent
- // TODO: find a better way to ingest n-triples (text/plain) while still supporting regular text files
- final RDFFormat rdfFormat = ("text/plain".equals(type) ? null : Rio.getParserFormatForMIMEType(type));
+ final RDFFormat rdfFormat = LdpUtils.matchDefaultMIMEType(type, LdpUtils.filterAvailableParsers(SERVER_PREFERED_RDF_FORMATS), null);
if (rdfFormat == null) {
log.debug("Creating new LDP-NR, because no suitable RDF parser found for type {}", type);
final Literal format = valueFactory.createLiteral(type);
@@ -411,8 +409,11 @@
log.debug("Creating new LDP-RS, data provided as {}", rdfFormat.getName());
connection.add(container, LDP.contains, resource, ldpContext);
- // FIXME: We are (are we?) allowed to filter out server-managed properties here
- connection.add(stream, resource.stringValue(), rdfFormat, resource);
+ final InterceptingRepositoryConnectionWrapper filtered = new InterceptingRepositoryConnectionWrapper(connection.getRepository(), connection);
+ final ServerManagedPropertiesInterceptor managedPropertiesInterceptor = new ServerManagedPropertiesInterceptor(ldpContext, resource);
+ filtered.addRepositoryConnectionInterceptor(managedPropertiesInterceptor);
+
+ filtered.add(stream, resource.stringValue(), rdfFormat, resource);
return resource.stringValue();
}
@@ -471,28 +472,12 @@
connection.clear(resource);
final InterceptingRepositoryConnectionWrapper filtered = new InterceptingRepositoryConnectionWrapper(connection.getRepository(), connection);
- final Set<URI> deniedProperties = new HashSet<>();
- filtered.addRepositoryConnectionInterceptor(new RepositoryConnectionInterceptorAdapter() {
- @Override
- public boolean add(RepositoryConnection conn, Resource subject, URI predicate, Value object, Resource... contexts) {
- try {
- if (connection.hasStatement(subject, predicate, object, true, ldpContext)) {
- // Ignore/Strip any triple that is already present in the mgnt-context (i.e. "unchanged" props).
- return true;
- } else if (resource.equals(subject) && SERVER_MANAGED_PROPERTIES.contains(predicate)) {
- // We do NOT allow changing server-managed properties.
- deniedProperties.add(predicate);
- return true;
- }
- } catch (RepositoryException e) {
- log.error("Error while filtering server managed properties: {}", e.getMessage());
- }
- return false;
- }
- });
+ final ServerManagedPropertiesInterceptor managedPropertiesInterceptor = new ServerManagedPropertiesInterceptor(ldpContext, resource);
+ filtered.addRepositoryConnectionInterceptor(managedPropertiesInterceptor);
filtered.add(stream, resource.stringValue(), rdfFormat, resource);
+ final Set<URI> deniedProperties = managedPropertiesInterceptor.getDeniedProperties();
if (!deniedProperties.isEmpty()) {
final URI prop = deniedProperties.iterator().next();
log.debug("Invalid property modification in update: <{}> is a server controlled property", prop);
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 326f55f..fca8187 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
@@ -18,9 +18,11 @@
package org.apache.marmotta.platform.ldp.util;
import info.aduna.iteration.CloseableIteration;
+import info.aduna.lang.FileFormat;
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.LdpService;
import org.apache.marmotta.platform.ldp.api.Preference;
import org.apache.marmotta.platform.ldp.webservices.PreferHeader;
import org.apache.tika.mime.MimeTypeException;
@@ -31,16 +33,15 @@
import org.openrdf.model.vocabulary.DCTERMS;
import org.openrdf.model.vocabulary.RDF;
import org.openrdf.repository.RepositoryException;
-import org.openrdf.rio.RDFFormat;
-import org.openrdf.rio.RDFHandlerException;
-import org.openrdf.rio.RDFParserRegistry;
-import org.openrdf.rio.RDFWriter;
+import org.openrdf.rio.*;
import org.slf4j.Logger;
import javax.ws.rs.core.MediaType;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
/**
* Various Util-Methods for the {@link org.apache.marmotta.platform.ldp.api.LdpService}.
@@ -133,7 +134,7 @@
}
public static String getAcceptPostHeader(String extraFormats) {
- final Set<RDFFormat> rdfFormats = RDFParserRegistry.getInstance().getKeys();
+ final Collection<RDFFormat> rdfFormats = filterAvailableParsers(LdpService.SERVER_PREFERED_RDF_FORMATS);
final StringBuilder sb = new StringBuilder();
for (RDFFormat rdfFormat : rdfFormats) {
sb.append(rdfFormat.getDefaultMIMEType());
@@ -147,6 +148,50 @@
return sb.toString();
}
+ /**
+ * Tries to match the specified MIME type with the MIME types of the supplied
+ * file formats.
+ * This method does exactly the same as {@link info.aduna.lang.FileFormat#matchMIMEType(String, Iterable)}
+ * but only considers the <strong>default</strong> mimeTypes.
+ *
+ * @param mimeType A MIME type, e.g. "text/plain".
+ * @param fileFormats The file formats to match the MIME type against.
+ * @return A FileFormat object if the MIME type was recognized, or {@code null} otherwise.
+ *
+ * @see #matchDefaultMIMEType(String, Iterable, info.aduna.lang.FileFormat)
+ * @see info.aduna.lang.FileFormat#matchMIMEType(String, Iterable)
+ *
+ */
+ public static <FF extends FileFormat> FF matchDefaultMIMEType(String mimeType, Iterable<FF> fileFormats) {
+ return matchDefaultMIMEType(mimeType, fileFormats, null);
+ }
+
+ /**
+ * Tries to match the specified MIME type with the default MIME types of the supplied
+ * file formats. The supplied fallback format will be returned when no
+ * matching format was found.
+ * This method does exactly the same as {@link info.aduna.lang.FileFormat#matchMIMEType(String, Iterable, info.aduna.lang.FileFormat)}
+ * but only considers the <strong>default</strong> mimeTypes.
+ *
+ * @param mimeType A MIME type, e.g. "text/plain".
+ * @param fileFormats The file formats to match the MIME type against.
+ * @param fallback The file format to return if no matching format can be found.
+ * @return A FileFormat that matches the MIME type, or the fallback format if the extension was not recognized.
+ *
+ * @see info.aduna.lang.FileFormat#matchMIMEType(String, Iterable, info.aduna.lang.FileFormat)
+ *
+ */
+ public static <FF extends FileFormat> FF matchDefaultMIMEType(String mimeType, Iterable<FF> fileFormats,
+ FF fallback) {
+ // Try to match with the default MIME type
+ for (FF fileFormat : fileFormats) {
+ if (fileFormat.hasDefaultMIMEType(mimeType)) {
+ return fileFormat;
+ }
+ }
+ return fallback;
+ }
+
public static String getContainer(String resource) throws MalformedURLException, URISyntaxException {
final int fragmentIndex = resource.indexOf('#');
if (fragmentIndex >= 0) {
@@ -190,6 +235,29 @@
return null;
}
+ public static List<RDFFormat> filterAvailableParsers(List<RDFFormat> rdfFormats) {
+ final List<RDFFormat> result = new ArrayList<>();
+ final RDFParserRegistry parserRegistry = RDFParserRegistry.getInstance();
+ for (RDFFormat f: rdfFormats) {
+ if (parserRegistry.has(f)) {
+ result.add(f);
+ }
+ }
+ return result;
+ }
+
+ public static List<RDFFormat> filterAvailableWriters(List<RDFFormat> rdfFormats) {
+ final List<RDFFormat> result = new ArrayList<>();
+ final RDFWriterRegistry writerRegistry = RDFWriterRegistry.getInstance();
+ for (RDFFormat f: rdfFormats) {
+ if (writerRegistry.has(f)) {
+ result.add(f);
+ }
+ }
+ return result;
+ }
+
+
private LdpUtils() {
// Static access only
}
diff --git a/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/ServerManagedPropertiesInterceptor.java b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/ServerManagedPropertiesInterceptor.java
new file mode 100644
index 0000000..54eeadb
--- /dev/null
+++ b/platform/marmotta-ldp/src/main/java/org/apache/marmotta/platform/ldp/util/ServerManagedPropertiesInterceptor.java
@@ -0,0 +1,95 @@
+/*
+ * 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.util;
+
+import org.apache.marmotta.platform.ldp.api.LdpService;
+import org.openrdf.model.Resource;
+import org.openrdf.model.URI;
+import org.openrdf.model.Value;
+import org.openrdf.model.impl.StatementImpl;
+import org.openrdf.repository.RepositoryConnection;
+import org.openrdf.repository.RepositoryException;
+import org.openrdf.repository.event.base.RepositoryConnectionInterceptorAdapter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * A ConnectionInterceptor that filters out all write operations that happen to
+ * <li>the <strong>managed context</strong>, or</li>
+ * <li>use a <strong>managed property</strong></li>
+ */
+public class ServerManagedPropertiesInterceptor extends RepositoryConnectionInterceptorAdapter {
+
+ private final Logger log = LoggerFactory.getLogger(this.getClass());
+
+ private final URI managedContext;
+ private final Resource subject;
+ private final Set<? extends Value> managedProperties;
+ private final Set<URI> deniedProperties;
+
+ public ServerManagedPropertiesInterceptor(URI managedContext, Resource subject) {
+ this(managedContext, subject, LdpService.SERVER_MANAGED_PROPERTIES);
+ }
+
+ public ServerManagedPropertiesInterceptor(URI managedContext, Resource subject, Set<? extends Value> managedProperties) {
+ this.managedContext = managedContext;
+ this.subject = subject;
+ this.managedProperties = managedProperties;
+ deniedProperties = new HashSet<>();
+ }
+
+ @Override
+ public boolean add(RepositoryConnection conn, Resource subject, URI predicate, Value object, Resource... contexts) {
+ return isManaged(conn, subject, predicate, object, "ADD");
+ }
+
+ @Override
+ public boolean remove(RepositoryConnection conn, Resource subject, URI predicate, Value object, Resource... contexts) {
+ return isManaged(conn, subject, predicate, object, "DEL");
+ }
+
+ private boolean isManaged(RepositoryConnection conn, Resource subject, URI predicate, Value object, String operation) {
+ try {
+ if (conn.hasStatement(subject, predicate, object, true, managedContext)) {
+ // Ignore/Strip any triple that is already present in the mgmt-context (i.e. "unchanged" props).
+ if (log.isTraceEnabled()) {
+ log.trace("[{}] filtering out statement that is already present in the managed context: {}", operation, new StatementImpl(subject, predicate, object));
+ }
+ return true;
+ } else if (this.subject.equals(subject) && managedProperties.contains(predicate)) {
+ // We do NOT allow changing server-managed properties.
+ if (log.isTraceEnabled()) {
+ log.trace("[{}] filtering out statement with managed propterty {}: {}", operation, predicate, new StatementImpl(subject, predicate, object));
+ }
+ deniedProperties.add(predicate);
+ return true;
+ }
+ } catch (RepositoryException e) {
+ log.error("Error while filtering server managed properties: {}", e.getMessage());
+ }
+ return false;
+ }
+
+ public Set<URI> getDeniedProperties() {
+ return Collections.unmodifiableSet(deniedProperties);
+ }
+
+}
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 92a799e..46b9cde 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
@@ -58,7 +58,7 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
-import java.util.UUID;
+import java.util.Locale;
/**
* Linked Data Platform web services.
@@ -107,17 +107,18 @@
public LdpWebService() {
producedRdfTypes = new ArrayList<>();
+ final List<RDFFormat> availableWriters = LdpUtils.filterAvailableWriters(LdpService.SERVER_PREFERED_RDF_FORMATS);
for(RDFFormat format : RDFWriterRegistry.getInstance().getKeys()) {
final String primaryQ;
- if (format == RDFFormat.TURTLE) {
- primaryQ = ";q=1.0";
- } else if (format == RDFFormat.JSONLD) {
- primaryQ = ";q=0.9";
- } else if (format == RDFFormat.RDFXML) {
- primaryQ = ";q=0.8";
- } else {
+ final int idx = availableWriters.indexOf(format);
+ if (idx < 0) {
+ // not a prefered format
primaryQ = ";q=0.5";
+ } else {
+ // a prefered format
+ primaryQ = String.format(Locale.ENGLISH, ";q=%.1f", Math.max(1.0-(idx*0.1), 0.55));
}
+
final String secondaryQ = ";q=0.3";
final List<String> mimeTypes = format.getMIMETypes();
for (int i = 0; i < mimeTypes.size(); i++) {