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 75968f4a9be..54e95d4624b 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
@@ -16,8 +16,12 @@
import org.eclipse.rdf4j.common.annotation.InternalUseOnly;
import org.eclipse.rdf4j.model.Model;
+import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
+import org.eclipse.rdf4j.rio.ParserConfig;
import org.eclipse.rdf4j.rio.RDFFormat;
import org.eclipse.rdf4j.rio.Rio;
+import org.eclipse.rdf4j.rio.helpers.BasicParserSettings;
+import org.eclipse.rdf4j.rio.helpers.ParseErrorLogger;
@InternalUseOnly
class RemoteValidation {
@@ -37,7 +41,9 @@ class RemoteValidation {
Model asModel() {
if (model == null) {
try {
- model = Rio.parse(stringReader, baseUri, format);
+ 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);
}
diff --git a/tools/server-spring/pom.xml b/tools/server-spring/pom.xml
index 195b0f109f7..26127a6c1dd 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-rdfjson
+ ${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 6163ad51a7b..4edb356141e 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
@@ -78,15 +78,14 @@ public ModelAndView resolveException(HttpServletRequest request, HttpServletResp
StringWriter stringWriter = new StringWriter();
- // We choose NQUADS because we want to support streaming in the future, and because there could be a use for
- // different graphs in the future
- Rio.write(validationReportModel, stringWriter, RDFFormat.NQUADS);
+ // We choose RDFJSON because this format doesn't rename blank nodes.
+ Rio.write(validationReportModel, stringWriter, RDFFormat.RDFJSON);
statusCode = HttpServletResponse.SC_CONFLICT;
errMsg = stringWriter.toString();
Map headers = new HashMap<>();
- headers.put("Content-Type", "application/shacl-validation-report+n-quads");
+ headers.put("Content-Type", "application/shacl-validation-report+rdf+json");
model.put(SimpleResponseView.CUSTOM_HEADERS_KEY, headers);
}
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 2573b6b916b..8510bf09e68 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,17 +12,26 @@
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;
@@ -30,6 +39,7 @@
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;
@@ -67,11 +77,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 .";
@BeforeEach
@@ -231,4 +242,54 @@ 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);
+
+ }
+ }
+
+ }
+
}