33
33
import org .apache .jena .rdf .model .StmtIterator ;
34
34
import org .apache .jena .util .PrintUtil ;
35
35
import org .apache .jena .vocabulary .RDF ;
36
-
37
- import com .google .common .collect .ImmutableMap ;
38
- import com .google .common .collect .Streams ;
39
-
40
36
import org .eclipse .esmf .aspectmodel .shacl .constraint .AllowedLanguagesConstraint ;
41
37
import org .eclipse .esmf .aspectmodel .shacl .constraint .AllowedValuesConstraint ;
42
38
import org .eclipse .esmf .aspectmodel .shacl .constraint .AndConstraint ;
75
71
import org .eclipse .esmf .aspectmodel .shacl .path .ZeroOrMorePath ;
76
72
import org .eclipse .esmf .aspectmodel .shacl .path .ZeroOrOnePath ;
77
73
74
+ import com .google .common .collect .ImmutableMap ;
75
+ import com .google .common .collect .Streams ;
76
+
78
77
/**
79
78
* Takes an RDF model describing one or more SHACL shapes as input and parses them into {@link Shape}s
80
79
*/
81
80
public class ShapeLoader implements Function <Model , List <Shape .Node >> {
82
81
private static final SHACL SHACL = new SHACL ();
83
82
83
+ /**
84
+ * When constraints are instantiated for a shape, the context is passed as input
85
+ * @param path If the parent shape of the constraint is a property shape, the path determines what the property shape refers to
86
+ */
87
+ private record ShapeContext (Statement statement , Optional <Path > path ) {
88
+ }
89
+
84
90
/**
85
91
* Encodes the logic of how to build an instance of {@link Constraint} from given RDF predicates on the value node
86
92
*/
87
- private final Map <Property , Function <Statement , Constraint >> constraintLoaders = ImmutableMap .<Property , Function <Statement , Constraint >> builder ()
88
- .put ( SHACL .class_ (), statement -> new ClassConstraint ( statement .getResource () ) )
89
- .put ( SHACL .datatype (), statement -> new DatatypeConstraint ( statement .getResource ().getURI () ) )
90
- .put ( SHACL .nodeKind (), statement -> new NodeKindConstraint ( Shape .NodeKind .valueOf ( statement .getResource ().getLocalName () ) ) )
91
- .put ( SHACL .minCount (), statement -> new MinCountConstraint ( statement .getLiteral ().getInt () ) )
92
- .put ( SHACL .maxCount (), statement -> new MaxCountConstraint ( statement .getLiteral ().getInt () ) )
93
- .put ( SHACL .minExclusive (), statement -> new MinExclusiveConstraint ( statement .getLiteral () ) )
94
- .put ( SHACL .minInclusive (), statement -> new MinInclusiveConstraint ( statement .getLiteral () ) )
95
- .put ( SHACL .maxExclusive (), statement -> new MaxExclusiveConstraint ( statement .getLiteral () ) )
96
- .put ( SHACL .maxInclusive (), statement -> new MaxInclusiveConstraint ( statement .getLiteral () ) )
97
- .put ( SHACL .minLength (), statement -> new MinLengthConstraint ( statement .getLiteral ().getInt () ) )
98
- .put ( SHACL .maxLength (), statement -> new MaxLengthConstraint ( statement .getLiteral ().getInt () ) )
99
- .put ( SHACL .pattern (), statement -> {
100
- String flagsString = Optional .ofNullable ( statement .getSubject ().getProperty ( SHACL .flags () ) ).map ( Statement ::getString ).orElse ( "" );
101
- return new PatternConstraint ( buildPattern ( statement .getLiteral ().getString (), flagsString ) );
93
+ private final Map <Property , Function <ShapeContext , Constraint >> constraintLoaders = ImmutableMap .<Property , Function <ShapeContext , Constraint >> builder ()
94
+ .put ( SHACL .class_ (), context -> new ClassConstraint ( context . statement () .getResource () ) )
95
+ .put ( SHACL .datatype (), context -> new DatatypeConstraint ( context . statement () .getResource ().getURI () ) )
96
+ .put ( SHACL .nodeKind (), context -> new NodeKindConstraint ( Shape .NodeKind .valueOf ( context . statement () .getResource ().getLocalName () ) ) )
97
+ .put ( SHACL .minCount (), context -> new MinCountConstraint ( context . statement () .getLiteral ().getInt () ) )
98
+ .put ( SHACL .maxCount (), context -> new MaxCountConstraint ( context . statement () .getLiteral ().getInt () ) )
99
+ .put ( SHACL .minExclusive (), context -> new MinExclusiveConstraint ( context . statement () .getLiteral () ) )
100
+ .put ( SHACL .minInclusive (), context -> new MinInclusiveConstraint ( context . statement () .getLiteral () ) )
101
+ .put ( SHACL .maxExclusive (), context -> new MaxExclusiveConstraint ( context . statement () .getLiteral () ) )
102
+ .put ( SHACL .maxInclusive (), context -> new MaxInclusiveConstraint ( context . statement () .getLiteral () ) )
103
+ .put ( SHACL .minLength (), context -> new MinLengthConstraint ( context . statement () .getLiteral ().getInt () ) )
104
+ .put ( SHACL .maxLength (), context -> new MaxLengthConstraint ( context . statement () .getLiteral ().getInt () ) )
105
+ .put ( SHACL .pattern (), context -> {
106
+ String flagsString = Optional .ofNullable ( context . statement () .getSubject ().getProperty ( SHACL .flags () ) ).map ( Statement ::getString ).orElse ( "" );
107
+ return new PatternConstraint ( buildPattern ( context . statement () .getLiteral ().getString (), flagsString ) );
102
108
} )
103
- .put ( SHACL .languageIn (), statement ->
104
- new AllowedLanguagesConstraint ( statement .getResource ().as ( RDFList .class ).mapWith ( rdfNode -> rdfNode .asLiteral ().getString () ).toList () ) )
105
- .put ( SHACL .uniqueLang (), statement -> new UniqueLangConstraint () )
106
- .put ( SHACL .equals (), statement -> new EqualsConstraint ( statement .getModel ().createProperty ( statement .getResource ().getURI () ) ) )
107
- .put ( SHACL .disjoint (), statement -> new DisjointConstraint ( statement .getModel ().createProperty ( statement .getResource ().getURI () ) ) )
108
- .put ( SHACL .lessThan (), statement -> new LessThanConstraint ( statement .getModel ().createProperty ( statement .getResource ().getURI () ) ) )
109
+ .put ( SHACL .languageIn (), context ->
110
+ new AllowedLanguagesConstraint (
111
+ context .statement ().getResource ().as ( RDFList .class ).mapWith ( rdfNode -> rdfNode .asLiteral ().getString () ).toList () ) )
112
+ .put ( SHACL .uniqueLang (), context -> new UniqueLangConstraint () )
113
+ .put ( SHACL .equals (), context -> new EqualsConstraint ( context .statement ().getModel ().createProperty ( context .statement ().getResource ().getURI () ) ) )
114
+ .put ( SHACL .disjoint (),
115
+ context -> new DisjointConstraint ( context .statement ().getModel ().createProperty ( context .statement ().getResource ().getURI () ) ) )
116
+ .put ( SHACL .lessThan (),
117
+ context -> new LessThanConstraint ( context .statement ().getModel ().createProperty ( context .statement ().getResource ().getURI () ) ) )
109
118
.put ( SHACL .lessThanOrEquals (),
110
- statement -> new LessThanOrEqualsConstraint ( statement .getModel ().createProperty ( statement .getResource ().getURI () ) ) )
111
- .put ( SHACL .not (), statement -> new NotConstraint ( constraints ( statement .getObject ().asResource () ).get ( 0 ) ) )
112
- .put ( SHACL .and (), statement -> new AndConstraint ( nestedConstraintList ( statement ) ) )
113
- .put ( SHACL .or (), statement -> new OrConstraint ( nestedConstraintList ( statement ) ) )
114
- .put ( SHACL .xone (), statement -> new XoneConstraint ( nestedConstraintList ( statement ) ) )
115
- .put ( SHACL .node (), statement -> new NodeConstraint ( nodeShape ( statement .getObject ().asResource () ) ) )
116
- .put ( SHACL .in (), statement -> new AllowedValuesConstraint ( statement .getResource ().as ( RDFList .class ).asJavaList () ) )
117
- .put ( SHACL .closed (), statement -> {
118
- boolean closed = statement .getBoolean ();
119
+ context -> new LessThanOrEqualsConstraint ( context . statement () .getModel ().createProperty ( context . statement () .getResource ().getURI () ) ) )
120
+ .put ( SHACL .not (), context -> new NotConstraint ( constraints ( context . statement () .getObject ().asResource (), context . path () ).get ( 0 ) ) )
121
+ .put ( SHACL .and (), context -> new AndConstraint ( nestedConstraintList ( context . statement (), context . path () ) ) )
122
+ .put ( SHACL .or (), context -> new OrConstraint ( nestedConstraintList ( context . statement (), context . path () ) ) )
123
+ .put ( SHACL .xone (), context -> new XoneConstraint ( nestedConstraintList ( context . statement (), context . path () ) ) )
124
+ .put ( SHACL .node (), context -> new NodeConstraint ( context . path (), nodeShape ( context . statement () .getObject ().asResource () ) ) )
125
+ .put ( SHACL .in (), context -> new AllowedValuesConstraint ( context . statement () .getResource ().as ( RDFList .class ).asJavaList () ) )
126
+ .put ( SHACL .closed (), context -> {
127
+ boolean closed = context . statement () .getBoolean ();
119
128
if ( !closed ) {
120
129
throw new RuntimeException ();
121
130
}
122
- Set <Property > ignoredProperties = statement .getSubject ().getProperty ( SHACL .ignoredProperties () ).getResource ()
131
+ Set <Property > ignoredProperties = context . statement () .getSubject ().getProperty ( SHACL .ignoredProperties () ).getResource ()
123
132
.as ( RDFList .class )
124
133
.asJavaList ()
125
134
.stream ()
126
135
.map ( RDFNode ::asResource )
127
136
.map ( Resource ::getURI )
128
- .map ( uri -> statement .getModel ().createProperty ( uri ) )
137
+ .map ( uri -> context . statement () .getModel ().createProperty ( uri ) )
129
138
.collect ( Collectors .toSet () );
130
139
return new ClosedConstraint ( ignoredProperties );
131
140
} )
132
- .put ( SHACL .hasValue (), statement -> new HasValueConstraint ( statement .getObject () ) )
133
- .put ( SHACL .sparql (), statement -> {
134
- final Resource constraintNode = statement .getResource ();
141
+ .put ( SHACL .hasValue (), context -> new HasValueConstraint ( context . statement () .getObject () ) )
142
+ .put ( SHACL .sparql (), context -> {
143
+ final Resource constraintNode = context . statement () .getResource ();
135
144
final String message = Optional .ofNullable ( constraintNode .getProperty ( SHACL .message () ) ).map ( Statement ::getString ).orElse ( "" );
136
145
return new SparqlConstraint ( message , sparqlQuery ( constraintNode ) );
137
146
} )
138
- .put ( SHACL .js (), statement -> {
139
- Resource constraintNode = statement .getResource ();
147
+ .put ( SHACL .js (), context -> {
148
+ Resource constraintNode = context . statement () .getResource ();
140
149
JsLibrary library = jsLibrary ( constraintNode .getProperty ( SHACL .jsLibrary () ).getResource () );
141
150
String functionName = constraintNode .getProperty ( SHACL .jsFunctionName () ).getString ();
142
151
final String message = Optional .ofNullable ( constraintNode .getProperty ( SHACL .message () ) ).map ( Statement ::getString ).orElse ( "" );
@@ -146,13 +155,13 @@ public class ShapeLoader implements Function<Model, List<Shape.Node>> {
146
155
147
156
private final Map <Resource , JsLibrary > jsLibraries = new HashMap <>();
148
157
149
- private List <Constraint > nestedConstraintList ( final Statement statement ) {
158
+ private List <Constraint > nestedConstraintList ( final Statement statement , final Optional < Path > path ) {
150
159
return statement .getObject ().as ( RDFList .class ).asJavaList ().stream ()
151
160
.filter ( RDFNode ::isResource )
152
161
.map ( RDFNode ::asResource )
153
162
.flatMap ( resource -> resource .isURIResource ()
154
163
? nodeShape ( resource ).attributes ().constraints ().stream ()
155
- : shapeAttributes ( resource ).constraints ().stream () )
164
+ : shapeAttributes ( resource , path ).constraints ().stream () )
156
165
.toList ();
157
166
}
158
167
@@ -188,7 +197,7 @@ public List<Shape.Node> apply( final Model model ) {
188
197
.toList ();
189
198
}
190
199
191
- private Shape .Attributes shapeAttributes ( final Resource shapeNode ) {
200
+ private Shape .Attributes shapeAttributes ( final Resource shapeNode , final Optional < Path > path ) {
192
201
final Optional <String > uri = Optional .ofNullable ( shapeNode .getURI () );
193
202
final Optional <Resource > targetNode = Optional .ofNullable ( shapeNode .getProperty ( SHACL .targetNode () ) ).map ( Statement ::getResource );
194
203
final Optional <Resource > targetClass = Optional .ofNullable ( shapeNode .getProperty ( SHACL .targetClass () ) ).map ( Statement ::getResource );
@@ -211,22 +220,22 @@ private Shape.Attributes shapeAttributes( final Resource shapeNode ) {
211
220
final boolean deactivated = Optional .ofNullable ( shapeNode .getProperty ( SHACL .deactivated () ) ).map ( Statement ::getBoolean ).orElse ( false );
212
221
final Optional <String > message = Optional .ofNullable ( shapeNode .getProperty ( SHACL .message () ) ).map ( Statement ::getString );
213
222
final Shape .Severity severity = severity ( shapeNode );
214
- final List <Constraint > constraints = constraints ( shapeNode );
223
+ final List <Constraint > constraints = constraints ( shapeNode , path );
215
224
return new Shape .Attributes ( uri , targetNode , targetClass , targetObjectsOf , targetSubjectsOf , targetSparql , name , description , order , group ,
216
225
defaultValue , deactivated , message , severity , constraints );
217
226
}
218
227
219
228
private Shape .Node nodeShape ( final Resource shapeNode ) {
220
- final Shape .Attributes attributes = shapeAttributes ( shapeNode );
229
+ final Shape .Attributes attributes = shapeAttributes ( shapeNode , Optional . empty () );
221
230
final List <Shape .Property > properties = Streams .stream ( shapeNode .listProperties ( SHACL .property () ) )
222
231
.map ( Statement ::getResource )
223
232
.map ( this ::propertyShape ).toList ();
224
233
return new Shape .Node ( attributes , properties );
225
234
}
226
235
227
236
private Shape .Property propertyShape ( final Resource shapeNode ) {
228
- final Shape .Attributes attributes = shapeAttributes ( shapeNode );
229
237
final Path path = path ( shapeNode .getProperty ( SHACL .path () ).getResource () );
238
+ final Shape .Attributes attributes = shapeAttributes ( shapeNode , Optional .of ( path ) );
230
239
return new Shape .Property ( attributes , path );
231
240
}
232
241
@@ -282,12 +291,12 @@ private boolean isRdfList( final Resource resource ) {
282
291
|| resource .hasProperty ( RDF .rest ) || resource .hasProperty ( RDF .first );
283
292
}
284
293
285
- private List <Constraint > constraints ( final Resource valueNode ) {
294
+ private List <Constraint > constraints ( final Resource valueNode , final Optional < Path > path ) {
286
295
return constraintLoaders .entrySet ().stream ()
287
296
.flatMap ( entry -> {
288
297
try {
289
298
return Streams .stream ( valueNode .listProperties ( entry .getKey () ) )
290
- .map ( statement -> entry .getValue ().apply ( statement ) );
299
+ .map ( statement -> entry .getValue ().apply ( new ShapeContext ( statement , path ) ) );
291
300
} catch ( final Exception exception ) {
292
301
throw new RuntimeException ( "Could not load SHACL shape: Invalid use of " + entry .getKey () + " on " + valueNode );
293
302
}
0 commit comments