diff --git a/core/http/client/src/main/java/org/eclipse/rdf4j/http/client/SPARQLProtocolSession.java b/core/http/client/src/main/java/org/eclipse/rdf4j/http/client/SPARQLProtocolSession.java
index 29d621999f5..42adcc56671 100644
--- a/core/http/client/src/main/java/org/eclipse/rdf4j/http/client/SPARQLProtocolSession.java
+++ b/core/http/client/src/main/java/org/eclipse/rdf4j/http/client/SPARQLProtocolSession.java
@@ -1076,6 +1076,13 @@ protected HttpResponse execute(HttpUriRequest method) throws IOException, RDF4JE
case HttpURLConnection.HTTP_UNAVAILABLE: // 503
throw new QueryInterruptedException();
default:
+
+ if (contentTypeIs(response, "application/shacl-validation-report")
+ && getContentTypeSerialisation(response) == RDFFormat.BINARY) {
+ throw new RepositoryException(new RemoteShaclValidationException(
+ response.getEntity().getContent(), "", RDFFormat.BINARY));
+ }
+
ErrorInfo errInfo = getErrorInfo(response);
// Throw appropriate exception
if (errInfo.getErrorType() == ErrorType.MALFORMED_DATA) {
@@ -1087,10 +1094,10 @@ protected HttpResponse execute(HttpUriRequest method) throws IOException, RDF4JE
} else if (errInfo.getErrorType() == ErrorType.UNSUPPORTED_QUERY_LANGUAGE) {
throw new UnsupportedQueryLanguageException(errInfo.getErrorMessage());
} else if (contentTypeIs(response, "application/shacl-validation-report")) {
+ // Legacy support for validation exceptions prior to 4.3.3
RDFFormat format = getContentTypeSerialisation(response);
- throw new RepositoryException(new RemoteShaclValidationException(
- new StringReader(errInfo.toString()), "", format));
-
+ throw new RepositoryException(
+ new RemoteShaclValidationException(new StringReader(errInfo.toString()), "", format));
} else if (errInfo.toString().length() > 0) {
throw new RepositoryException(errInfo.toString());
} else {
diff --git a/core/http/client/src/main/java/org/eclipse/rdf4j/http/client/shacl/RemoteShaclValidationException.java b/core/http/client/src/main/java/org/eclipse/rdf4j/http/client/shacl/RemoteShaclValidationException.java
index 3687b8011bc..e5fc8a59c56 100644
--- a/core/http/client/src/main/java/org/eclipse/rdf4j/http/client/shacl/RemoteShaclValidationException.java
+++ b/core/http/client/src/main/java/org/eclipse/rdf4j/http/client/shacl/RemoteShaclValidationException.java
@@ -11,6 +11,7 @@
package org.eclipse.rdf4j.http.client.shacl;
+import java.io.InputStream;
import java.io.StringReader;
import org.eclipse.rdf4j.common.annotation.Experimental;
@@ -34,6 +35,10 @@ public RemoteShaclValidationException(StringReader stringReader, String s, RDFFo
remoteValidation = new RemoteValidation(stringReader, s, format);
}
+ public RemoteShaclValidationException(InputStream stringReader, String s, RDFFormat format) {
+ remoteValidation = new RemoteValidation(stringReader, s, format);
+ }
+
/**
* @return A Model containing the validation report as specified by the SHACL Recommendation
*/
diff --git a/core/http/client/src/main/java/org/eclipse/rdf4j/http/client/shacl/RemoteValidation.java b/core/http/client/src/main/java/org/eclipse/rdf4j/http/client/shacl/RemoteValidation.java
index 54e95d4624b..e0dbf19ae29 100644
--- a/core/http/client/src/main/java/org/eclipse/rdf4j/http/client/shacl/RemoteValidation.java
+++ b/core/http/client/src/main/java/org/eclipse/rdf4j/http/client/shacl/RemoteValidation.java
@@ -12,6 +12,7 @@
package org.eclipse.rdf4j.http.client.shacl;
import java.io.IOException;
+import java.io.InputStream;
import java.io.StringReader;
import org.eclipse.rdf4j.common.annotation.InternalUseOnly;
@@ -25,30 +26,29 @@
@InternalUseOnly
class RemoteValidation {
-
- StringReader stringReader;
- String baseUri;
- RDFFormat format;
-
Model model;
- RemoteValidation(StringReader stringReader, String baseUri, RDFFormat format) {
- this.stringReader = stringReader;
- this.baseUri = baseUri;
- this.format = format;
+ RemoteValidation(InputStream inputStream, String baseUri, RDFFormat format) {
+ try {
+ ParserConfig parserConfig = new ParserConfig().set(BasicParserSettings.PRESERVE_BNODE_IDS, true);
+ model = Rio.parse(inputStream, baseUri, format, parserConfig, SimpleValueFactory.getInstance(),
+ new ParseErrorLogger());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
- Model asModel() {
- if (model == null) {
- try {
- ParserConfig parserConfig = new ParserConfig().set(BasicParserSettings.PRESERVE_BNODE_IDS, true);
- model = Rio.parse(stringReader, baseUri, format, parserConfig, SimpleValueFactory.getInstance(),
- new ParseErrorLogger());
- } catch (IOException e) {
- throw new RuntimeException(e);
- }
+ RemoteValidation(StringReader stringReader, String baseUri, RDFFormat format) {
+ try {
+ ParserConfig parserConfig = new ParserConfig().set(BasicParserSettings.PRESERVE_BNODE_IDS, true);
+ model = Rio.parse(stringReader, baseUri, format, parserConfig, SimpleValueFactory.getInstance(),
+ new ParseErrorLogger());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
}
+ }
+ Model asModel() {
return model;
}
diff --git a/tools/server-spring/pom.xml b/tools/server-spring/pom.xml
index d2ee53151df..4892b210024 100644
--- a/tools/server-spring/pom.xml
+++ b/tools/server-spring/pom.xml
@@ -27,6 +27,11 @@
rdf4j-config
${project.version}
+
+ ${project.groupId}
+ rdf4j-rio-binary
+ ${project.version}
+
javax.servlet
javax.servlet-api
diff --git a/tools/server-spring/src/main/java/org/eclipse/rdf4j/http/server/ProtocolExceptionResolver.java b/tools/server-spring/src/main/java/org/eclipse/rdf4j/http/server/ProtocolExceptionResolver.java
index 4edb356141e..90631033308 100644
--- a/tools/server-spring/src/main/java/org/eclipse/rdf4j/http/server/ProtocolExceptionResolver.java
+++ b/tools/server-spring/src/main/java/org/eclipse/rdf4j/http/server/ProtocolExceptionResolver.java
@@ -10,7 +10,6 @@
*******************************************************************************/
package org.eclipse.rdf4j.http.server;
-import java.io.StringWriter;
import java.util.HashMap;
import java.util.Map;
@@ -19,9 +18,10 @@
import org.eclipse.rdf4j.common.exception.ValidationException;
import org.eclipse.rdf4j.common.webapp.views.SimpleResponseView;
-import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.http.server.repository.statements.ValidationExceptionView;
import org.eclipse.rdf4j.rio.RDFFormat;
-import org.eclipse.rdf4j.rio.Rio;
+import org.eclipse.rdf4j.rio.RDFWriterFactory;
+import org.eclipse.rdf4j.rio.RDFWriterRegistry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.HandlerExceptionResolver;
@@ -72,25 +72,21 @@ public ModelAndView resolveException(HttpServletRequest request, HttpServletResp
}
if (temp instanceof ValidationException) {
- // This is currently just a simple fix that causes the validation report to be printed.
- // This should not be the final solution.
- Model validationReportModel = ((ValidationException) temp).validationReportAsModel();
- StringWriter stringWriter = new StringWriter();
+ model.put(SimpleResponseView.SC_KEY, HttpServletResponse.SC_CONFLICT);
- // We choose RDFJSON because this format doesn't rename blank nodes.
- Rio.write(validationReportModel, stringWriter, RDFFormat.RDFJSON);
+ ProtocolUtil.logRequestParameters(request);
- statusCode = HttpServletResponse.SC_CONFLICT;
- errMsg = stringWriter.toString();
+ RDFWriterFactory rdfWriterFactory = RDFWriterRegistry.getInstance().get(RDFFormat.BINARY).orElseThrow();
- Map headers = new HashMap<>();
- headers.put("Content-Type", "application/shacl-validation-report+rdf+json");
- model.put(SimpleResponseView.CUSTOM_HEADERS_KEY, headers);
- }
+ model.put(ValidationExceptionView.FACTORY_KEY, rdfWriterFactory);
+ model.put(ValidationExceptionView.VALIDATION_EXCEPTION, temp);
+ return new ModelAndView(ValidationExceptionView.getInstance(), model);
- model.put(SimpleResponseView.SC_KEY, statusCode);
- model.put(SimpleResponseView.CONTENT_KEY, errMsg);
+ } else {
+ model.put(SimpleResponseView.SC_KEY, statusCode);
+ model.put(SimpleResponseView.CONTENT_KEY, errMsg);
+ }
return new ModelAndView(SimpleResponseView.getInstance(), model);
}
diff --git a/tools/server-spring/src/main/java/org/eclipse/rdf4j/http/server/repository/statements/ValidationExceptionView.java b/tools/server-spring/src/main/java/org/eclipse/rdf4j/http/server/repository/statements/ValidationExceptionView.java
new file mode 100644
index 00000000000..4dcc2473dab
--- /dev/null
+++ b/tools/server-spring/src/main/java/org/eclipse/rdf4j/http/server/repository/statements/ValidationExceptionView.java
@@ -0,0 +1,97 @@
+/*******************************************************************************
+ * Copyright (c) 2015 Eclipse RDF4J contributors, Aduna, and others.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Distribution License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ *******************************************************************************/
+package org.eclipse.rdf4j.http.server.repository.statements;
+
+import java.io.ByteArrayOutputStream;
+import java.io.OutputStream;
+import java.nio.charset.Charset;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.eclipse.rdf4j.common.exception.ValidationException;
+import org.eclipse.rdf4j.common.webapp.views.SimpleResponseView;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.Namespace;
+import org.eclipse.rdf4j.model.Statement;
+import org.eclipse.rdf4j.rio.RDFFormat;
+import org.eclipse.rdf4j.rio.RDFWriter;
+import org.eclipse.rdf4j.rio.RDFWriterFactory;
+import org.springframework.web.servlet.View;
+
+/**
+ * View used to export a ValidationException.
+ *
+ * @author HÃ¥vard Ottestad
+ */
+public class ValidationExceptionView implements View {
+
+ public static final String FACTORY_KEY = "factory";
+
+ public static final String VALIDATION_EXCEPTION = "validationException";
+
+ private static final ValidationExceptionView INSTANCE = new ValidationExceptionView();
+
+ public static ValidationExceptionView getInstance() {
+ return INSTANCE;
+ }
+
+ private ValidationExceptionView() {
+ }
+
+ @Override
+ public String getContentType() {
+ return null;
+ }
+
+ @SuppressWarnings("rawtypes")
+ @Override
+ public void render(Map model, HttpServletRequest request, HttpServletResponse response) throws Exception {
+
+ RDFWriterFactory rdfWriterFactory = (RDFWriterFactory) model.get(FACTORY_KEY);
+
+ RDFFormat rdfFormat = rdfWriterFactory.getRDFFormat();
+
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
+ RDFWriter rdfWriter = rdfWriterFactory.getWriter(baos);
+
+ ValidationException validationException = (ValidationException) model.get(VALIDATION_EXCEPTION);
+
+ Model validationReportModel = validationException.validationReportAsModel();
+
+ rdfWriter.startRDF();
+ for (Namespace namespace : validationReportModel.getNamespaces()) {
+ rdfWriter.handleNamespace(namespace.getPrefix(), namespace.getName());
+ }
+ for (Statement statement : validationReportModel) {
+ rdfWriter.handleStatement(statement);
+ }
+ rdfWriter.endRDF();
+
+ try (OutputStream out = response.getOutputStream()) {
+ response.setStatus((int) model.get(SimpleResponseView.SC_KEY));
+
+ String mimeType = rdfFormat.getDefaultMIMEType();
+ if (rdfFormat.hasCharset()) {
+ Charset charset = rdfFormat.getCharset();
+ mimeType += "; charset=" + charset.name();
+ }
+
+ assert mimeType.startsWith("application/");
+ response.setContentType("application/shacl-validation-report+" + mimeType.replace("application/", ""));
+
+ out.write(baos.toByteArray());
+ }
+ }
+ }
+
+}
diff --git a/tools/server/src/test/java/org/eclipse/rdf4j/http/server/ShaclValidationReportIT.java b/tools/server/src/test/java/org/eclipse/rdf4j/http/server/ShaclValidationReportIT.java
index a0427ccf1d6..a9ca28eecf5 100644
--- a/tools/server/src/test/java/org/eclipse/rdf4j/http/server/ShaclValidationReportIT.java
+++ b/tools/server/src/test/java/org/eclipse/rdf4j/http/server/ShaclValidationReportIT.java
@@ -15,20 +15,30 @@
import java.io.IOException;
import java.io.StringReader;
+import java.util.List;
+import java.util.stream.Collectors;
import org.eclipse.rdf4j.common.exception.ValidationException;
+import org.eclipse.rdf4j.http.client.shacl.RemoteShaclValidationException;
import org.eclipse.rdf4j.http.protocol.Protocol;
+import org.eclipse.rdf4j.model.BNode;
+import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.Statement;
+import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
+import org.eclipse.rdf4j.model.util.Values;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.RDF4J;
import org.eclipse.rdf4j.model.vocabulary.RDFS;
import org.eclipse.rdf4j.model.vocabulary.SHACL;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
+import org.eclipse.rdf4j.repository.RepositoryException;
import org.eclipse.rdf4j.repository.http.HTTPRepository;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -65,11 +75,12 @@ public static void stopServer() throws Exception {
"ex:PersonShape\n" +
"\ta sh:NodeShape ;\n" +
"\tsh:targetClass rdfs:Resource ;\n" +
- "\tsh:property ex:PersonShapeProperty .\n" +
+ "\tsh:property _:bnode .\n" +
"\n" +
"\n" +
- "ex:PersonShapeProperty\n" +
+ "_:bnode\n" +
" sh:path rdfs:label ;\n" +
+ " rdfs:label \"abc\" ;\n" +
" sh:minCount 1 .";
@Test
@@ -128,4 +139,54 @@ public void testAddingData() throws IOException {
}
+ @Test
+ public void testBlankNodeIdsPreserved() throws IOException {
+
+ Repository repository = new HTTPRepository(
+ Protocol.getRepositoryLocation(TestServer.SERVER_URL, TestServer.TEST_SHACL_REPO_ID));
+
+ try (RepositoryConnection connection = repository.getConnection()) {
+ connection.begin();
+ connection.add(new StringReader(shacl), "", RDFFormat.TURTLE, RDF4J.SHACL_SHAPE_GRAPH);
+ connection.commit();
+ }
+
+ try (RepositoryConnection connection = repository.getConnection()) {
+ connection.begin();
+ connection.add(RDFS.RESOURCE, RDF.TYPE, RDFS.RESOURCE);
+ connection.commit();
+ } catch (RepositoryException repositoryException) {
+
+ Model validationReport = ((RemoteShaclValidationException) repositoryException.getCause())
+ .validationReportAsModel();
+
+ BNode shapeBnode = (BNode) validationReport
+ .filter(null, SHACL.SOURCE_SHAPE, null)
+ .objects()
+ .stream()
+ .findAny()
+ .orElseThrow();
+
+ try (RepositoryConnection connection = repository.getConnection()) {
+ List collect = connection
+ .getStatements(shapeBnode, null, null, RDF4J.SHACL_SHAPE_GRAPH)
+ .stream()
+ .collect(Collectors.toList());
+
+ Assertions.assertEquals(3, collect.size());
+
+ Value rdfsLabel = collect
+ .stream()
+ .filter(s -> s.getPredicate().equals(RDFS.LABEL))
+ .map(Statement::getObject)
+ .findAny()
+ .orElseThrow();
+
+ Assertions.assertEquals(Values.literal("abc"), rdfsLabel);
+
+ }
+ }
+
+ }
+
}
diff --git a/tools/server/src/test/java/org/eclipse/rdf4j/http/server/TransactionSettingsIT.java b/tools/server/src/test/java/org/eclipse/rdf4j/http/server/TransactionSettingsIT.java
index 8510bf09e68..de1ab4fafc8 100644
--- a/tools/server/src/test/java/org/eclipse/rdf4j/http/server/TransactionSettingsIT.java
+++ b/tools/server/src/test/java/org/eclipse/rdf4j/http/server/TransactionSettingsIT.java
@@ -12,26 +12,17 @@
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import java.io.IOException;
import java.io.StringReader;
-import java.util.List;
-import java.util.stream.Collectors;
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
import org.eclipse.rdf4j.http.client.shacl.RemoteShaclValidationException;
import org.eclipse.rdf4j.http.protocol.Protocol;
-import org.eclipse.rdf4j.model.BNode;
-import org.eclipse.rdf4j.model.Model;
import org.eclipse.rdf4j.model.Resource;
-import org.eclipse.rdf4j.model.Statement;
-import org.eclipse.rdf4j.model.Value;
import org.eclipse.rdf4j.model.ValueFactory;
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
-import org.eclipse.rdf4j.model.util.Values;
import org.eclipse.rdf4j.model.vocabulary.RDF;
import org.eclipse.rdf4j.model.vocabulary.RDF4J;
import org.eclipse.rdf4j.model.vocabulary.RDFS;
-import org.eclipse.rdf4j.model.vocabulary.SHACL;
import org.eclipse.rdf4j.repository.Repository;
import org.eclipse.rdf4j.repository.RepositoryConnection;
import org.eclipse.rdf4j.repository.RepositoryException;
@@ -39,7 +30,6 @@
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.sail.shacl.ShaclSail;
import org.junit.jupiter.api.AfterAll;
-import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -242,54 +232,4 @@ public void testValidationDisabledSnapshotSerializableValidation() throws Throwa
}
- @Test
- public void testBlankNodeIdsPreserved() throws IOException {
-
- Repository repository = new HTTPRepository(
- Protocol.getRepositoryLocation(TestServer.SERVER_URL, TestServer.TEST_SHACL_REPO_ID));
-
- try (RepositoryConnection connection = repository.getConnection()) {
- connection.begin();
- connection.add(new StringReader(shacl), "", RDFFormat.TURTLE, RDF4J.SHACL_SHAPE_GRAPH);
- connection.commit();
- }
-
- try (RepositoryConnection connection = repository.getConnection()) {
- connection.begin();
- connection.add(RDFS.RESOURCE, RDF.TYPE, RDFS.RESOURCE);
- connection.commit();
- } catch (RepositoryException repositoryException) {
-
- Model validationReport = ((RemoteShaclValidationException) repositoryException.getCause())
- .validationReportAsModel();
-
- BNode shapeBnode = (BNode) validationReport
- .filter(null, SHACL.SOURCE_SHAPE, null)
- .objects()
- .stream()
- .findAny()
- .orElseThrow();
-
- try (RepositoryConnection connection = repository.getConnection()) {
- List collect = connection
- .getStatements(shapeBnode, null, null, RDF4J.SHACL_SHAPE_GRAPH)
- .stream()
- .collect(Collectors.toList());
-
- Assertions.assertEquals(3, collect.size());
-
- Value rdfsLabel = collect
- .stream()
- .filter(s -> s.getPredicate().equals(RDFS.LABEL))
- .map(Statement::getObject)
- .findAny()
- .orElseThrow();
-
- Assertions.assertEquals(Values.literal("abc"), rdfsLabel);
-
- }
- }
-
- }
-
}