diff --git a/compliance/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/SHACLComplianceTest.java b/compliance/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/SHACLComplianceTest.java index d3bd646f2..bb9971680 100644 --- a/compliance/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/SHACLComplianceTest.java +++ b/compliance/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/SHACLComplianceTest.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2018 Eclipse RDF4J contributors, Aduna, and others. + * Copyright (c) 2018 Eclipse RDF4J contributors. * 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 @@ -8,11 +8,6 @@ package org.eclipse.rdf4j.sail.shacl; import org.eclipse.rdf4j.model.Model; -import org.eclipse.rdf4j.model.Resource; -import org.eclipse.rdf4j.model.impl.LinkedHashModel; -import org.eclipse.rdf4j.model.vocabulary.RDF; -import org.eclipse.rdf4j.model.vocabulary.SHACL; -import org.eclipse.rdf4j.repository.sail.SailRepository; import org.eclipse.rdf4j.sail.NotifyingSail; import org.eclipse.rdf4j.sail.Sail; import org.eclipse.rdf4j.sail.memory.MemoryStore; @@ -62,39 +57,9 @@ protected NotifyingSail newDataSail() { return new MemoryStore(); } - protected SailRepository createShapesRepository() { - SailRepository repo = new SailRepository(new MemoryStore()); - repo.initialize(); - return repo; - } - @Override - protected Sail newSail(Model shapesGraph) { - SailRepository shapesRep = createShapesRepository(); - if (shapesGraph != null) { - try { - upload(shapesRep, shapesGraph); - } - catch (Exception exc) { - try { - shapesRep.shutDown(); - shapesRep = null; - } - catch (Exception e2) { - logger.error(e2.toString(), e2); - } - throw exc; - } - } - Model infer = new LinkedHashModel(); - for (Resource subj : shapesGraph.filter(null, RDF.TYPE, SHACL.NODE_SHAPE).subjects()) { - infer.add(subj, RDF.TYPE, SHACL.SHAPE); - } - for (Resource subj : shapesGraph.filter(null, RDF.TYPE, SHACL.PROPERTY_SHAPE).subjects()) { - infer.add(subj, RDF.TYPE, SHACL.SHAPE); - } - upload(shapesRep, infer); - return new ShaclSail(newDataSail(), shapesRep); + protected Sail newSail() { + return new ShaclSail(newDataSail()); } - + } diff --git a/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/NoShapesLoadedException.java b/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/NoShapesLoadedException.java index eaf48baa9..33f99ca24 100644 --- a/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/NoShapesLoadedException.java +++ b/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/NoShapesLoadedException.java @@ -1,8 +1,13 @@ package org.eclipse.rdf4j.sail.shacl; +import org.eclipse.rdf4j.model.vocabulary.RDF4J; + public class NoShapesLoadedException extends RuntimeException { + private static final long serialVersionUID = 1L; + public NoShapesLoadedException() { - super("Load shapes by adding them to named graph <"+ShaclSail.SHAPE_GRAPH+"> in the first transaction after initialization!"); + super("Load shapes by adding them to named graph <" + RDF4J.SHACL_SHAPE_GRAPH + + "> in the first transaction after initialization!"); } } diff --git a/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSail.java b/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSail.java index 25d19c757..3c946d316 100644 --- a/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSail.java +++ b/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSail.java @@ -11,6 +11,7 @@ import org.apache.commons.io.IOUtils; import org.eclipse.rdf4j.model.IRI; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.vocabulary.RDF4J; import org.eclipse.rdf4j.repository.Repository; import org.eclipse.rdf4j.repository.sail.SailRepository; import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; @@ -29,7 +30,9 @@ import java.util.List; /** - * A {@link Sail} implementation that adds support for the Shapes Constraint Language (SHACL) + * A {@link Sail} implementation that adds support for the Shapes Constraint Language (SHACL). + *

+ * The ShaclSail looks for SHACL shape data in a special named graph {@link RDF4J#SHACL_SHAPE_GRAPH}. * * @author Heshan Jayasinghe * @author HÃ¥vard Ottestad @@ -37,17 +40,10 @@ */ public class ShaclSail extends NotifyingSailWrapper { - /** - * The virtual context identifier for persisting the SHACL shapes information. - */ - @SuppressWarnings("WeakerAccess") - public final static IRI SHAPE_GRAPH = SimpleValueFactory - .getInstance() - .createIRI("http://rdf4j.org/schema/schacl#ShapeGraph"); - private List nodeShapes; boolean debugPrintPlans = false; + private boolean ignoreNoShapesLoadedException = false; ShaclSailConfig config = new ShaclSailConfig(); @@ -64,18 +60,14 @@ public class ShaclSail extends NotifyingSailWrapper { static { try { SH_OR_UPDATE_QUERY = IOUtils.toString( - ShaclSail - .class - .getClassLoader() - .getResourceAsStream("shacl-sparql-inference/sh_or.rq"), - "UTF-8"); + ShaclSail.class.getClassLoader().getResourceAsStream("shacl-sparql-inference/sh_or.rq"), + "UTF-8"); SH_OR_NODE_SHAPE_UPDATE_QUERY = IOUtils.toString( - ShaclSail - .class - .getClassLoader() - .getResourceAsStream("shacl-sparql-inference/sh_or_node_shape.rq"), - "UTF-8"); - } catch (IOException e) { + ShaclSail.class.getClassLoader().getResourceAsStream( + "shacl-sparql-inference/sh_or_node_shape.rq"), + "UTF-8"); + } + catch (IOException e) { throw new IllegalStateException(e); } @@ -86,10 +78,12 @@ public ShaclSail(NotifyingSail baseSail) { String path = null; if (baseSail.getDataDir() != null) { path = baseSail.getDataDir().getPath(); - } else { + } + else { try { path = Files.createTempDirectory("shacl-shapes").toString(); - } catch (IOException e) { + } + catch (IOException e) { throw new SailConfigException(e); } } @@ -118,7 +112,8 @@ void refreshShapes(SailRepositoryConnection shapesRepoConnection) throws SailExc // Our inferencer both adds and removes statements. // To support updates I recommend having two graphs, one raw one with the unmodified data. // Then copy all that data into a new graph, run inferencing on that graph and use it to generate the java objects - throw new IllegalStateException("ShaclSail does not support modifying shapes that are already loaded or loading more shapes"); + throw new IllegalStateException( + "ShaclSail does not support modifying shapes that are already loaded or loading more shapes"); } } @@ -130,7 +125,8 @@ void refreshShapes(SailRepositoryConnection shapesRepoConnection) throws SailExc public void shutDown() throws SailException { try { shapesRepo.shutDown(); - } finally { + } + finally { shapesRepo = null; } super.shutDown(); @@ -138,7 +134,8 @@ public void shutDown() throws SailException { @Override public NotifyingSailConnection getConnection() throws SailException { - return new ShaclSailConnection(this, super.getConnection(), super.getConnection(), shapesRepo.getConnection()); + return new ShaclSailConnection(this, super.getConnection(), super.getConnection(), + shapesRepo.getConnection()); } public void disableValidation() { diff --git a/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSailConnection.java b/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSailConnection.java index ace3b7021..3ce2f4422 100644 --- a/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSailConnection.java +++ b/shacl/src/main/java/org/eclipse/rdf4j/sail/shacl/ShaclSailConnection.java @@ -8,6 +8,13 @@ package org.eclipse.rdf4j.sail.shacl; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + import org.eclipse.rdf4j.IsolationLevel; import org.eclipse.rdf4j.IsolationLevels; import org.eclipse.rdf4j.common.iteration.Iterations; @@ -15,6 +22,7 @@ import org.eclipse.rdf4j.model.Resource; import org.eclipse.rdf4j.model.Statement; import org.eclipse.rdf4j.model.Value; +import org.eclipse.rdf4j.model.vocabulary.RDF4J; import org.eclipse.rdf4j.repository.Repository; import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.repository.sail.SailRepository; @@ -22,6 +30,7 @@ import org.eclipse.rdf4j.sail.NotifyingSailConnection; import org.eclipse.rdf4j.sail.SailConnectionListener; import org.eclipse.rdf4j.sail.SailException; +import org.eclipse.rdf4j.sail.UpdateContext; import org.eclipse.rdf4j.sail.helpers.NotifyingSailConnectionWrapper; import org.eclipse.rdf4j.sail.memory.MemoryStore; import org.eclipse.rdf4j.sail.shacl.AST.NodeShape; @@ -30,14 +39,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.stream.Collectors; -import java.util.stream.Stream; - /** * @author Heshan Jayasinghe */ @@ -64,7 +65,8 @@ public class ShaclSailConnection extends NotifyingSailConnectionWrapper { private SailRepositoryConnection shapesConnection; ShaclSailConnection(ShaclSail sail, NotifyingSailConnection connection, - NotifyingSailConnection previousStateConnection, SailRepositoryConnection shapesConnection) { + NotifyingSailConnection previousStateConnection, SailRepositoryConnection shapesConnection) + { super(connection); this.previousStateConnection = previousStateConnection; this.shapesConnection = shapesConnection; @@ -74,23 +76,23 @@ public class ShaclSailConnection extends NotifyingSailConnectionWrapper { addConnectionListener(new SailConnectionListener() { - @Override - public void statementAdded(Statement statement) { - boolean add = addedStatementsSet.add(statement); - if (!add) { - removedStatementsSet.remove(statement); - } + @Override + public void statementAdded(Statement statement) { + boolean add = addedStatementsSet.add(statement); + if (!add) { + removedStatementsSet.remove(statement); + } - } + } - @Override - public void statementRemoved(Statement statement) { - boolean add = removedStatementsSet.add(statement); - if (!add) { - addedStatementsSet.remove(statement); - } - } - } + @Override + public void statementRemoved(Statement statement) { + boolean add = removedStatementsSet.add(statement); + if (!add) { + addedStatementsSet.remove(statement); + } + } + } ); } @@ -144,7 +146,10 @@ public void commit() throws SailException { refreshShapes(shapesConnection); - if (!sail.isIgnoreNoShapesLoadedException() && ((!addedStatementsSet.isEmpty() || !removedStatementsSet.isEmpty()) && sail.getNodeShapes().isEmpty())) { + if (!sail.isIgnoreNoShapesLoadedException() + && ((!addedStatementsSet.isEmpty() || !removedStatementsSet.isEmpty()) + && sail.getNodeShapes().isEmpty())) + { throw new NoShapesLoadedException(); } @@ -157,33 +162,65 @@ public void commit() throws SailException { rollback(); refreshShapes(shapesConnection); throw new ShaclSailValidationException(invalidTuples); - } else { + } + else { shapesConnection.commit(); super.commit(); } - } finally { + } + finally { cleanup(); } } } + @Override + public void addStatement(UpdateContext modify, Resource subj, IRI pred, Value obj, Resource... contexts) + throws SailException + { + if (contexts.length == 1 && contexts[0].equals(RDF4J.SHACL_SHAPE_GRAPH)) { + shapesConnection.add(subj, pred, obj); + isShapeRefreshNeeded = true; + } + else { + super.addStatement(modify, subj, pred, obj, contexts); + } + } + + @Override + public void removeStatement(UpdateContext modify, Resource subj, IRI pred, Value obj, + Resource... contexts) + throws SailException + { + if (contexts.length == 1 && contexts[0].equals(RDF4J.SHACL_SHAPE_GRAPH)) { + shapesConnection.remove(subj, pred, obj); + isShapeRefreshNeeded = true; + } + else { + super.removeStatement(modify, subj, pred, obj, contexts); + } + } + @Override public void addStatement(Resource subj, IRI pred, Value obj, Resource... contexts) throws SailException { - if (contexts.length == 1 && contexts[0].equals(ShaclSail.SHAPE_GRAPH)) { + if (contexts.length == 1 && contexts[0].equals(RDF4J.SHACL_SHAPE_GRAPH)) { shapesConnection.add(subj, pred, obj); isShapeRefreshNeeded = true; - } else { + } + else { super.addStatement(subj, pred, obj, contexts); } } @Override public void removeStatements(Resource subj, IRI pred, Value obj, Resource... contexts) - throws SailException { - if (contexts.length == 1 && contexts[0].equals(ShaclSail.SHAPE_GRAPH)) { + throws SailException + { + if (contexts.length == 1 && contexts[0].equals(RDF4J.SHACL_SHAPE_GRAPH)) { shapesConnection.remove(subj, pred, obj); isShapeRefreshNeeded = true; - } else { + } + else { super.removeStatements(subj, pred, obj, contexts); } } @@ -243,12 +280,12 @@ private List validate() { boolean valid = collect.size() == 0; if (!valid) { logger.warn( - "SHACL not valid. The following experimental debug results were produced: \n\tNodeShape: {} \n\t\t{}", - nodeShape.toString(), - String.join("\n\t\t", - collect.stream().map( - a -> a.toString() + " -cause-> " + a.getCause()).collect( - Collectors.toList()))); + "SHACL not valid. The following experimental debug results were produced: \n\tNodeShape: {} \n\t\t{}", + nodeShape.toString(), + String.join("\n\t\t", + collect.stream().map( + a -> a.toString() + " -cause-> " + a.getCause()).collect( + Collectors.toList()))); } } } @@ -268,14 +305,14 @@ void fillAddedAndRemovedStatementRepositories() { try (RepositoryConnection connection = addedStatements.getConnection()) { connection.begin(IsolationLevels.NONE); addedStatementsSet.stream().filter( - statement -> !removedStatementsSet.contains(statement)).forEach(connection::add); + statement -> !removedStatementsSet.contains(statement)).forEach(connection::add); connection.commit(); } try (RepositoryConnection connection = removedStatements.getConnection()) { connection.begin(IsolationLevels.NONE); removedStatementsSet.stream().filter( - statement -> !addedStatementsSet.contains(statement)).forEach(connection::add); + statement -> !addedStatementsSet.contains(statement)).forEach(connection::add); connection.commit(); } } diff --git a/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/RuntimeModifyShapesTest.java b/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/RuntimeModifyShapesTest.java index a973ae279..debb7cdad 100644 --- a/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/RuntimeModifyShapesTest.java +++ b/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/RuntimeModifyShapesTest.java @@ -1,6 +1,7 @@ package org.eclipse.rdf4j.sail.shacl; 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.repository.sail.SailRepository; import org.eclipse.rdf4j.repository.sail.SailRepositoryConnection; @@ -49,10 +50,10 @@ public void checkForExceptionWhenModifyingShapes() throws IOException { try (SailRepositoryConnection connection = sailRepository.getConnection()) { connection.begin(); - connection.add(RuntimeModifyShapesTest.class.getClassLoader().getResourceAsStream("shaclDatatype.ttl"), "http://example.com/", RDFFormat.TURTLE, ShaclSail.SHAPE_GRAPH); + connection.add(RuntimeModifyShapesTest.class.getClassLoader().getResourceAsStream("shaclDatatype.ttl"), "http://example.com/", RDFFormat.TURTLE, RDF4J.SHACL_SHAPE_GRAPH); connection.commit(); connection.begin(); - connection.add(RuntimeModifyShapesTest.class.getClassLoader().getResourceAsStream("shacl.ttl"), "http://example.com/", RDFFormat.TURTLE, ShaclSail.SHAPE_GRAPH); + connection.add(RuntimeModifyShapesTest.class.getClassLoader().getResourceAsStream("shacl.ttl"), "http://example.com/", RDFFormat.TURTLE, RDF4J.SHACL_SHAPE_GRAPH); connection.commit(); } diff --git a/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/Utils.java b/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/Utils.java index a95b78906..460701095 100644 --- a/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/Utils.java +++ b/shacl/src/test/java/org/eclipse/rdf4j/sail/shacl/Utils.java @@ -17,6 +17,7 @@ import org.eclipse.rdf4j.model.Model; import org.eclipse.rdf4j.model.Statement; import org.eclipse.rdf4j.model.impl.SimpleValueFactory; +import org.eclipse.rdf4j.model.vocabulary.RDF4J; import org.eclipse.rdf4j.repository.RepositoryConnection; import org.eclipse.rdf4j.repository.sail.SailRepository; import org.eclipse.rdf4j.rio.RDFFormat; @@ -34,11 +35,11 @@ public static void loadShapeData(ShaclSail sail, String resourceName) throws RDF4JException, UnsupportedRDFormatException, IOException { InputStream shapesData = Utils.class.getResourceAsStream("/" + resourceName); - Model shapes = Rio.parse(shapesData, "", RDFFormat.TURTLE, ShaclSail.SHAPE_GRAPH); + Model shapes = Rio.parse(shapesData, "", RDFFormat.TURTLE, RDF4J.SHACL_SHAPE_GRAPH); try (SailConnection conn = sail.getConnection()) { conn.begin(); for (Statement st : shapes) { - conn.addStatement(st.getSubject(), st.getPredicate(), st.getObject(), ShaclSail.SHAPE_GRAPH); + conn.addStatement(st.getSubject(), st.getPredicate(), st.getObject(), RDF4J.SHACL_SHAPE_GRAPH); } conn.commit(); } @@ -49,11 +50,11 @@ public static void loadShapeData(SailRepository repo, String resourceName) throws RDF4JException, UnsupportedRDFormatException, IOException { InputStream shapesData = Utils.class.getResourceAsStream("/" + resourceName); - Model shapes = Rio.parse(shapesData, "", RDFFormat.TURTLE, ShaclSail.SHAPE_GRAPH); + Model shapes = Rio.parse(shapesData, "", RDFFormat.TURTLE, RDF4J.SHACL_SHAPE_GRAPH); try (RepositoryConnection conn = repo.getConnection()) { conn.begin(); for (Statement st : shapes) { - conn.add(st.getSubject(), st.getPredicate(), st.getObject(), ShaclSail.SHAPE_GRAPH); + conn.add(st.getSubject(), st.getPredicate(), st.getObject(), RDF4J.SHACL_SHAPE_GRAPH); } conn.commit(); }