Skip to content

Commit 2a7c294

Browse files
authored
Merge pull request #205 from bci-oss/202-fix-model-resolution
Fix model resolution
2 parents 44c22b5 + 5d131d4 commit 2a7c294

File tree

17 files changed

+196
-206
lines changed

17 files changed

+196
-206
lines changed

core/sds-aspect-model-resolver/src/main/java/io/openmanufacturing/sds/aspectmodel/resolver/AspectModelResolver.java

Lines changed: 104 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright (c) 2021 Robert Bosch Manufacturing Solutions GmbH
33
*
44
* See the AUTHORS file(s) distributed with this work for additional
5-
* information regarding authorship.
5+
* information regarding authorship.
66
*
77
* This Source Code Form is subject to the terms of the Mozilla Public
88
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -15,31 +15,31 @@
1515

1616
import java.io.FileNotFoundException;
1717
import java.util.Collections;
18+
import java.util.HashSet;
19+
import java.util.Map;
1820
import java.util.Set;
21+
import java.util.Stack;
1922
import java.util.function.Function;
2023
import java.util.stream.Collectors;
2124
import java.util.stream.Stream;
2225

2326
import org.apache.jena.rdf.model.Model;
2427
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;
2830
import org.apache.jena.vocabulary.RDF;
31+
import org.apache.jena.vocabulary.XSD;
2932

30-
import com.google.common.collect.Sets;
3133
import com.google.common.collect.Streams;
3234

3335
import io.openmanufacturing.sds.aspectmodel.VersionNumber;
3436
import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;
3537
import io.openmanufacturing.sds.aspectmodel.urn.AspectModelUrn;
38+
import io.openmanufacturing.sds.aspectmodel.urn.ElementType;
3639
import io.openmanufacturing.sds.aspectmodel.urn.UrnSyntaxException;
3740
import io.openmanufacturing.sds.aspectmodel.versionupdate.MigratorFactory;
3841
import io.openmanufacturing.sds.aspectmodel.versionupdate.MigratorService;
3942
import io.openmanufacturing.sds.aspectmodel.versionupdate.MigratorServiceLoader;
40-
import io.vavr.Tuple;
41-
import io.vavr.Tuple2;
42-
import io.vavr.Value;
4343
import io.vavr.control.Option;
4444
import io.vavr.control.Try;
4545

@@ -57,7 +57,7 @@ public class AspectModelResolver {
5757
* @param model The RDF model
5858
* @return The set of URNs
5959
*/
60-
public static Set<AspectModelUrn> getAllUrnsInModel( final Model model ) {
60+
public static Set<String> getAllUrnsInModel( final Model model ) {
6161
return Streams.stream( model.listStatements().mapWith( statement -> {
6262
final Stream<String> subjectUri = statement.getSubject().isURIResource() ?
6363
Stream.of( statement.getSubject().getURI() ) : Stream.empty();
@@ -66,26 +66,10 @@ public static Set<AspectModelUrn> getAllUrnsInModel( final Model model ) {
6666
Stream.of( statement.getObject().asResource().getURI() ) : Stream.empty();
6767

6868
return Stream.of( subjectUri, propertyUri, objectUri )
69-
.flatMap( Function.identity() )
70-
.map( AspectModelResolver::getAspectModelUrn )
71-
.flatMap( Value::toJavaStream );
69+
.flatMap( Function.identity() );
7270
} ) ).flatMap( Function.identity() ).collect( Collectors.toSet() );
7371
}
7472

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-
8973
/**
9074
* Method to resolve a given {@link AspectModelUrn} using a suitable {@link ResolutionStrategy}.
9175
* 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 ) {
9579
* @return The resolved model on success
9680
*/
9781
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 );
10283

10384
if ( mergedModel.isFailure() ) {
10485
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() ) );
10687
}
10788
return Try.failure( mergedModel.getCause() );
10889
}
@@ -112,7 +93,7 @@ public Try<VersionedModel> resolveAspectModel( final ResolutionStrategy resolver
11293

11394
final Set<VersionNumber> usedMetaModelVersions =
11495
mergedModel.map( resourceResolver::getUsedMetaModelVersions )
115-
.getOrElse( Collections.emptySet() );
96+
.getOrElse( Collections.emptySet() );
11697

11798
if ( usedMetaModelVersions.isEmpty() ) {
11899
return Try.failure( new ModelResolutionException( "Could not determine used meta model version" ) );
@@ -121,7 +102,7 @@ public Try<VersionedModel> resolveAspectModel( final ResolutionStrategy resolver
121102
if ( usedMetaModelVersions.size() == 1 && migratorService.getMigratorFactory().isEmpty() ) {
122103
return mergedModel.flatMap( model ->
123104
migratorService.getSdsMigratorFactory().createAspectMetaModelResourceResolver()
124-
.mergeMetaModelIntoRawModel( model, usedMetaModelVersions.iterator().next() ) );
105+
.mergeMetaModelIntoRawModel( model, usedMetaModelVersions.iterator().next() ) );
125106
}
126107

127108
final Try<VersionNumber> oldestVersion =
@@ -130,85 +111,116 @@ public Try<VersionedModel> resolveAspectModel( final ResolutionStrategy resolver
130111
return mergedModel.flatMap( model ->
131112
oldestVersion.flatMap( oldest ->
132113
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 ) ) );
141122
}
142123

143124
/**
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.
146126
*
147127
* @param model the model
148128
* @param urn the URN of the model element
149129
* @return true if the model contains the definition of the model element
150130
*/
151131
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 );
154133
}
155134

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+
}
163173

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+
}
167176

