Skip to content

Commit

Permalink
GH-4622 SHACL preserve bnode (simple solution) (#4794)
Browse files Browse the repository at this point in the history
  • Loading branch information
hmottestad authored Oct 3, 2023
2 parents c0ab946 + 13d9331 commit 454436f
Show file tree
Hide file tree
Showing 5 changed files with 141 additions and 9 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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);
}
Expand Down
5 changes: 5 additions & 0 deletions tools/server-spring/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
<artifactId>rdf4j-config</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>rdf4j-rio-rdfjson</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<String, String> 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);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<Statement> 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);

}
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -12,24 +12,34 @@

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;
import org.eclipse.rdf4j.repository.http.HTTPRepository;
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;
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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<Statement> 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);

}
}

}

}

0 comments on commit 454436f

Please sign in to comment.