Skip to content

Commit 3613b19

Browse files
committed
Update in the focus node resolving process
1 parent b1be495 commit 3613b19

File tree

13 files changed

+348
-261
lines changed

13 files changed

+348
-261
lines changed

shacl-diagram/src/main/java/fr/sparna/rdf/shacl/diagram/PlantUmlBox.java

-2
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,6 @@ public List<Resource> getShNode() {
6969

7070

7171
public boolean isTargeting(Resource classUri) {
72-
73-
7472
boolean hasShTargetClass = this.getTargetClass().filter(c -> c.equals(classUri)).isPresent();
7573
boolean isItselfTheClass =
7674
this.nodeShape.hasProperty(RDF.type, RDFS.Class)

shacl-play/src/main/java/fr/sparna/rdf/shacl/shaclplay/validate/ValidateController.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ private Model doValidate(
491491
null
492492
);
493493
validator.setProgressMonitor(new StringBufferProgressMonitor("SHACL validator"));
494-
validator.setValidateShapesTargets(true);
494+
validator.setResolveFocusNodes(true);
495495
validator.setCreateDetails(createDetails);
496496

497497
Model results = validator.validate(dataModel);
@@ -507,7 +507,7 @@ private Model doValidate(
507507
null
508508
);
509509
validator.setProgressMonitor(new StringBufferProgressMonitor("SHACL validator"));
510-
validator.setValidateShapesTargets(true);
510+
validator.setResolveFocusNodes(true);
511511
validator.setCreateDetails(createDetails);
512512

513513
Thread thread = new Thread(validator);

shacl-validator/src/main/java/fr/sparna/rdf/shacl/SHP.java

+18-1
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,27 @@
22

33
public class SHP {
44

5-
public static final String NAMESPACE = "http://shacl-play.sparna.fr/ontology#";
5+
public static final String NAMESPACE = "https://shacl-play.sparna.fr/ontology#";
6+
7+
public static final String NAMESPACE_SHAPES = "https://shacl-play.sparna.fr/shapes#";
68

9+
/**
10+
* Boolean flag on a SHACL shape indicating if it matched at least focus node
11+
*/
712
public static final String TARGET_MATCHED = NAMESPACE+"targetMatched";
813

14+
/**
15+
* Boolean flag on the validation report itself indicating if at least one shape matched a focus node
16+
*/
917
public static final String HAS_MATCHED = NAMESPACE+"hasMatched";
18+
19+
/**
20+
* The property that links a SHACL shape to a focus node it is targeting
21+
*/
22+
public static final String HAS_FOCUS_NODE = NAMESPACE+"hasFocusNode";
23+
24+
public static final String CLOSED_GRAPH_SHAPE = NAMESPACE_SHAPES+"ClosedGraphShape";
25+
26+
public static final String CLOSED_GRAPH_CONSTRAINT_COMPONENT = NAMESPACE_SHAPES+"ClosedGraphConstraintComponent";
1027

1128
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package fr.sparna.rdf.shacl.targets;
2+
3+
4+
import java.util.ArrayList;
5+
import java.util.List;
6+
7+
import org.apache.jena.rdf.model.Model;
8+
import org.apache.jena.rdf.model.RDFNode;
9+
import org.apache.jena.rdf.model.Resource;
10+
import org.apache.jena.vocabulary.RDF;
11+
import org.slf4j.Logger;
12+
import org.slf4j.LoggerFactory;
13+
import org.topbraid.shacl.vocabulary.SH;
14+
15+
import fr.sparna.rdf.shacl.SHP;
16+
17+
18+
/**
19+
* A listener that adds a "shacl-play:targetMatched false" triple to the target model for each shape that did not match any focus node,
20+
* and also adds a sh:hasMatched global triple to the validation report.
21+
*/
22+
public class AddHasTargetListener implements FocusNodeListener {
23+
24+
private Logger log = LoggerFactory.getLogger(this.getClass().getName());
25+
26+
private Model targetModel;
27+
28+
private List<Resource> shapesWithTargets = new ArrayList<Resource>();
29+
30+
public AddHasTargetListener(Model targetModel) {
31+
super();
32+
this.targetModel = targetModel;
33+
}
34+
35+
@Override
36+
public void notifyFocusNodes(Resource shape, Model data, List<RDFNode> focusNodes) {
37+
if(!focusNodes.isEmpty()) {
38+
if(!shapesWithTargets.contains(shape)) {
39+
shapesWithTargets.add(shape);
40+
}
41+
}
42+
}
43+
44+
@Override
45+
public void notifyEndShape(Resource shape, Model data) {
46+
if(!shapesWithTargets.contains(shape)) {
47+
log.debug("Shape "+shape+" did not match any focus node");
48+
targetModel.add(targetModel.createLiteralStatement(
49+
shape,
50+
targetModel.createProperty(SHP.TARGET_MATCHED),
51+
false
52+
));
53+
}
54+
}
55+
56+
@Override
57+
public void notifyEnd() {
58+
targetModel.add(targetModel.createLiteralStatement(
59+
targetModel.listResourcesWithProperty(RDF.type, SH.ValidationReport).next(),
60+
targetModel.createProperty(SHP.HAS_MATCHED),
61+
(this.shapesWithTargets.size() > 0)
62+
));
63+
}
64+
65+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
package fr.sparna.rdf.shacl.targets;
2+
3+
4+
import java.util.HashSet;
5+
import java.util.List;
6+
import java.util.Set;
7+
8+
import org.apache.jena.rdf.model.Model;
9+
import org.apache.jena.rdf.model.RDFNode;
10+
import org.apache.jena.rdf.model.Resource;
11+
import org.apache.jena.vocabulary.RDF;
12+
import org.slf4j.Logger;
13+
import org.slf4j.LoggerFactory;
14+
import org.topbraid.shacl.vocabulary.SH;
15+
16+
import fr.sparna.rdf.shacl.SHP;
17+
18+
19+
/**
20+
* Listens for the focus nodes of shapes and adds a validation result for each subject in the data model that is not the target of any shape.
21+
*/
22+
public class AddNotTargetOfAnyShapeListener implements FocusNodeListener {
23+
24+
private Logger log = LoggerFactory.getLogger(this.getClass().getName());
25+
26+
private Model dataModel;
27+
private Model existingValidationReport;
28+
29+
private Set<RDFNode> targetedResources = new HashSet<RDFNode>();
30+
31+
public AddNotTargetOfAnyShapeListener(Model dataModel, Model existingValidationReport) {
32+
super();
33+
this.dataModel = dataModel;
34+
this.existingValidationReport = existingValidationReport;
35+
}
36+
37+
@Override
38+
public void notifyFocusNodes(Resource shape, Model data, List<RDFNode> focusNodes) {
39+
targetedResources.addAll(focusNodes);
40+
}
41+
42+
@Override
43+
public void notifyEndShape(Resource shape, Model data) {
44+
// nothing
45+
}
46+
47+
@Override
48+
public void notifyEnd() {
49+
// every subject of any triple in the validated graph should be the target of at least one shape
50+
// otherwise, add a violation result in the validation report
51+
52+
Resource report = existingValidationReport.listResourcesWithProperty(RDF.type, SH.ValidationReport).next();
53+
54+
// Iterate over all subjects in the data model
55+
dataModel.listSubjects().forEachRemaining(subject -> {
56+
// Check if the subject is the target of at least one shape
57+
boolean isTarget = targetedResources.contains(subject);
58+
59+
// If not, add a violation result in the validation report
60+
if (!isTarget) {
61+
log.debug("Subject " + subject + " is not the target of any shape");
62+
Resource violation = existingValidationReport.createResource();
63+
violation.addProperty(RDF.type, SH.ValidationResult);
64+
violation.addProperty(SH.resultSeverity, SH.Info);
65+
violation.addProperty(SH.focusNode, subject);
66+
violation.addProperty(SH.resultMessage, "Subject is not the target of any shape");
67+
violation.addProperty(SH.sourceConstraintComponent, existingValidationReport.createResource(SHP.CLOSED_GRAPH_SHAPE));
68+
violation.addProperty(SH.sourceShape, existingValidationReport.createResource(SHP.CLOSED_GRAPH_CONSTRAINT_COMPONENT));
69+
70+
report.addProperty(SH.result, violation);
71+
}
72+
});
73+
}
74+
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package fr.sparna.rdf.shacl.targets;
2+
3+
4+
import java.util.List;
5+
6+
import org.apache.jena.rdf.model.Model;
7+
import org.apache.jena.rdf.model.RDFNode;
8+
import org.apache.jena.rdf.model.Resource;
9+
10+
/**
11+
* A listener interface for the shape focus nodes resolver. Implementations of this interface will be notified of the focus nodes.
12+
*/
13+
public interface FocusNodeListener {
14+
15+
public void notifyFocusNodes(Resource shape, Model data, List<RDFNode> focusNodes);
16+
17+
public void notifyEndShape(Resource shape, Model data);
18+
19+
public void notifyEnd();
20+
21+
}

shacl-validator/src/main/java/fr/sparna/rdf/shacl/targets/FocusNodeProcessor.java

-13
This file was deleted.

shacl-validator/src/main/java/fr/sparna/rdf/shacl/targets/ShapeTargetResolver.java shacl-validator/src/main/java/fr/sparna/rdf/shacl/targets/ShapeFocusNodesResolver.java

+72-22
Original file line numberDiff line numberDiff line change
@@ -19,78 +19,127 @@
1919
import org.apache.jena.vocabulary.RDFS;
2020
import org.topbraid.shacl.vocabulary.SH;
2121

22-
public class ShapeTargetResolver {
22+
/**
23+
* Resolves the target definitions of shapes to find their focus nodes, and notify the listeners of the results.
24+
*/
25+
public class ShapeFocusNodesResolver {
2326

2427
protected Model shapeModel;
2528
protected Model data;
2629

30+
protected List<FocusNodeListener> listeners = new ArrayList<FocusNodeListener>();
31+
2732

28-
public ShapeTargetResolver(Model shapeModel, Model data) {
33+
public ShapeFocusNodesResolver(Model shapeModel, Model data) {
2934
super();
3035
this.shapeModel = shapeModel;
3136
this.data = data;
3237
}
3338

34-
public void resolveFocusNodes(FocusNodeProcessor p) {
39+
public void resolveFocusNodes() {
3540

3641
// for each subject of a target predicate...
3742
List<Resource> shapes = shapeModel.listResourcesWithProperty(SH.targetNode)
3843
.andThen(shapeModel.listResourcesWithProperty(SH.targetClass))
3944
.andThen(shapeModel.listResourcesWithProperty(SH.targetSubjectsOf))
4045
.andThen(shapeModel.listResourcesWithProperty(SH.targetObjectsOf))
41-
.andThen(shapeModel.listResourcesWithProperty(RDF.type, RDFS.Class))
42-
.andThen(shapeModel.listResourcesWithProperty(RDF.type, OWL.Class))
46+
.andThen(shapeModel.listResourcesWithProperty(RDF.type, RDFS.Class).filterKeep(r -> r.hasProperty(RDF.type, SH.NodeShape)))
47+
.andThen(shapeModel.listResourcesWithProperty(RDF.type, OWL.Class).filterKeep(r -> r.hasProperty(RDF.type, SH.NodeShape)))
4348
// generic SPARQL-based targets
4449
.andThen(shapeModel.listResourcesWithProperty(SH.target)).toList();
4550

46-
for (Resource r : shapes) {
47-
List<RDFNode> focusNodes = resolveFocusNodes(r, data);
48-
// TODO
51+
for (Resource shape : shapes) {
52+
resolveFocusNodes(shape, data);
53+
}
54+
55+
// notify of end
56+
for(FocusNodeListener listener : listeners) {
57+
listener.notifyEnd();
4958
}
5059
}
5160

52-
private List<RDFNode> resolveFocusNodes(Resource shape, Model data) {
53-
List<RDFNode> focusNodes = new ArrayList<RDFNode>();
54-
61+
public void setListeners(List<FocusNodeListener> listeners) {
62+
this.listeners = listeners;
63+
}
64+
65+
public List<FocusNodeListener> getListeners() {
66+
return this.listeners;
67+
}
68+
69+
private void resolveFocusNodes(Resource shape, Model data) {
70+
5571
// * sh:targetNode
5672
StmtIterator it = shape.listProperties(SH.targetNode);
5773
while(it.hasNext()) {
58-
focusNodes.addAll(resolveTargetNode(it.next().getObject().asResource(), data));
74+
notifyListeners(
75+
shape,
76+
data,
77+
resolveTargetNode(it.next().getObject().asResource(), data)
78+
);
5979
}
6080

6181
// * sh:targetClass
6282
it = shape.listProperties(SH.targetClass);
6383
while(it.hasNext()) {
64-
focusNodes.addAll(resolveTargetClass(it.next().getObject().asResource(), data));
84+
notifyListeners(
85+
shape,
86+
data,
87+
resolveTargetClass(it.next().getObject().asResource(), data)
88+
);
6589
}
6690

6791
// * implicit targetClass if the shape is also a class
6892
if(shape.hasProperty(RDF.type, RDFS.Class)) {
69-
focusNodes.addAll(resolveTargetClass(shape, data));
93+
notifyListeners(
94+
shape,
95+
data,
96+
resolveTargetClass(shape, data)
97+
);
7098
}
7199

72100
// * sh:targetSubjectsOf
73101
it = shape.listProperties(SH.targetSubjectsOf);
74102
while(it.hasNext()) {
75-
focusNodes.addAll(resolveTargetSubjectsOf(it.next().getObject().asResource(), data));
103+
notifyListeners(
104+
shape,
105+
data,
106+
resolveTargetSubjectsOf(it.next().getObject().asResource(), data)
107+
);
76108
}
77109

78110
// * sh:targetObjectsOf
79111
it = shape.listProperties(SH.targetObjectsOf);
80112
while(it.hasNext()) {
81-
focusNodes.addAll(resolveTargetObjectsOf(it.next().getObject().asResource(), data));
113+
notifyListeners(
114+
shape,
115+
data,
116+
resolveTargetObjectsOf(it.next().getObject().asResource(), data)
117+
);
82118
}
83119

84120
// * sh:target
85121
it = shape.listProperties(SH.target);
86122
while(it.hasNext()) {
87123
Resource shTargetValue = it.next().getObject().asResource();
88124
if(shTargetValue.hasProperty(SH.select)) {
89-
focusNodes.addAll(resolveTargetSparql(shTargetValue.getProperty(SH.select).getObject().asLiteral().getLexicalForm(), data));
125+
notifyListeners(
126+
shape,
127+
data,
128+
resolveTargetSparql(shTargetValue.getProperty(SH.select).getObject().asLiteral().getLexicalForm(), data)
129+
);
90130
}
91131
}
92-
93-
return focusNodes;
132+
133+
// notify of end shape
134+
for(FocusNodeListener listener : listeners) {
135+
listener.notifyEndShape(shape, data);
136+
}
137+
}
138+
139+
private void notifyListeners(Resource shape, Model data, List<RDFNode> focusNodes) {
140+
for(FocusNodeListener listener : listeners) {
141+
listener.notifyFocusNodes(shape, data, focusNodes);
142+
}
94143
}
95144

96145
private List<RDFNode> resolveTargetNode(Resource targetNode, Model data) {
@@ -118,10 +167,11 @@ private List<RDFNode> resolveTargetClass(Resource targetClass, Model data) {
118167
}
119168
}
120169

121-
private List<Resource> resolveTargetSubjectsOf(Resource targetSubjectsOf, Model data) {
122-
return data.listSubjectsWithProperty(data.createProperty(targetSubjectsOf.getURI())).toList();
170+
private List<RDFNode> resolveTargetSubjectsOf(Resource targetSubjectsOf, Model data) {
171+
List<Resource> resources = data.listSubjectsWithProperty(data.createProperty(targetSubjectsOf.getURI())).toList();
172+
// cast to a List<RDFNode> to match the return type
173+
return new ArrayList<RDFNode>(resources);
123174
}
124-
125175
private List<RDFNode> resolveTargetObjectsOf(Resource targetObjectsOf, Model data) {
126176
return data.listObjectsOfProperty(data.createProperty(targetObjectsOf.getURI())).toList();
127177
}

0 commit comments

Comments
 (0)