This is an automated email from the ASF dual-hosted git repository. andy pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/jena.git
commit 8fef93101bbbd942b8ce8ac03257345bc225d065 Author: Andy Seaborne <[email protected]> AuthorDate: Sun Dec 31 15:42:05 2023 +0000 GH-2157: Support ?currentShape and ?shapesGraph --- .../shacl/engine/constraint/SparqlValidation.java | 31 ++++++--- jena-shacl/src/test/files/local/manifest.ttl | 4 ++ .../files/local/other/sparql-vars-001-data.ttl | 10 +++ .../files/local/other/sparql-vars-001-results.ttl | 18 +++++ .../files/local/other/sparql-vars-001-shape.ttl | 35 ++++++++++ .../test/files/std/sparql/pre-binding/manifest.ttl | 3 +- .../test/java/org/apache/jena/shacl/TC_SHACL.java | 2 + .../tests/jena_shacl/JenaShaclTestsByCode.java | 77 ++++++++++++++++++++++ 8 files changed, 169 insertions(+), 11 deletions(-) diff --git a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/SparqlValidation.java b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/SparqlValidation.java index d40d0f092b..aa9e36b6bb 100644 --- a/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/SparqlValidation.java +++ b/jena-shacl/src/main/java/org/apache/jena/shacl/engine/constraint/SparqlValidation.java @@ -33,6 +33,7 @@ import org.apache.jena.query.*; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.ModelFactory; import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; import org.apache.jena.riot.out.NodeFmtLib; import org.apache.jena.shacl.engine.Parameter; import org.apache.jena.shacl.engine.ShaclPaths; @@ -41,9 +42,7 @@ import org.apache.jena.shacl.lib.ShLib; import org.apache.jena.shacl.parser.Constraint; import org.apache.jena.shacl.parser.Shape; import org.apache.jena.shacl.validation.event.ConstraintEvaluatedOnSinglePathNodeEvent; -import org.apache.jena.sparql.core.PathBlock; -import org.apache.jena.sparql.core.TriplePath; -import org.apache.jena.sparql.core.Var; +import org.apache.jena.sparql.core.*; import org.apache.jena.sparql.engine.binding.Binding; import org.apache.jena.sparql.path.P_Link; import org.apache.jena.sparql.path.Path; @@ -109,6 +108,7 @@ import org.apache.jena.sparql.util.ModelUtils; if ( path != null && !(path instanceof P_Link ) ) query = QueryTransformOps.transform(query, new ElementTransformPath(SparqlConstraint.varPath, path)); + if ( USE_QueryTransformOps ) { // Done with QueryTransformOps.transform Map<Var, Node> substitutions = parameterMapToSyntaxSubstitutions(parameterMap, focusNode, path); @@ -121,7 +121,18 @@ import org.apache.jena.sparql.util.ModelUtils; QuerySolutionMap qsm = parameterMapToPreBinding(parameterMap, focusNode, path, model); if ( query.isAskType() ) qsm.add("value", ModelUtils.convertGraphNodeToRDFNode(valueNode, model)); - qExec = QueryExecution.create().query(query).model(model).initialBinding(qsm).build(); + //qExec = QueryExecution.create().query(query).model(model).initialBinding(qsm).build(); + + // ---- Dataset needed for the shapes graph + Resource shapesGraphResource = model.createResource("foo"); + qsm.add("currentShape", ModelUtils.convertGraphNodeToRDFNode(shape.getShapeNode(), model)); + qsm.add("shapesGraph", shapesGraphResource); + + // No copying of graphs. Set the default graph on creation. + DatasetGraph dsg = DatasetGraphFactory.createGeneral(model.getGraph()); // Dataset by links. + dsg.addGraph(shapesGraphResource.asNode(), shape.getShapeGraph()); + Dataset ds = DatasetFactory.wrap(dsg); + qExec = QueryExecution.create().query(query).dataset(ds).initialBinding(qsm).build(); } // ASK validator. @@ -133,14 +144,16 @@ import org.apache.jena.sparql.util.ModelUtils; : substitute(violationTemplate, parameterMap, focusNode, path, valueNode); vCxt.reportEntry(msg, shape, focusNode, path, valueNode, reportConstraint); } - vCxt.notifyValidationListener(() -> new ConstraintEvaluatedOnSinglePathNodeEvent(vCxt, shape, focusNode, reportConstraint, path, valueNode, - b)); + vCxt.notifyValidationListener(() -> + new ConstraintEvaluatedOnSinglePathNodeEvent(vCxt, shape, focusNode, reportConstraint, path, valueNode,b)); return b; } + // SELECT validator. ResultSet rs = qExec.execSelect(); if ( ! rs.hasNext() ) { - vCxt.notifyValidationListener(() -> new ConstraintEvaluatedOnSinglePathNodeEvent(vCxt, shape, focusNode, reportConstraint, path, valueNode, true)); + vCxt.notifyValidationListener(() -> + new ConstraintEvaluatedOnSinglePathNodeEvent(vCxt, shape, focusNode, reportConstraint, path, valueNode, true)); return true; } @@ -168,8 +181,8 @@ import org.apache.jena.sparql.util.ModelUtils; } final Path finalRPath = rPath; final Node finalValue = value; - vCxt.notifyValidationListener(() -> new ConstraintEvaluatedOnSinglePathNodeEvent(vCxt, shape, focusNode, reportConstraint, finalRPath, finalValue, - false)); + vCxt.notifyValidationListener(() -> + new ConstraintEvaluatedOnSinglePathNodeEvent(vCxt, shape, focusNode, reportConstraint, finalRPath, finalValue, false)); vCxt.reportEntry(msg, shape, focusNode, rPath, value, reportConstraint); } return false; diff --git a/jena-shacl/src/test/files/local/manifest.ttl b/jena-shacl/src/test/files/local/manifest.ttl index 76ae277694..0b0d688d90 100644 --- a/jena-shacl/src/test/files/local/manifest.ttl +++ b/jena-shacl/src/test/files/local/manifest.ttl @@ -7,6 +7,10 @@ PREFIX sh: <http://www.w3.org/ns/shacl#> PREFIX sht: <http://www.w3.org/ns/shacl-test#> PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> +## Not all the test are controlled by manifest because they +## test features not easily captured in a single test file. +## e.g. split shapes and data + <> rdf:type mf:Manifest ; mf:include <additional/lang-simple-1.ttl> ; diff --git a/jena-shacl/src/test/files/local/other/sparql-vars-001-data.ttl b/jena-shacl/src/test/files/local/other/sparql-vars-001-data.ttl new file mode 100644 index 0000000000..7ee05ce1ca --- /dev/null +++ b/jena-shacl/src/test/files/local/other/sparql-vars-001-data.ttl @@ -0,0 +1,10 @@ +PREFIX : <http://example/> + +:testDataSubject1 :p1 1 . + +:testDataSubject2 :p1 2 . + +:testDataSubject3 :p2 1 . + +:testDataSubject3 :p2 2 . + diff --git a/jena-shacl/src/test/files/local/other/sparql-vars-001-results.ttl b/jena-shacl/src/test/files/local/other/sparql-vars-001-results.ttl new file mode 100644 index 0000000000..a2c8e63cd2 --- /dev/null +++ b/jena-shacl/src/test/files/local/other/sparql-vars-001-results.ttl @@ -0,0 +1,18 @@ +PREFIX : <http://example/> +PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> +PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> +PREFIX sh: <http://www.w3.org/ns/shacl#> +PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> + +[ rdf:type sh:ValidationReport; + sh:conforms false; + sh:result [ rdf:type sh:ValidationResult; + sh:focusNode :testDataSubject1; + sh:resultMessage "Property-value pair (<http://example/p1> 1) found"; + sh:resultPath :p1; + sh:resultSeverity sh:Violation; + sh:sourceConstraintComponent sh:SPARQLConstraintComponent; + sh:sourceShape <file:///home/afs/ASF/jena5/jena-shacl/src/test/files/local/other/sparql-vars-001-shape.ttl#PropertyValueShape>; + sh:value 1 + ] +] . diff --git a/jena-shacl/src/test/files/local/other/sparql-vars-001-shape.ttl b/jena-shacl/src/test/files/local/other/sparql-vars-001-shape.ttl new file mode 100644 index 0000000000..45420509db --- /dev/null +++ b/jena-shacl/src/test/files/local/other/sparql-vars-001-shape.ttl @@ -0,0 +1,35 @@ +# Test ?currentShape and ?shapesGraph + +PREFIX : <http://example/> +PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> +PREFIX sh: <http://www.w3.org/ns/shacl#> +## PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> + + +<#prefixes> + sh:declare [ sh:prefix "ex"; sh:namespace "http://example/" ] ; + . + +<#PropertyValueShape> + rdf:type sh:NodeShape ; + + sh:targetNode :testDataSubject1 ; + sh:targetNode :testDataSubject2 ; + sh:targetNode :testDataSubject3 ; + sh:targetNode :testDataSubject4 ; + + sh:labelTemplate "Test for absence of a property-value pair, using SPARQL" ; + sh:prefixes <#prefixes> ; + # Target must not have this pair. + :propertyValue ( :p1 1 ) ; + sh:sparql [ + sh:message "Property-value pair ({?path} {?value}) found" ; + sh:prefixes <#prefixes> ; + sh:select """ + SELECT ?this (?property AS ?path) ?value { + GRAPH ?shapesGraph { ?currentShape ex:propertyValue ( ?property ?value ) } + ?this ?property ?value . + } +""" ; + ] ; +. diff --git a/jena-shacl/src/test/files/std/sparql/pre-binding/manifest.ttl b/jena-shacl/src/test/files/std/sparql/pre-binding/manifest.ttl index 8276ee2898..2c8a6457eb 100644 --- a/jena-shacl/src/test/files/std/sparql/pre-binding/manifest.ttl +++ b/jena-shacl/src/test/files/std/sparql/pre-binding/manifest.ttl @@ -14,8 +14,7 @@ ## mf:include <pre-binding-006.ttl> ; mf:include <pre-binding-007.ttl> ; - # $shapesGraph not supported. -## mf:include <shapesGraph-001.ttl> ; + mf:include <shapesGraph-001.ttl> ; # MINUS ## mf:include <unsupported-sparql-001.ttl> ; diff --git a/jena-shacl/src/test/java/org/apache/jena/shacl/TC_SHACL.java b/jena-shacl/src/test/java/org/apache/jena/shacl/TC_SHACL.java index 15f99f0b9a..879a87b40c 100644 --- a/jena-shacl/src/test/java/org/apache/jena/shacl/TC_SHACL.java +++ b/jena-shacl/src/test/java/org/apache/jena/shacl/TC_SHACL.java @@ -22,6 +22,7 @@ import org.apache.jena.shacl.compact.TS_Compact; import org.apache.jena.shacl.tests.TestImports; import org.apache.jena.shacl.tests.TestValidationReport; import org.apache.jena.shacl.tests.ValidationListenerTests; +import org.apache.jena.shacl.tests.jena_shacl.JenaShaclTestsByCode; import org.apache.jena.shacl.tests.jena_shacl.TS_JenaShacl; import org.apache.jena.shacl.tests.std.TS_StdSHACL; import org.junit.runner.RunWith; @@ -35,6 +36,7 @@ import org.junit.runners.Suite; , TS_Compact.class , TestImports.class , ValidationListenerTests.class + , JenaShaclTestsByCode.class } ) public class TC_SHACL { } diff --git a/jena-shacl/src/test/java/org/apache/jena/shacl/tests/jena_shacl/JenaShaclTestsByCode.java b/jena-shacl/src/test/java/org/apache/jena/shacl/tests/jena_shacl/JenaShaclTestsByCode.java new file mode 100644 index 0000000000..7114359063 --- /dev/null +++ b/jena-shacl/src/test/java/org/apache/jena/shacl/tests/jena_shacl/JenaShaclTestsByCode.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.jena.shacl.tests.jena_shacl; + +import static org.junit.Assert.assertTrue; + +import java.io.PrintStream; + +import org.apache.jena.graph.Graph; +import org.apache.jena.riot.Lang; +import org.apache.jena.riot.RDFParser; +import org.apache.jena.riot.RDFWriter; +import org.apache.jena.shacl.ShaclValidator; +import org.apache.jena.sparql.util.IsoMatcher; +import org.junit.Test; + +public class JenaShaclTestsByCode { + static final String DIR = "src/test/files/local/other/"; + @Test public void sparql_vars_001() { + execTest("sparql-vars-001", + DIR+"sparql-vars-001-data.ttl", + DIR+"sparql-vars-001-shape.ttl", + DIR+"sparql-vars-001-results.ttl"); + + } + + private void execTest(String label, String datafile, String shapesfile, String expectedResults) { + Graph data = load(datafile) ; + Graph shapes = load(shapesfile) ; + Graph expected = load(expectedResults); + + Graph actual = ShaclValidator.get().validate(shapes, data).getGraph(); + + boolean checkResults = IsoMatcher.isomorphic(expected, actual); + if ( ! checkResults ) { + PrintStream out = System.out; + out.println("==== Failure: "+label); + if ( false ) { + // Very verbose : enable for development/debugging! + out.println("== Data:"); + write(out, data); + out.println("== Shapes:"); + write(out, shapes); + } + out.println("== Expected:"); + write(out, expected); + out.println("== Actual:"); + write(out, actual); + } + assertTrue(checkResults); + } + + private Graph load(String file) { + return RDFParser.source(file).toGraph(); + } + + private void write(PrintStream out, Graph graph) { + RDFWriter.source(graph).lang(Lang.TTL).output(out); + } + +}
