Skip to content

Commit 3538f03

Browse files
authored
Merge pull request #509 from bci-oss/508-fix-resolution-in-samm-cli-native
Fix resolution of model elements from other namespaces in native
2 parents 679fdd2 + d5cc258 commit 3538f03

File tree

11 files changed

+192
-167
lines changed

11 files changed

+192
-167
lines changed

core/esmf-aspect-model-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelResolver.java

+46-51
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313

1414
package org.eclipse.esmf.aspectmodel.resolver;
1515

16-
import static io.vavr.API.*;
16+
import static io.vavr.API.$;
17+
import static io.vavr.API.Case;
1718
import static io.vavr.Predicates.instanceOf;
1819

1920
import java.io.ByteArrayInputStream;
@@ -36,15 +37,9 @@
3637
import java.util.stream.Collectors;
3738
import java.util.stream.Stream;
3839

39-
import org.apache.commons.io.FilenameUtils;
40-
import org.apache.jena.rdf.model.Model;
41-
import org.apache.jena.rdf.model.ModelFactory;
42-
import org.apache.jena.rdf.model.Property;
43-
import org.apache.jena.rdf.model.RDFNode;
44-
import org.apache.jena.rdf.model.Resource;
45-
import org.apache.jena.vocabulary.RDF;
46-
import org.apache.jena.vocabulary.XSD;
4740
import org.eclipse.esmf.aspectmodel.VersionNumber;
41+
import org.eclipse.esmf.aspectmodel.resolver.fs.FlatModelsRoot;
42+
import org.eclipse.esmf.aspectmodel.resolver.fs.StructuredModelsRoot;
4843
import org.eclipse.esmf.aspectmodel.resolver.services.SammAspectMetaModelResourceResolver;
4944
import org.eclipse.esmf.aspectmodel.resolver.services.TurtleLoader;
5045
import org.eclipse.esmf.aspectmodel.resolver.services.VersionedModel;
@@ -58,17 +53,27 @@
5853
import org.eclipse.esmf.samm.KnownVersion;
5954

6055
import com.google.common.collect.Streams;
61-
6256
import io.vavr.CheckedFunction1;
6357
import io.vavr.Value;
6458
import io.vavr.control.Option;
6559
import io.vavr.control.Try;
60+
import org.apache.commons.io.FilenameUtils;
61+
import org.apache.jena.rdf.model.Model;
62+
import org.apache.jena.rdf.model.ModelFactory;
63+
import org.apache.jena.rdf.model.Property;
64+
import org.apache.jena.rdf.model.RDFNode;
65+
import org.apache.jena.rdf.model.Resource;
66+
import org.apache.jena.vocabulary.RDF;
67+
import org.apache.jena.vocabulary.XSD;
68+
import org.slf4j.Logger;
69+
import org.slf4j.LoggerFactory;
6670