168-
if ( resolvedUrns.contains( urn ) ) {
169-
return Tuple.of( Try.success( targetModel ), resolvedUrns );
170-
}
177+
private final Model EMPTY_MODEL = ModelFactory.createDefaultModel();
171178

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 );
175190
}
176191

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 );
180207
}
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-
} );
195208
}
196209

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+
}
200222
}
201223

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 );
213225
}
214226
}

core/sds-aspect-model-resolver/src/main/java/io/openmanufacturing/sds/aspectmodel/resolver/ClasspathStrategy.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,6 @@ public Try<Model> apply( final AspectModelUrn aspectModelUrn ) {
173173
.getOrElse( false ) )
174174
.findFirst()
175175
.orElse( Try.failure( new FileNotFoundException(
176-
"The AspectModel: " + aspectModelUrn + " could not be found in directory: " + directory ) ) );
176+
"The model file " + aspectModelUrn + " could not be found in directory: " + directory ) ) );
177177
}
178178
}

core/sds-aspect-model-resolver/src/main/java/io/openmanufacturing/sds/aspectmodel/resolver/FileSystemStrategy.java

Lines changed: 12 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
* Copyright (c) 2021 Robert Bosch Manufacturing Solutions GmbH
33
*
44
* See the AUTHORS file(s) distributed with this work for additional
5-
* information regarding authorship.
5+
* information regarding authorship.
66
*
77
* This Source Code Form is subject to the terms of the Mozilla Public
88
* License, v. 2.0. If a copy of the MPL was not distributed with this
@@ -80,17 +80,16 @@ public Try<Model> apply( final AspectModelUrn aspectModelUrn ) {
8080
aspectModelUrn.getName(), directory );
8181

8282
return Arrays.stream( Optional.ofNullable( directory.toFile().listFiles() ).orElse( new File[] {} ) )
83-
.filter( File::isFile )
84-
.filter( file -> file.getName().endsWith( ".ttl" ) )
85-
.map( File::toURI )
86-
.sorted()
87-
.map( this::loadFromUri )
88-
.filter( tryModel -> tryModel
89-
.map( model -> AspectModelResolver.containsDefinition( model, aspectModelUrn ) )
90-
.getOrElse( false ) )
91-
.findFirst()
92-
.orElse( Try.failure( new FileNotFoundException(
93-
"The AspectModel: " + aspectModelUrn.toString() + " could not be found in directory: "
94-
+ directory ) ) );
83+
.filter( File::isFile )
84+
.filter( file -> file.getName().endsWith( ".ttl" ) )
85+
.map( File::toURI )
86+
.sorted()
87+
.map( this::loadFromUri )
88+
.filter( tryModel -> tryModel
89+
.map( model -> AspectModelResolver.containsDefinition( model, aspectModelUrn ) )
90+
.getOrElse( false ) )
91+
.findFirst()
92+
.orElse( Try.failure( new FileNotFoundException(
93+
"The model file " + aspectModelUrn.toString() + " could not be found in directory: " + directory ) ) );
9594
}
9695
}

0 commit comments

Comments
 (0)