2
2
* Copyright (c) 2021 Robert Bosch Manufacturing Solutions GmbH
3
3
*
4
4
* See the AUTHORS file(s) distributed with this work for additional
5
- * information regarding authorship.
5
+ * information regarding authorship.
6
6
*
7
7
* This Source Code Form is subject to the terms of the Mozilla Public
8
8
* License, v. 2.0. If a copy of the MPL was not distributed with this
15
15
16
16
import java .io .FileNotFoundException ;
17
17
import java .util .Collections ;
18
+ import java .util .HashSet ;
19
+ import java .util .Map ;
18
20
import java .util .Set ;
21
+ import java .util .Stack ;
19
22
import java .util .function .Function ;
20
23
import java .util .stream .Collectors ;
21
24
import java .util .stream .Stream ;
22
25
23
26
import org .apache .jena .rdf .model .Model ;
24
27
import org .apache .jena .rdf .model .ModelFactory ;
25
- import org .apache .jena .rdf .model .Resource ;
26
- import org .apache .jena .rdf .model .ResourceFactory ;
27
- import org .apache .jena .rdf .model .Statement ;
28
+ import org .apache .jena .rdf .model .Property ;
29
+ import org .apache .jena .rdf .model .RDFNode ;
28
30
import org .apache .jena .vocabulary .RDF ;
31
+ import org .apache .jena .vocabulary .XSD ;
29
32
30
- import com .google .common .collect .Sets ;
31
33
import com .google .common .collect .Streams ;
32
34
33
35
import io .openmanufacturing .sds .aspectmodel .VersionNumber ;
34
36
import io .openmanufacturing .sds .aspectmodel .resolver .services .VersionedModel ;
35
37
import io .openmanufacturing .sds .aspectmodel .urn .AspectModelUrn ;
38
+ import io .openmanufacturing .sds .aspectmodel .urn .ElementType ;
36
39
import io .openmanufacturing .sds .aspectmodel .urn .UrnSyntaxException ;
37
40
import io .openmanufacturing .sds .aspectmodel .versionupdate .MigratorFactory ;
38
41
import io .openmanufacturing .sds .aspectmodel .versionupdate .MigratorService ;
39
42
import io .openmanufacturing .sds .aspectmodel .versionupdate .MigratorServiceLoader ;
40
- import io .vavr .Tuple ;
41
- import io .vavr .Tuple2 ;
42
- import io .vavr .Value ;
43
43
import io .vavr .control .Option ;
44
44
import io .vavr .control .Try ;
45
45
@@ -57,7 +57,7 @@ public class AspectModelResolver {
57
57
* @param model The RDF model
58
58
* @return The set of URNs
59
59
*/
60
- public static Set <AspectModelUrn > getAllUrnsInModel ( final Model model ) {
60
+ public static Set <String > getAllUrnsInModel ( final Model model ) {
61
61
return Streams .stream ( model .listStatements ().mapWith ( statement -> {
62
62
final Stream <String > subjectUri = statement .getSubject ().isURIResource () ?
63
63
Stream .of ( statement .getSubject ().getURI () ) : Stream .empty ();
@@ -66,26 +66,10 @@ public static Set<AspectModelUrn> getAllUrnsInModel( final Model model ) {
66
66
Stream .of ( statement .getObject ().asResource ().getURI () ) : Stream .empty ();
67
67
68
68
return Stream .of ( subjectUri , propertyUri , objectUri )
69
- .flatMap ( Function .identity () )
70
- .map ( AspectModelResolver ::getAspectModelUrn )
71
- .flatMap ( Value ::toJavaStream );
69
+ .flatMap ( Function .identity () );
72
70
} ) ).flatMap ( Function .identity () ).collect ( Collectors .toSet () );
73
71
}
74
72
75
- /**
76
- * Parses an Aspect (meta) model URN into an {@link AspectModelUrn}
77
- *
78
- * @param uri The Aspect (meta) model URN
79
- * @return The {@link AspectModelUrn} if parsing succeeds, an {@link UrnSyntaxException} otherwise
80
- */
81
- private static Try <AspectModelUrn > getAspectModelUrn ( final String uri ) {
82
- try {
83
- return Try .success ( AspectModelUrn .fromUrn ( uri ) );
84
- } catch ( final UrnSyntaxException exception ) {
85
- return Try .failure ( exception );
86
- }
87
- }
88
-
89
73
/**
90
74
* Method to resolve a given {@link AspectModelUrn} using a suitable {@link ResolutionStrategy}.
91
75
* This creates the closure (merged model) of all referenced models and the corresponding meta model.
@@ -95,14 +79,11 @@ private static Try<AspectModelUrn> getAspectModelUrn( final String uri ) {
95
79
* @return The resolved model on success
96
80
*/
97
81
public Try <VersionedModel > resolveAspectModel ( final ResolutionStrategy resolver , final AspectModelUrn input ) {
98
-
99
- final Tuple2 <Try <Model >, Set <AspectModelUrn >> resolutionResult =
100
- resolve ( ModelFactory .createDefaultModel (), input , resolver , Set .of () );
101
- final Try <Model > mergedModel = resolutionResult ._1 ();
82
+ final Try <Model > mergedModel = resolve ( input .toString (), resolver );
102
83
103
84
if ( mergedModel .isFailure () ) {
104
85
if ( mergedModel .getCause () instanceof FileNotFoundException ) {
105
- return Try .failure ( new ModelResolutionException ( mergedModel .getCause () ) );
86
+ return Try .failure ( new ModelResolutionException ( "While trying to resolve " + input + ": " + mergedModel .getCause () ) );
106
87
}
107
88
return Try .failure ( mergedModel .getCause () );
108
89
}
@@ -112,7 +93,7 @@ public Try<VersionedModel> resolveAspectModel( final ResolutionStrategy resolver
112
93
113
94
final Set <VersionNumber > usedMetaModelVersions =
114
95
mergedModel .map ( resourceResolver ::getUsedMetaModelVersions )
115
- .getOrElse ( Collections .emptySet () );
96
+ .getOrElse ( Collections .emptySet () );
116
97
117
98
if ( usedMetaModelVersions .isEmpty () ) {
118
99
return Try .failure ( new ModelResolutionException ( "Could not determine used meta model version" ) );
@@ -121,7 +102,7 @@ public Try<VersionedModel> resolveAspectModel( final ResolutionStrategy resolver
121
102
if ( usedMetaModelVersions .size () == 1 && migratorService .getMigratorFactory ().isEmpty () ) {
122
103
return mergedModel .flatMap ( model ->
123
104
migratorService .getSdsMigratorFactory ().createAspectMetaModelResourceResolver ()
124
- .mergeMetaModelIntoRawModel ( model , usedMetaModelVersions .iterator ().next () ) );
105
+ .mergeMetaModelIntoRawModel ( model , usedMetaModelVersions .iterator ().next () ) );
125
106
}
126
107
127
108
final Try <VersionNumber > oldestVersion =
@@ -130,85 +111,116 @@ public Try<VersionedModel> resolveAspectModel( final ResolutionStrategy resolver
130
111
return mergedModel .flatMap ( model ->
131
112
oldestVersion .flatMap ( oldest ->
132
113
migratorService .getSdsMigratorFactory ()
133
- .createAspectMetaModelResourceResolver ()
134
- .mergeMetaModelIntoRawModel ( model , oldest )
135
- .orElse ( () -> migratorService .getMigratorFactory ()
136
- .map ( MigratorFactory ::createAspectMetaModelResourceResolver )
137
- .map ( Try ::success )
138
- .orElseThrow ()
139
- .flatMap ( metaResolver -> metaResolver .mergeMetaModelIntoRawModel ( model , oldest ) ) )
140
- .flatMap ( migratorService ::updateMetaModelVersion ) ) );
114
+ .createAspectMetaModelResourceResolver ()
115
+ .mergeMetaModelIntoRawModel ( model , oldest )
116
+ .orElse ( () -> migratorService .getMigratorFactory ()
117
+ .map ( MigratorFactory ::createAspectMetaModelResourceResolver )
118
+ .map ( Try ::success )
119
+ .orElseThrow ()
120
+ .flatMap ( metaResolver -> metaResolver .mergeMetaModelIntoRawModel ( model , oldest ) ) )
121
+ .flatMap ( migratorService ::updateMetaModelVersion ) ) );
141
122
}
142
123
143
124
/**
144
- * Checks if a given model contains the definition of a model element. This is determined by checking whether the
145
- * statement "modelElement rdf:type *" or "modelElement bamm:refines *" exists.
125
+ * Checks if a given model contains the definition of a model element.
146
126
*
147
127
* @param model the model
148
128
* @param urn the URN of the model element
149
129
* @return true if the model contains the definition of the model element
150
130
*/
151
131
public static boolean containsDefinition ( final Model model , final AspectModelUrn urn ) {
152
- return Streams .stream ( model .listStatements () )
153
- .anyMatch ( statement -> isBammElementDefinition ( urn , statement ) );
132
+ return model .contains ( model .createResource ( urn .toString () ), RDF .type , (RDFNode ) null );
154
133
}
155
134
156
- private static boolean isBammElementDefinition ( final AspectModelUrn urn , final Statement statement ) {
157
- final Resource resource = ResourceFactory .createResource ( urn .getUrn ().toString () );
158
- final String bammRefinesRegex = ".*\\ bmeta-model:\\ b.*\\ b#refines" ;
159
- return resource .equals ( statement .getSubject () ) &&
160
- (RDF .type .equals ( statement .getPredicate () ) ||
161
- statement .getPredicate ().toString ().matches ( bammRefinesRegex ));
162
- }
135
+ /**
136
+ * The main model resolution method that takes an Aspect Model element URN and a resolution strategy as input.
137
+ * The strategy is applied to the URN to load a model, and then repeated for all URNs in the loaded model that
138
+ * have not yet been loaded.
139
+ * @param urn the Aspect Model element URN
140
+ * @param resolutionStrategy the resolution strategy that knowns how to turn a URN into a Model
141
+ * @return the fully resolved model, or a failure if one of the transitively referenced elements can't be found
142
+ */
143
+ private Try <Model > resolve ( final String urn , final ResolutionStrategy resolutionStrategy ) {
144
+ final Model result = ModelFactory .createDefaultModel ();
145
+ final Stack <String > unresolvedUrns = new Stack <>();
146
+ final Set <Model > mergedModels = new HashSet <>();
147
+ unresolvedUrns .push ( urn );
148
+
149
+ while ( !unresolvedUrns .isEmpty () ) {
150
+ final String urnToResolve = unresolvedUrns .pop ();
151
+ final Try <Model > resolvedModel = getModelForUrn ( urnToResolve , resolutionStrategy );
152
+ if ( resolvedModel .isFailure () ) {
153
+ return resolvedModel ;
154
+ }
155
+ final Model model = resolvedModel .get ();
156
+ final Property refines = model .createProperty ( "urn:bamm:io.openmanufacturing:meta-model:1.0.0#refines" );
157
+
158
+ // Merge the resolved model into the target if it was not already merged before.
159
+ // It could have been merged before when the model contains another model definition that was already resolved
160
+ if ( !mergedModels .contains ( model ) ) {
161
+ mergeModels ( result , model );
162
+ mergedModels .add ( model );
163
+ }
164
+ for ( final String element : getAllUrnsInModel ( model ) ) {
165
+ if ( !result .contains ( model .createResource ( element ), RDF .type , (RDFNode ) null )
166
+ // Backwards compatibility with BAMM 1.0.0
167
+ && !result .contains ( model .createResource ( element ), refines , (RDFNode ) null )
168
+ && !unresolvedUrns .contains ( element ) ) {
169
+ unresolvedUrns .push ( element );
170
+ }
171
+ }
172
+ }
163
173
164
- private Tuple2 <Try <Model >, Set <AspectModelUrn >> resolve ( final Model targetModel ,
165
- final AspectModelUrn urn ,
166
- final ResolutionStrategy resolver , final Set <AspectModelUrn > resolvedUrns ) {
174
+ return Try .success ( result );
175
+ }
167
176
168
- if ( resolvedUrns .contains ( urn ) ) {
169
- return Tuple .of ( Try .success ( targetModel ), resolvedUrns );
170
- }
177
+ private final Model EMPTY_MODEL = ModelFactory .createDefaultModel ();
171
178
172
- final Set <AspectModelUrn > updatedResolvedUrns = Sets .union ( resolvedUrns , Set .of ( urn ) );
173
- if ( containsDefinition ( targetModel , urn ) ) {
174
- return Tuple .of ( Try .success ( targetModel ), updatedResolvedUrns );
179
+ /**
180
+ * Applies a {@link ResolutionStrategy} to a URI to be resolved, but only if the URI is actually a valid {@link AspectModelUrn}.
181
+ * For meta model elements or other URIs, an empty model is returned. This method returns only a failure, when the used resolution
182
+ * strategy fails.
183
+ * @param urn the URN to resolve
184
+ * @param resolutionStrategy the resolution strategy to apply
185
+ * @return the model containing the defintion of the given model element
186
+ */
187
+ private Try <Model > getModelForUrn ( final String urn , final ResolutionStrategy resolutionStrategy ) {
188
+ if ( urn .startsWith ( RDF .getURI () ) || urn .startsWith ( XSD .getURI () ) ) {
189
+ return Try .success ( EMPTY_MODEL );
175
190
}
176
191
177
- final Try <Model > loadedModel = resolver .apply ( urn );
178
- if ( loadedModel .isFailure () ) {
179
- return Tuple .of ( loadedModel , updatedResolvedUrns );
192
+ try {
193
+ final AspectModelUrn aspectModelUrn = AspectModelUrn .fromUrn ( urn );
194
+ if ( aspectModelUrn .getElementType () != ElementType .NONE ) {
195
+ return Try .success ( EMPTY_MODEL );
196
+ }
197
+ return resolutionStrategy .apply ( aspectModelUrn ).flatMap ( model -> {
198
+ if ( !model .contains ( model .createResource ( urn ), RDF .type , (RDFNode ) null ) ) {
199
+ return Try .failure ( new ModelResolutionException ( "Resolution strategy returned a model which does contain element definition for " + urn ) );
200
+ }
201
+ return Try .success ( model );
202
+ } );
203
+ } catch ( final UrnSyntaxException e ) {
204
+ // If it's no valid Aspect Model URN but some other URI (e.g., a bamm:see value), there is nothing
205
+ // to resolve, so we return just an empty model
206
+ return Try .success ( EMPTY_MODEL );
180
207
}
181
-
182
- addModelWithoutOverwritingEmptyPrefix ( targetModel , loadedModel .get () );
183
- return getAllUrnsInModel ( loadedModel .get () )
184
- .stream ()
185
- .filter ( modelUrn -> !modelUrn .isBammUrn () )
186
- .reduce ( Tuple .of ( Try .success ( targetModel ), updatedResolvedUrns ),
187
- (( tuple , aspectModelUrn ) ->
188
- resolve ( targetModel , aspectModelUrn , resolver , updatedResolvedUrns )),
189
- ( tuple , tuple2 ) -> {
190
- final Try <Model > mergedModels = tuple ._1 ().flatMap ( model ->
191
- tuple2 ._1 ().map ( model2 -> addModelWithoutOverwritingEmptyPrefix ( model , model2 ) ) );
192
- final Set <AspectModelUrn > mergedSets = Sets .union ( tuple ._2 (), tuple2 ._2 () );
193
- return Tuple .of ( mergedModels , mergedSets );
194
- } );
195
208
}
196
209
197
- private Model addModelWithoutOverwritingEmptyPrefix ( final Model target , final Model modelToAdd ) {
198
- if ( !target .getNsPrefixMap ().containsKey ( "" ) && modelToAdd .getNsPrefixMap ().containsKey ( "" ) ) {
199
- target .setNsPrefix ( "" , modelToAdd .getNsPrefixURI ( "" ) );
210
+ /**
211
+ * Merge a model into an existing target model. Prefixes are only added when they are not already present, i.e.,
212
+ * a model won't overwrite the empty prefix of the target model.
213
+ *
214
+ * @param target the model to merge into
215
+ * @param other the model to be merged
216
+ */
217
+ private void mergeModels ( final Model target , final Model other ) {
218
+ for ( final Map .Entry <String , String > prefixEntry : other .getNsPrefixMap ().entrySet () ) {
219
+ if ( !target .getNsPrefixMap ().containsKey ( prefixEntry .getKey () ) ) {
220
+ target .setNsPrefix ( prefixEntry .getKey (), prefixEntry .getValue () );
221
+ }
200
222
}
201
223
202
- modelToAdd .getNsPrefixMap ().entrySet ().stream ().filter ( entry -> !"" .equals ( entry .getKey () ) )
203
- .forEach ( entry -> target .setNsPrefix ( entry .getKey (), entry .getValue () ) );
204
-
205
- migratorService .getMigratorFactory ().map ( MigratorFactory ::createAspectMetaModelResourceResolver ).stream ()
206
- .flatMap (
207
- aspectMetaModelResourceResolver -> aspectMetaModelResourceResolver
208
- .listAspectStatements ( modelToAdd , target ) ).forEach ( target ::add );
209
-
210
- migratorService .getSdsMigratorFactory ().createAspectMetaModelResourceResolver ()
211
- .listAspectStatements ( modelToAdd , target ).forEach ( target ::add );
212
- return target ;
224
+ other .listStatements ().forEach ( target ::add );
213
225
}
214
226
}
0 commit comments