6771
/**
6872
* Provides facilities for loading an Aspect model and resolving referenced meta model elements and
6973
* model elements from other Aspect models
7074
*/
7175
public class AspectModelResolver {
76+
private static final Logger LOG = LoggerFactory.getLogger( AspectModelResolver.class );
7277

7378
private final MigratorService migratorService = MigratorServiceLoader.getInstance().getMigratorService();
7479
private final BammUriRewriter bamm100UriRewriter = new BammUriRewriter( BammUriRewriter.BAMM_VERSION.BAMM_1_0_0 );
@@ -130,9 +135,7 @@ public Try<VersionedModel> resolveAspectModel( final ResolutionStrategy resoluti
130135
* @return the resolved model on success
131136
*/
132137
public Try<VersionedModel> resolveAspectModel( final ResolutionStrategy resolutionStrategy, final InputStream inputStream ) {
133-
return TurtleLoader.loadTurtle( inputStream )
134-
.flatMap( model -> resolveAspectModel( FileSystemStrategy.DefaultNamespace.withDefaultNamespace( resolutionStrategy, model ),
135-
model ) );
138+
return TurtleLoader.loadTurtle( inputStream ).flatMap( model -> resolveAspectModel( resolutionStrategy, model ) );
136139
}
137140

138141
/**
@@ -185,8 +188,16 @@ public Try<VersionedModel> resolveAspectModel( final Model initialModel, final R
185188
.map( bamm200UriRewriter::migrate );
186189

187190
if ( mergedModel.isFailure() ) {
188-
if ( mergedModel.getCause() instanceof FileNotFoundException ) {
189-
return Try.failure( new ModelResolutionException( "Could not resolve " + input, mergedModel.getCause() ) );
191+
if ( mergedModel.getCause() instanceof final FileNotFoundException fileNotFoundException ) {
192+
final String failedUrns = input.stream()
193+
.filter( urn -> !urn.getElementType().equals( ElementType.META_MODEL ) )
194+
.filter( urn -> !urn.getElementType().equals( ElementType.CHARACTERISTIC ) )
195+
.filter( urn -> !urn.getElementType().equals( ElementType.ENTITY ) )
196+
.filter( urn -> !urn.getElementType().equals( ElementType.UNIT ) )
197+
.map( AspectModelUrn::toString )
198+
.collect( Collectors.joining( ", " ) );
199+
LOG.debug( "Could not resolve {}", failedUrns, fileNotFoundException );
200+
return Try.failure( new ModelResolutionException( "Could not resolve " + failedUrns, fileNotFoundException ) );
190201
}
191202
return Try.failure( mergedModel.getCause() );
192203
}
@@ -236,9 +247,14 @@ public Try<VersionedModel> resolveAspectModel( final Model initialModel, final R
236247
*/
237248
public static boolean containsDefinition( final Model model, final AspectModelUrn urn ) {
238249
if ( model.getNsPrefixMap().values().stream().anyMatch( prefixUri -> prefixUri.startsWith( "urn:bamm:" ) ) ) {
239-
return model.contains( model.createResource( urn.toString().replace( "urn:samm:", "urn:bamm:" ) ), RDF.type, (RDFNode) null );
250+
final boolean result = model.contains( model.createResource( urn.toString().replace( "urn:samm:", "urn:bamm:" ) ), RDF.type,
251+
(RDFNode) null );
252+
LOG.debug( "Checking if model contains {}: {}", urn, result );
253+
return result;
240254
}
241-
return model.contains( model.createResource( urn.toString() ), RDF.type, (RDFNode) null );
255+
final boolean result = model.contains( model.createResource( urn.toString() ), RDF.type, (RDFNode) null );
256+
LOG.debug( "Checking if model contains {}: {}", urn, result );
257+
return result;
242258
}
243259

244260
/**
@@ -262,6 +278,7 @@ private Try<Model> resolve( final Model result, final List<AspectModelUrn> urns,
262278
final String urnToResolve = unresolvedUrns.pop();
263279
final Try<Model> resolvedModel = getModelForUrn( urnToResolve, resolutionStrategy );
264280
if ( resolvedModel.isFailure() ) {
281+
LOG.debug( "Tried to resolve {} using {}, but it failed", urnToResolve, resolutionStrategy );
265282
return resolvedModel;
266283
}
267284
final Model model = resolvedModel.get();
@@ -399,40 +416,18 @@ private void mergeModels( final Model target, final Model other ) {
399416
* @return the resolved model on success
400417
*/
401418
public static Try<VersionedModel> loadAndResolveModel( final File input ) {
402-
return loadAndResolveModelFromUrnLikeDir( input )
403-
.orElse( () -> loadAndResolveModelFromDir( input ) );
404-
}
405-
406-
private static Try<VersionedModel> loadAndResolveModelFromUrnLikeDir( final File input ) {
407-
final AspectModelResolver resolver = new AspectModelResolver();
408-
final File inputFile = input.getAbsoluteFile();
409-
final Try<AspectModelUrn> urnTry = fileToUrn( inputFile );
410-
final Try<FileSystemStrategy> strategyTry = getModelRoot( inputFile ).map( FileSystemStrategy::new );
411-
//noinspection unchecked
412-
return urnTry
413-
.flatMap( urn -> strategyTry
414-
.flatMap( strategy -> resolver.resolveAspectModel( strategy, urn ) )
415-
.mapFailure(
416-
Case( $( instanceOf( IOException.class ) ),
417-
e -> new ModelResolutionException( "Could not load model " + urn + " from file " + input, e ) )
418-
)
419-
);
420-
}
421-
422-
private static Try<VersionedModel> loadAndResolveModelFromDir( final File input ) {
423419
final AspectModelResolver resolver = new AspectModelResolver();
424420
final File inputFile = input.getAbsoluteFile();
425-
final Try<Path> modelsRoot = Try.of( () -> inputFile.getParentFile().toPath() );
426-
final Try<FileSystemStrategy> strategyTry = modelsRoot.map( FileSystemStrategy.DefaultNamespace::new );
427-
428-
//noinspection unchecked
429-
return strategyTry
430-
.flatMapTry( strategy -> Try
431-
.withResources( () -> new FileInputStream( input ) )
432-
.of( stream -> resolver.resolveAspectModel( strategy, stream ) ) )
433-
.flatMap( Function.identity() )
434-
.mapFailure( Case( $( instanceOf( IOException.class ) ),
435-
e -> new ModelResolutionException( "Could not open file " + input, e ) ) );
421+
final ResolutionStrategy fromSameDirectory = new FileSystemStrategy( new FlatModelsRoot( inputFile.getParentFile().toPath() ) );
422+
423+
// Construct the resolution strategy: Models should be searched in the structured folder (if it exists) and then in the
424+
// same directory. If the structured folder can not be resolved, directly search in the same directory.
425+
final ResolutionStrategy resolutionStrategy = getModelRoot( inputFile ).map(
426+
modelsRoot -> new FileSystemStrategy( new StructuredModelsRoot( modelsRoot ) ) )
427+
.<ResolutionStrategy> map( structured -> new EitherStrategy( structured, fromSameDirectory ) ).getOrElse( fromSameDirectory );
428+
return Try.withResources( () -> new FileInputStream( input ) )
429+
.of( stream -> resolver.resolveAspectModel( resolutionStrategy, stream ) )
430+
.flatMap( Function.identity() );
436431
}
437432

438433
/**
@@ -488,9 +483,9 @@ public static Try<AspectModelUrn> fileToUrn( final File inputFile ) {
488483
* @param file the input model file
489484
* @return the URN of the model element that corresponds to the file name and its location inside the models root
490485
*/
491-
public static AspectModelUrn urnFromModel(final VersionedModel model, final File file) {
486+
public static AspectModelUrn urnFromModel( final VersionedModel model, final File file ) {
492487
final String aspectName = FilenameUtils.removeExtension( file.getName() );
493-
return Streams.stream( model.getRawModel().listSubjects()).filter( s-> aspectName.equals( s.getLocalName() ) )
488+
return Streams.stream( model.getRawModel().listSubjects() ).filter( s -> aspectName.equals( s.getLocalName() ) )
494489
.findFirst()
495490
.map( Resource::getURI )
496491
.map( AspectModelUrn::fromUrn )

core/esmf-aspect-model-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/EitherStrategy.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,10 @@
1313

1414
package org.eclipse.esmf.aspectmodel.resolver;
1515

16-
import org.apache.jena.rdf.model.Model;
1716
import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;
1817

1918
import io.vavr.control.Try;
19+
import org.apache.jena.rdf.model.Model;
2020

2121
/**
2222
* A Resolution strategy that supports two types of inputs and wraps two dedicated sub-resolution strategies
@@ -39,4 +39,9 @@ public Try<Model> apply( final AspectModelUrn input ) {
3939
}
4040
return strategy2.apply( input );
4141
}
42+
43+
@Override
44+
public String toString() {
45+
return "EitherStrategy(strategy1=" + strategy1 + ", strategy2=" + strategy2 + ")";
46+
}
4247
}

core/esmf-aspect-model-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/FileSystemStrategy.java

+34-70
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,17 @@
1313

1414
package org.eclipse.esmf.aspectmodel.resolver;
1515

16-
import static io.vavr.API.$;
17-
import static io.vavr.API.Case;
18-
import static io.vavr.Predicates.instanceOf;
19-
2016
import java.io.File;
2117
import java.io.FileNotFoundException;
2218
import java.net.MalformedURLException;
2319
import java.nio.file.Path;
2420
import java.util.Arrays;
2521
import java.util.Optional;
2622

23+
import org.eclipse.esmf.aspectmodel.resolver.fs.ModelsRoot;
24+
import org.eclipse.esmf.aspectmodel.resolver.fs.StructuredModelsRoot;
2725
import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;
2826

29-
import fs.ModelsRoot;
3027
import io.vavr.control.Try;
3128
import org.apache.jena.rdf.model.Model;
3229
import org.slf4j.Logger;
@@ -58,7 +55,16 @@ public class FileSystemStrategy extends AbstractResolutionStrategy {
5855
* @param modelsRoot The root directory for model files
5956
*/
6057
public FileSystemStrategy( final Path modelsRoot ) {
61-
this.modelsRoot = new ModelsRoot( modelsRoot );
58+
this( new StructuredModelsRoot( modelsRoot ) );
59+
}
60+
61+
/**
62+
* Initialize the FileSystemStrategy with the root path of models.
63+
*
64+
* @param modelsRoot The root directory for model files
65+
*/
66+
public FileSystemStrategy( final ModelsRoot modelsRoot ) {
67+
this.modelsRoot = modelsRoot;
6268
}
6369

6470
/**
@@ -73,11 +79,7 @@ public FileSystemStrategy( final Path modelsRoot ) {
7379
*/
7480
@Override
7581
public Try<Model> apply( final AspectModelUrn aspectModelUrn ) {
76-
final Path directory = resolve( aspectModelUrn );
77-
return loadFromDirectory( aspectModelUrn, directory );
78-
}
79-
80-
protected Try<Model> loadFromDirectory( final AspectModelUrn aspectModelUrn, final Path directory ) {
82+
final Path directory = modelsRoot.directoryForNamespace( aspectModelUrn );
8183
final File namedResourceFile = directory.resolve( aspectModelUrn.getName() + ".ttl" ).toFile();
8284
if ( namedResourceFile.exists() ) {
8385
return loadFromUri( namedResourceFile.toURI() );
@@ -86,70 +88,32 @@ protected Try<Model> loadFromDirectory( final AspectModelUrn aspectModelUrn, fin
8688
LOG.warn( "Looking for {}, but no {}.ttl was found. Inspecting files in {}", aspectModelUrn.getName(),
8789
aspectModelUrn.getName(), directory );
8890

89-
return Arrays.stream( Optional.ofNullable( directory.toFile().listFiles() ).orElse( new File[] {} ) )
90-
.filter( File::isFile )
91-
.filter( file -> file.getName().endsWith( ".ttl" ) )
92-
.map( File::toURI )
93-
.sorted()
94-
.map( this::loadFromUri )
95-
.filter( tryModel -> tryModel
96-
.map( model -> AspectModelResolver.containsDefinition( model, aspectModelUrn ) )
97-
.getOrElse( false ) )
98-
.findFirst()
99-
.orElse( Try.failure( new FileNotFoundException(
100-
"No model file containing " + aspectModelUrn + " could be found in directory: " + directory ) ) );
101-
}
91+
final File[] files = Optional.ofNullable( directory.toFile().listFiles() ).orElse( new File[] {} );
92+
Arrays.sort( files );
10293

103-
protected Path resolve( final AspectModelUrn aspectModelUrn ) {
104-
return modelsRoot.directoryForNamespace( aspectModelUrn );
94+
for ( final File file : files ) {
95+
if ( !file.isFile() || !file.getName().endsWith( ".ttl" ) ) {
96+
continue;
97+
}
98+
LOG.debug( "Looking for {} in {}", aspectModelUrn, file );
99+
final Try<Model> tryModel = loadFromUri( file.toURI() );
100+
if ( tryModel.isFailure() ) {
101+
LOG.debug( "Could not load model from {}", file, tryModel.getCause() );
102+
} else {
103+
final Model model = tryModel.get();
104+
if ( AspectModelResolver.containsDefinition( model, aspectModelUrn ) ) {
105+
return Try.success( model );
106+
} else {
107+
LOG.debug( "File {} does not contain {}", file, aspectModelUrn );
108+
}
109+
}
110+
}
111+
return Try.failure(
112+
new FileNotFoundException( "No model file containing " + aspectModelUrn + " could be found in directory: " + directory ) );
105113
}
106114

107115
@Override
108116
public String toString() {
109117
return "FileSystemStrategy(root=" + modelsRoot + ')';
110118
}
111-
112-
/**
113-
* Initialize the File System Strategy where the default namespace is resolved in the root folder.
114-
*/
115-
public static class DefaultNamespace extends FileSystemStrategy {
116-
AspectModelUrn defaultUrn;
117-
118-
public DefaultNamespace( final Path modelsRoot ) {
119-
super( modelsRoot );
120-
}
121-
122-
@Override
123-
protected Path resolve( final AspectModelUrn urn ) {
124-
return (urn.getNamespace().equals( defaultUrn.getNamespace() ) && urn.getVersion().equals( defaultUrn.getVersion() ))
125-
? modelsRoot.rootPath()
126-
: super.resolve( urn );
127-
}
128-
129-
@Override
130-
public Try<Model> apply( final AspectModelUrn urn ) {
131-
final Path directory = resolve( urn );
132-
return loadFromDirectory( urn, directory )
133-
.recoverWith( FileNotFoundException.class,
134-
ex -> loadFromDirectory( urn, super.resolve( urn ) )
135-
.mapFailure( Case( $( instanceOf( FileNotFoundException.class ) ),
136-
e -> new FileNotFoundException( ex.getMessage() + ". AND " + e.getMessage() ) ) )
137-
);
138-
}
139-
140-
public static <T extends ResolutionStrategy> T withDefaultNamespace( final T strategy, final Model model ) {
141-
if ( strategy instanceof DefaultNamespace ) {
142-
Optional.ofNullable( model.getNsPrefixURI( "" ) ).ifPresent( ns ->
143-
((DefaultNamespace) strategy).defaultUrn = AspectModelUrn.fromUrn( ns + "DefaultPath" )
144-
);
145-
}
146-
147-
return strategy;
148-
}
149-
150-
@Override
151-
public String toString() {
152-
return super.toString() + ".DefaultNamespace(ns=" + defaultUrn + ')';
153-
}
154-
}
155119
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH
3+
*
4+
* See the AUTHORS file(s) distributed with this work for additional
5+
* information regarding authorship.
6+
*
7+
* This Source Code Form is subject to the terms of the Mozilla Public
8+
* License, v. 2.0. If a copy of the MPL was not distributed with this
9+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
10+
*
11+
* SPDX-License-Identifier: MPL-2.0
12+
*/
13+
14+
package org.eclipse.esmf.aspectmodel.resolver.fs;
15+
16+
import java.nio.file.Path;
17+
18+
import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;
19+
20+
/**
21+
* A models root directory that assumes all model files are in the same directory.
22+
*/
23+
public class FlatModelsRoot extends ModelsRoot {
24+
public FlatModelsRoot( final Path root ) {
25+
super( root );
26+
}
27+
28+
@Override
29+
public Path directoryForNamespace( final AspectModelUrn urn ) {
30+
return rootPath();
31+
}
32+
33+
@Override
34+
public String toString() {
35+
return "FlatModelsRoot(rootPath=" + rootPath() + ")";
36+
}
37+
}

0 commit comments

Comments
 (0)