Skip to content

Commit 13d9331

Browse files
committed
GH-4622 use RDF JSON to preserve bnode identifiers in the remote shacl valdiation report
1 parent c0ab946 commit 13d9331

File tree

5 files changed

+141
-9
lines changed

5 files changed

+141
-9
lines changed

core/http/client/src/main/java/org/eclipse/rdf4j/http/client/shacl/RemoteValidation.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,12 @@
1616

1717
import org.eclipse.rdf4j.common.annotation.InternalUseOnly;
1818
import org.eclipse.rdf4j.model.Model;
19+
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
20+
import org.eclipse.rdf4j.rio.ParserConfig;
1921
import org.eclipse.rdf4j.rio.RDFFormat;
2022
import org.eclipse.rdf4j.rio.Rio;
23+
import org.eclipse.rdf4j.rio.helpers.BasicParserSettings;
24+
import org.eclipse.rdf4j.rio.helpers.ParseErrorLogger;
2125

2226
@InternalUseOnly
2327
class RemoteValidation {
@@ -37,7 +41,9 @@ class RemoteValidation {
3741
Model asModel() {
3842
if (model == null) {
3943
try {
40-
model = Rio.parse(stringReader, baseUri, format);
44+
ParserConfig parserConfig = new ParserConfig().set(BasicParserSettings.PRESERVE_BNODE_IDS, true);
45+
model = Rio.parse(stringReader, baseUri, format, parserConfig, SimpleValueFactory.getInstance(),
46+
new ParseErrorLogger());
4147
} catch (IOException e) {
4248
throw new RuntimeException(e);
4349
}

tools/server-spring/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@
2727
<artifactId>rdf4j-config</artifactId>
2828
<version>${project.version}</version>
2929
</dependency>
30+
<dependency>
31+
<groupId>${project.groupId}</groupId>
32+
<artifactId>rdf4j-rio-rdfjson</artifactId>
33+
<version>${project.version}</version>
34+
</dependency>
3035
<dependency>
3136
<groupId>javax.servlet</groupId>
3237
<artifactId>javax.servlet-api</artifactId>

tools/server-spring/src/main/java/org/eclipse/rdf4j/http/server/ProtocolExceptionResolver.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,14 @@ public ModelAndView resolveException(HttpServletRequest request, HttpServletResp
7878

7979
StringWriter stringWriter = new StringWriter();
8080

81-
// We choose NQUADS because we want to support streaming in the future, and because there could be a use for
82-
// different graphs in the future
83-
Rio.write(validationReportModel, stringWriter, RDFFormat.NQUADS);
81+
// We choose RDFJSON because this format doesn't rename blank nodes.
82+
Rio.write(validationReportModel, stringWriter, RDFFormat.RDFJSON);
8483

8584
statusCode = HttpServletResponse.SC_CONFLICT;
8685
errMsg = stringWriter.toString();
8786

8887
Map<String, String> headers = new HashMap<>();
89-
headers.put("Content-Type", "application/shacl-validation-report+n-quads");
88+
headers.put("Content-Type", "application/shacl-validation-report+rdf+json");
9089
model.put(SimpleResponseView.CUSTOM_HEADERS_KEY, headers);
9190
}
9291

tools/server/src/test/java/org/eclipse/rdf4j/http/server/ShaclValidationReportIT.java

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,30 @@
1515

1616
import java.io.IOException;
1717
import java.io.StringReader;
18+
import java.util.List;
19+
import java.util.stream.Collectors;
1820

1921
import org.eclipse.rdf4j.common.exception.ValidationException;
22+
import org.eclipse.rdf4j.http.client.shacl.RemoteShaclValidationException;
2023
import org.eclipse.rdf4j.http.protocol.Protocol;
24+
import org.eclipse.rdf4j.model.BNode;
25+
import org.eclipse.rdf4j.model.Model;
26+
import org.eclipse.rdf4j.model.Statement;
27+
import org.eclipse.rdf4j.model.Value;
2128
import org.eclipse.rdf4j.model.ValueFactory;
2229
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
30+
import org.eclipse.rdf4j.model.util.Values;
2331
import org.eclipse.rdf4j.model.vocabulary.RDF;
2432
import org.eclipse.rdf4j.model.vocabulary.RDF4J;
2533
import org.eclipse.rdf4j.model.vocabulary.RDFS;
2634
import org.eclipse.rdf4j.model.vocabulary.SHACL;
2735
import org.eclipse.rdf4j.repository.Repository;
2836
import org.eclipse.rdf4j.repository.RepositoryConnection;
37+
import org.eclipse.rdf4j.repository.RepositoryException;
2938
import org.eclipse.rdf4j.repository.http.HTTPRepository;
3039
import org.eclipse.rdf4j.rio.RDFFormat;
3140
import org.junit.jupiter.api.AfterAll;
41+
import org.junit.jupiter.api.Assertions;
3242
import org.junit.jupiter.api.BeforeAll;
3343
import org.junit.jupiter.api.Test;
3444

@@ -65,11 +75,12 @@ public static void stopServer() throws Exception {
6575
"ex:PersonShape\n" +
6676
"\ta sh:NodeShape ;\n" +
6777
"\tsh:targetClass rdfs:Resource ;\n" +
68-
"\tsh:property ex:PersonShapeProperty .\n" +
78+
"\tsh:property _:bnode .\n" +
6979
"\n" +
7080
"\n" +
71-
"ex:PersonShapeProperty\n" +
81+
"_:bnode\n" +
7282
" sh:path rdfs:label ;\n" +
83+
" rdfs:label \"abc\" ;\n" +
7384
" sh:minCount 1 .";
7485

7586
@Test
@@ -128,4 +139,54 @@ public void testAddingData() throws IOException {
128139

129140
}
130141

142+
@Test
143+
public void testBlankNodeIdsPreserved() throws IOException {
144+
145+
Repository repository = new HTTPRepository(
146+
Protocol.getRepositoryLocation(TestServer.SERVER_URL, TestServer.TEST_SHACL_REPO_ID));
147+
148+
try (RepositoryConnection connection = repository.getConnection()) {
149+
connection.begin();
150+
connection.add(new StringReader(shacl), "", RDFFormat.TURTLE, RDF4J.SHACL_SHAPE_GRAPH);
151+
connection.commit();
152+
}
153+
154+
try (RepositoryConnection connection = repository.getConnection()) {
155+
connection.begin();
156+
connection.add(RDFS.RESOURCE, RDF.TYPE, RDFS.RESOURCE);
157+
connection.commit();
158+
} catch (RepositoryException repositoryException) {
159+
160+
Model validationReport = ((RemoteShaclValidationException) repositoryException.getCause())
161+
.validationReportAsModel();
162+
163+
BNode shapeBnode = (BNode) validationReport
164+
.filter(null, SHACL.SOURCE_SHAPE, null)
165+
.objects()
166+
.stream()
167+
.findAny()
168+
.orElseThrow();
169+
170+
try (RepositoryConnection connection = repository.getConnection()) {
171+
List<Statement> collect = connection
172+
.getStatements(shapeBnode, null, null, RDF4J.SHACL_SHAPE_GRAPH)
173+
.stream()
174+
.collect(Collectors.toList());
175+
176+
Assertions.assertEquals(3, collect.size());
177+
178+
Value rdfsLabel = collect
179+
.stream()
180+
.filter(s -> s.getPredicate().equals(RDFS.LABEL))
181+
.map(Statement::getObject)
182+
.findAny()
183+
.orElseThrow();
184+
185+
Assertions.assertEquals(Values.literal("abc"), rdfsLabel);
186+
187+
}
188+
}
189+
190+
}
191+
131192
}

tools/server/src/test/java/org/eclipse/rdf4j/http/server/TransactionSettingsIT.java

Lines changed: 63 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,34 @@
1212

1313
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
1414

15+
import java.io.IOException;
1516
import java.io.StringReader;
17+
import java.util.List;
18+
import java.util.stream.Collectors;
1619

1720
import org.eclipse.rdf4j.common.transaction.IsolationLevels;
1821
import org.eclipse.rdf4j.http.client.shacl.RemoteShaclValidationException;
1922
import org.eclipse.rdf4j.http.protocol.Protocol;
23+
import org.eclipse.rdf4j.model.BNode;
24+
import org.eclipse.rdf4j.model.Model;
2025
import org.eclipse.rdf4j.model.Resource;
26+
import org.eclipse.rdf4j.model.Statement;
27+
import org.eclipse.rdf4j.model.Value;
2128
import org.eclipse.rdf4j.model.ValueFactory;
2229
import org.eclipse.rdf4j.model.impl.SimpleValueFactory;
30+
import org.eclipse.rdf4j.model.util.Values;
2331
import org.eclipse.rdf4j.model.vocabulary.RDF;
2432
import org.eclipse.rdf4j.model.vocabulary.RDF4J;
2533
import org.eclipse.rdf4j.model.vocabulary.RDFS;
34+
import org.eclipse.rdf4j.model.vocabulary.SHACL;
2635
import org.eclipse.rdf4j.repository.Repository;
2736
import org.eclipse.rdf4j.repository.RepositoryConnection;
2837
import org.eclipse.rdf4j.repository.RepositoryException;
2938
import org.eclipse.rdf4j.repository.http.HTTPRepository;
3039
import org.eclipse.rdf4j.rio.RDFFormat;
3140
import org.eclipse.rdf4j.sail.shacl.ShaclSail;
3241
import org.junit.jupiter.api.AfterAll;
42+
import org.junit.jupiter.api.Assertions;
3343
import org.junit.jupiter.api.BeforeAll;
3444
import org.junit.jupiter.api.BeforeEach;
3545
import org.junit.jupiter.api.Test;
@@ -67,11 +77,12 @@ public static void stopServer() throws Exception {
6777
"ex:PersonShape\n" +
6878
"\ta sh:NodeShape ;\n" +
6979
"\tsh:targetClass rdfs:Resource ;\n" +
70-
"\tsh:property ex:PersonShapeProperty .\n" +
80+
"\tsh:property _:bnode .\n" +
7181
"\n" +
7282
"\n" +
73-
"ex:PersonShapeProperty\n" +
83+
"_:bnode\n" +
7484
" sh:path rdfs:label ;\n" +
85+
" rdfs:label \"abc\" ;\n" +
7586
" sh:minCount 1 .";
7687

7788
@BeforeEach
@@ -231,4 +242,54 @@ public void testValidationDisabledSnapshotSerializableValidation() throws Throwa
231242

232243
}
233244

245+
@Test
246+
public void testBlankNodeIdsPreserved() throws IOException {
247+
248+
Repository repository = new HTTPRepository(
249+
Protocol.getRepositoryLocation(TestServer.SERVER_URL, TestServer.TEST_SHACL_REPO_ID));
250+
251+
try (RepositoryConnection connection = repository.getConnection()) {
252+
connection.begin();
253+
connection.add(new StringReader(shacl), "", RDFFormat.TURTLE, RDF4J.SHACL_SHAPE_GRAPH);
254+
connection.commit();
255+
}
256+
257+
try (RepositoryConnection connection = repository.getConnection()) {
258+
connection.begin();
259+
connection.add(RDFS.RESOURCE, RDF.TYPE, RDFS.RESOURCE);
260+
connection.commit();
261+
} catch (RepositoryException repositoryException) {
262+
263+
Model validationReport = ((RemoteShaclValidationException) repositoryException.getCause())
264+
.validationReportAsModel();
265+
266+
BNode shapeBnode = (BNode) validationReport
267+
.filter(null, SHACL.SOURCE_SHAPE, null)
268+
.objects()
269+
.stream()
270+
.findAny()
271+
.orElseThrow();
272+
273+
try (RepositoryConnection connection = repository.getConnection()) {
274+
List<Statement> collect = connection
275+
.getStatements(shapeBnode, null, null, RDF4J.SHACL_SHAPE_GRAPH)
276+
.stream()
277+
.collect(Collectors.toList());
278+
279+
Assertions.assertEquals(3, collect.size());
280+
281+
Value rdfsLabel = collect
282+
.stream()
283+
.filter(s -> s.getPredicate().equals(RDFS.LABEL))
284+
.map(Statement::getObject)
285+
.findAny()
286+
.orElseThrow();
287+
288+
Assertions.assertEquals(Values.literal("abc"), rdfsLabel);
289+
290+
}
291+
}
292+
293+
}
294+
234295
}

0 commit comments

Comments
 (0)