|
41 | 41 | import org.apache.jena.rdf.model.Property;
|
42 | 42 | import org.apache.jena.rdf.model.RDFNode;
|
43 | 43 | import org.apache.jena.rdf.model.Resource;
|
| 44 | +import org.apache.jena.rdf.model.ResourceFactory; |
44 | 45 | import org.apache.jena.rdf.model.Statement;
|
45 | 46 | import org.apache.jena.vocabulary.RDF;
|
46 | 47 |
|
@@ -78,8 +79,7 @@ public ShaclValidator( final Model shapesModel ) {
|
78 | 79 | * If you have more than one element to validate, prefer the method {@link #validateElements(List)} to calling this method in a loop
|
79 | 80 | * for better performance.
|
80 | 81 | * {@link Resource#getModel()} on the element must not return null, i.e., the resource may not be created using
|
81 |
| - * {@link org.apache.jena.rdf.model.ResourceFactory#createProperty(String)}, but instead must be created via |
82 |
| - * {@link Model#createResource(String)}. |
| 82 | + * {@link ResourceFactory#createProperty(String)}, but instead must be created via {@link Model#createResource(String)}. |
83 | 83 | *
|
84 | 84 | * @param element the element to be validated
|
85 | 85 | * @return the list of {@link Violation}s if there are violations
|
@@ -170,45 +170,59 @@ public List<Violation> validateElements( final List<Resource> elements ) {
|
170 | 170 | }
|
171 | 171 |
|
172 | 172 | public List<Violation> validateShapeForElement( final Resource element, final Shape.Node nodeShape, final Model resolvedModel ) {
|
| 173 | + return validateShapeForElement( element, nodeShape, resolvedModel, Optional.empty() ); |
| 174 | + } |
| 175 | + |
| 176 | + public List<Violation> validateShapeForElement( final Resource element, final Shape.Node nodeShape, final Model resolvedModel, |
| 177 | + final Optional<EvaluationContext> parentContext ) { |
173 | 178 | final List<Violation> violations = new ArrayList<>();
|
174 | 179 | for ( final Shape.Property propertyShape : nodeShape.properties() ) {
|
175 |
| - for ( final Constraint constraint : propertyShape.attributes().constraints() ) { |
176 |
| - final List<Statement> reachableNodes = propertyShape.path().accept( element, retriever ); |
177 |
| - // For all values that are present on the target node, check the applicable shapes and collect violations |
178 |
| - for ( final Statement assertion : reachableNodes ) { |
179 |
| - final EvaluationContext context = new EvaluationContext( element, nodeShape, Optional.of( propertyShape ), |
180 |
| - Optional.of( assertion.getPredicate() ), List.of( assertion ), this, resolvedModel ); |
181 |
| - violations.addAll( constraint.apply( assertion.getObject(), context ) ); |
182 |
| - } |
183 |
| - |
184 |
| - // important detail: Sparql constraints must run independent of whether there are any matches via the sh:path property or not |
185 |
| - // ( the check could be the verification whether the property exists ) |
186 |
| - if ( reachableNodes.isEmpty() && constraint instanceof SparqlConstraint ) { |
187 |
| - final EvaluationContext context = new EvaluationContext( element, nodeShape, Optional.of( propertyShape ), Optional.empty(), |
188 |
| - List.of(), this, resolvedModel ); |
189 |
| - violations.addAll( constraint.apply( null, context ) ); |
190 |
| - } |
191 |
| - |
192 |
| - // MinCount needs to be handled separately: If the property is not used at all on the target node, but a MinCount constraints |
193 |
| - // >= 1 exists, a violation must be emitted even though no value for the property exists |
194 |
| - if ( reachableNodes.isEmpty() && constraint instanceof MinCountConstraint |
195 |
| - && propertyShape.path() instanceof final PredicatePath predicatePath ) { |
196 |
| - final Property rdfProperty = resolvedModel.createProperty( predicatePath.predicate().getURI() ); |
197 |
| - final EvaluationContext context = new EvaluationContext( element, nodeShape, Optional.of( propertyShape ), |
198 |
| - Optional.of( rdfProperty ), List.of(), this, resolvedModel ); |
199 |
| - violations.addAll( constraint.apply( null, context ) ); |
200 |
| - } |
201 |
| - } |
| 180 | + violations.addAll( validateShapeForElement( element, nodeShape, propertyShape, resolvedModel, parentContext ) ); |
202 | 181 | }
|
203 | 182 |
|
204 |
| - final EvaluationContext context = new EvaluationContext( element, nodeShape, Optional.empty(), Optional.empty(), List.of(), this, |
205 |
| - resolvedModel ); |
| 183 | + final EvaluationContext context = new EvaluationContext( element, nodeShape, Optional.empty(), Optional.empty(), |
| 184 | + parentContext, List.of(), this, resolvedModel ); |
206 | 185 | for ( final Constraint constraint : nodeShape.attributes().constraints() ) {
|
207 | 186 | if ( !constraint.canBeUsedOnNodeShapes() ) {
|
208 | 187 | continue;
|
209 | 188 | }
|
210 | 189 | violations.addAll( constraint.apply( element, context ) );
|
211 | 190 | }
|
| 191 | + return violations; |
| 192 | + } |
| 193 | + |
| 194 | + public List<Violation> validateShapeForElement( final Resource element, final Shape.Node nodeShape, final Shape.Property propertyShape, |
| 195 | + final Model resolvedModel, final Optional<EvaluationContext> parentContext ) { |
| 196 | + final List<Violation> violations = new ArrayList<>(); |
| 197 | + |
| 198 | + for ( final Constraint constraint : propertyShape.attributes().constraints() ) { |
| 199 | + final List<Statement> reachableNodes = propertyShape.path().accept( element, retriever ); |
| 200 | + // For all values that are present on the target node, check the applicable shapes and collect violations |
| 201 | + for ( final Statement assertion : reachableNodes ) { |
| 202 | + final EvaluationContext context = new EvaluationContext( element, nodeShape, Optional.of( propertyShape ), |
| 203 | + Optional.of( assertion.getPredicate() ), parentContext, List.of( assertion ), this, resolvedModel ); |
| 204 | + violations.addAll( constraint.apply( assertion.getObject(), context ) ); |
| 205 | + } |
| 206 | + |
| 207 | + // important detail: Sparql constraints must run independent of whether there are any matches via the sh:path property or not |
| 208 | + // ( the check could be the verification whether the property exists ) |
| 209 | + if ( reachableNodes.isEmpty() && constraint instanceof SparqlConstraint ) { |
| 210 | + final EvaluationContext context = new EvaluationContext( element, nodeShape, Optional.of( propertyShape ), Optional.empty(), |
| 211 | + parentContext, List.of(), this, resolvedModel ); |
| 212 | + violations.addAll( constraint.apply( null, context ) ); |
| 213 | + } |
| 214 | + |
| 215 | + // MinCount needs to be handled separately: If the property is not used at all on the target node, but a MinCount constraints |
| 216 | + // >= 1 exists, a violation must be emitted even though no value for the property exists |
| 217 | + if ( reachableNodes.isEmpty() && constraint instanceof MinCountConstraint ) { |
| 218 | + final Optional<Property> property = propertyShape.path() instanceof PredicatePath ? |
| 219 | + Optional.of( resolvedModel.createProperty( ((PredicatePath) propertyShape.path()).predicate().getURI() ) ) : |
| 220 | + Optional.empty(); |
| 221 | + final EvaluationContext context = new EvaluationContext( element, nodeShape, Optional.of( propertyShape ), |
| 222 | + property, parentContext, List.of(), this, resolvedModel ); |
| 223 | + violations.addAll( constraint.apply( null, context ) ); |
| 224 | + } |
| 225 | + } |
212 | 226 |
|
213 | 227 | return violations;
|
214 | 228 | }
|
@@ -267,4 +281,8 @@ public List<Shape.Node> getShapes() {
|
267 | 281 | public Model getShapesModel() {
|
268 | 282 | return shapesModel;
|
269 | 283 | }
|
| 284 | + |
| 285 | + public PathNodeRetriever getRetriever() { |
| 286 | + return retriever; |
| 287 | + } |
270 | 288 | }
|
0 commit comments