Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix resolution of model elements from other namespaces in native #509

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@

package org.eclipse.esmf.aspectmodel.resolver;

import static io.vavr.API.*;
import static io.vavr.API.$;
import static io.vavr.API.Case;
import static io.vavr.Predicates.instanceOf;

import java.io.ByteArrayInputStream;
Expand All @@ -36,15 +37,9 @@
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.io.FilenameUtils;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.vocabulary.XSD;
import org.eclipse.esmf.aspectmodel.VersionNumber;
import org.eclipse.esmf.aspectmodel.resolver.fs.FlatModelsRoot;
import org.eclipse.esmf.aspectmodel.resolver.fs.StructuredModelsRoot;
import org.eclipse.esmf.aspectmodel.resolver.services.SammAspectMetaModelResourceResolver;
import org.eclipse.esmf.aspectmodel.resolver.services.TurtleLoader;
import org.eclipse.esmf.aspectmodel.resolver.services.VersionedModel;
Expand All @@ -58,17 +53,27 @@
import org.eclipse.esmf.samm.KnownVersion;

import com.google.common.collect.Streams;

import io.vavr.CheckedFunction1;
import io.vavr.Value;
import io.vavr.control.Option;
import io.vavr.control.Try;
import org.apache.commons.io.FilenameUtils;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.ModelFactory;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.vocabulary.RDF;
import org.apache.jena.vocabulary.XSD;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

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

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

if ( mergedModel.isFailure() ) {
if ( mergedModel.getCause() instanceof FileNotFoundException ) {
return Try.failure( new ModelResolutionException( "Could not resolve " + input, mergedModel.getCause() ) );
if ( mergedModel.getCause() instanceof final FileNotFoundException fileNotFoundException ) {
final String failedUrns = input.stream()
.filter( urn -> !urn.getElementType().equals( ElementType.META_MODEL ) )
.filter( urn -> !urn.getElementType().equals( ElementType.CHARACTERISTIC ) )
.filter( urn -> !urn.getElementType().equals( ElementType.ENTITY ) )
.filter( urn -> !urn.getElementType().equals( ElementType.UNIT ) )
.map( AspectModelUrn::toString )
.collect( Collectors.joining( ", " ) );
LOG.debug( "Could not resolve {}", failedUrns, fileNotFoundException );
return Try.failure( new ModelResolutionException( "Could not resolve " + failedUrns, fileNotFoundException ) );
}
return Try.failure( mergedModel.getCause() );
}
Expand Down Expand Up @@ -236,9 +247,14 @@ public Try<VersionedModel> resolveAspectModel( final Model initialModel, final R
*/
public static boolean containsDefinition( final Model model, final AspectModelUrn urn ) {
if ( model.getNsPrefixMap().values().stream().anyMatch( prefixUri -> prefixUri.startsWith( "urn:bamm:" ) ) ) {
return model.contains( model.createResource( urn.toString().replace( "urn:samm:", "urn:bamm:" ) ), RDF.type, (RDFNode) null );
final boolean result = model.contains( model.createResource( urn.toString().replace( "urn:samm:", "urn:bamm:" ) ), RDF.type,
(RDFNode) null );
LOG.debug( "Checking if model contains {}: {}", urn, result );
return result;
}
return model.contains( model.createResource( urn.toString() ), RDF.type, (RDFNode) null );
final boolean result = model.contains( model.createResource( urn.toString() ), RDF.type, (RDFNode) null );
LOG.debug( "Checking if model contains {}: {}", urn, result );
return result;
}

/**
Expand All @@ -262,6 +278,7 @@ private Try<Model> resolve( final Model result, final List<AspectModelUrn> urns,
final String urnToResolve = unresolvedUrns.pop();
final Try<Model> resolvedModel = getModelForUrn( urnToResolve, resolutionStrategy );
if ( resolvedModel.isFailure() ) {
LOG.debug( "Tried to resolve {} using {}, but it failed", urnToResolve, resolutionStrategy );
return resolvedModel;
}
final Model model = resolvedModel.get();
Expand Down Expand Up @@ -399,40 +416,18 @@ private void mergeModels( final Model target, final Model other ) {
* @return the resolved model on success
*/
public static Try<VersionedModel> loadAndResolveModel( final File input ) {
return loadAndResolveModelFromUrnLikeDir( input )
.orElse( () -> loadAndResolveModelFromDir( input ) );
}

private static Try<VersionedModel> loadAndResolveModelFromUrnLikeDir( final File input ) {
final AspectModelResolver resolver = new AspectModelResolver();
final File inputFile = input.getAbsoluteFile();
final Try<AspectModelUrn> urnTry = fileToUrn( inputFile );
final Try<FileSystemStrategy> strategyTry = getModelRoot( inputFile ).map( FileSystemStrategy::new );
//noinspection unchecked
return urnTry
.flatMap( urn -> strategyTry
.flatMap( strategy -> resolver.resolveAspectModel( strategy, urn ) )
.mapFailure(
Case( $( instanceOf( IOException.class ) ),
e -> new ModelResolutionException( "Could not load model " + urn + " from file " + input, e ) )
)
);
}

private static Try<VersionedModel> loadAndResolveModelFromDir( final File input ) {
final AspectModelResolver resolver = new AspectModelResolver();
final File inputFile = input.getAbsoluteFile();
final Try<Path> modelsRoot = Try.of( () -> inputFile.getParentFile().toPath() );
final Try<FileSystemStrategy> strategyTry = modelsRoot.map( FileSystemStrategy.DefaultNamespace::new );

//noinspection unchecked
return strategyTry
.flatMapTry( strategy -> Try
.withResources( () -> new FileInputStream( input ) )
.of( stream -> resolver.resolveAspectModel( strategy, stream ) ) )
.flatMap( Function.identity() )
.mapFailure( Case( $( instanceOf( IOException.class ) ),
e -> new ModelResolutionException( "Could not open file " + input, e ) ) );
final ResolutionStrategy fromSameDirectory = new FileSystemStrategy( new FlatModelsRoot( inputFile.getParentFile().toPath() ) );

// Construct the resolution strategy: Models should be searched in the structured folder (if it exists) and then in the
// same directory. If the structured folder can not be resolved, directly search in the same directory.
final ResolutionStrategy resolutionStrategy = getModelRoot( inputFile ).map(
modelsRoot -> new FileSystemStrategy( new StructuredModelsRoot( modelsRoot ) ) )
.<ResolutionStrategy> map( structured -> new EitherStrategy( structured, fromSameDirectory ) ).getOrElse( fromSameDirectory );
return Try.withResources( () -> new FileInputStream( input ) )
.of( stream -> resolver.resolveAspectModel( resolutionStrategy, stream ) )
.flatMap( Function.identity() );
}

/**
Expand Down Expand Up @@ -488,9 +483,9 @@ public static Try<AspectModelUrn> fileToUrn( final File inputFile ) {
* @param file the input model file
* @return the URN of the model element that corresponds to the file name and its location inside the models root
*/
public static AspectModelUrn urnFromModel(final VersionedModel model, final File file) {
public static AspectModelUrn urnFromModel( final VersionedModel model, final File file ) {
final String aspectName = FilenameUtils.removeExtension( file.getName() );
return Streams.stream( model.getRawModel().listSubjects()).filter( s-> aspectName.equals( s.getLocalName() ) )
return Streams.stream( model.getRawModel().listSubjects() ).filter( s -> aspectName.equals( s.getLocalName() ) )
.findFirst()
.map( Resource::getURI )
.map( AspectModelUrn::fromUrn )
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@

package org.eclipse.esmf.aspectmodel.resolver;

import org.apache.jena.rdf.model.Model;
import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;

import io.vavr.control.Try;
import org.apache.jena.rdf.model.Model;

/**
* A Resolution strategy that supports two types of inputs and wraps two dedicated sub-resolution strategies
Expand All @@ -39,4 +39,9 @@ public Try<Model> apply( final AspectModelUrn input ) {
}
return strategy2.apply( input );
}

@Override
public String toString() {
return "EitherStrategy(strategy1=" + strategy1 + ", strategy2=" + strategy2 + ")";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,20 +13,17 @@

package org.eclipse.esmf.aspectmodel.resolver;

import static io.vavr.API.$;
import static io.vavr.API.Case;
import static io.vavr.Predicates.instanceOf;

import java.io.File;
import java.io.FileNotFoundException;
import java.net.MalformedURLException;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Optional;

import org.eclipse.esmf.aspectmodel.resolver.fs.ModelsRoot;
import org.eclipse.esmf.aspectmodel.resolver.fs.StructuredModelsRoot;
import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;

import fs.ModelsRoot;
import io.vavr.control.Try;
import org.apache.jena.rdf.model.Model;
import org.slf4j.Logger;
Expand Down Expand Up @@ -58,7 +55,16 @@ public class FileSystemStrategy extends AbstractResolutionStrategy {
* @param modelsRoot The root directory for model files
*/
public FileSystemStrategy( final Path modelsRoot ) {
this.modelsRoot = new ModelsRoot( modelsRoot );
this( new StructuredModelsRoot( modelsRoot ) );
}

/**
* Initialize the FileSystemStrategy with the root path of models.
*
* @param modelsRoot The root directory for model files
*/
public FileSystemStrategy( final ModelsRoot modelsRoot ) {
this.modelsRoot = modelsRoot;
}

/**
Expand All @@ -73,11 +79,7 @@ public FileSystemStrategy( final Path modelsRoot ) {
*/
@Override
public Try<Model> apply( final AspectModelUrn aspectModelUrn ) {
final Path directory = resolve( aspectModelUrn );
return loadFromDirectory( aspectModelUrn, directory );
}

protected Try<Model> loadFromDirectory( final AspectModelUrn aspectModelUrn, final Path directory ) {
final Path directory = modelsRoot.directoryForNamespace( aspectModelUrn );
final File namedResourceFile = directory.resolve( aspectModelUrn.getName() + ".ttl" ).toFile();
if ( namedResourceFile.exists() ) {
return loadFromUri( namedResourceFile.toURI() );
Expand All @@ -86,70 +88,32 @@ protected Try<Model> loadFromDirectory( final AspectModelUrn aspectModelUrn, fin
LOG.warn( "Looking for {}, but no {}.ttl was found. Inspecting files in {}", aspectModelUrn.getName(),
aspectModelUrn.getName(), directory );

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

protected Path resolve( final AspectModelUrn aspectModelUrn ) {
return modelsRoot.directoryForNamespace( aspectModelUrn );
for ( final File file : files ) {
if ( !file.isFile() || !file.getName().endsWith( ".ttl" ) ) {
continue;
}
LOG.debug( "Looking for {} in {}", aspectModelUrn, file );
final Try<Model> tryModel = loadFromUri( file.toURI() );
if ( tryModel.isFailure() ) {
LOG.debug( "Could not load model from {}", file, tryModel.getCause() );
} else {
final Model model = tryModel.get();
if ( AspectModelResolver.containsDefinition( model, aspectModelUrn ) ) {
return Try.success( model );
} else {
LOG.debug( "File {} does not contain {}", file, aspectModelUrn );
}
}
}
return Try.failure(
new FileNotFoundException( "No model file containing " + aspectModelUrn + " could be found in directory: " + directory ) );
}

@Override
public String toString() {
return "FileSystemStrategy(root=" + modelsRoot + ')';
}

/**
* Initialize the File System Strategy where the default namespace is resolved in the root folder.
*/
public static class DefaultNamespace extends FileSystemStrategy {
AspectModelUrn defaultUrn;

public DefaultNamespace( final Path modelsRoot ) {
super( modelsRoot );
}

@Override
protected Path resolve( final AspectModelUrn urn ) {
return (urn.getNamespace().equals( defaultUrn.getNamespace() ) && urn.getVersion().equals( defaultUrn.getVersion() ))
? modelsRoot.rootPath()
: super.resolve( urn );
}

@Override
public Try<Model> apply( final AspectModelUrn urn ) {
final Path directory = resolve( urn );
return loadFromDirectory( urn, directory )
.recoverWith( FileNotFoundException.class,
ex -> loadFromDirectory( urn, super.resolve( urn ) )
.mapFailure( Case( $( instanceOf( FileNotFoundException.class ) ),
e -> new FileNotFoundException( ex.getMessage() + ". AND " + e.getMessage() ) ) )
);
}

public static <T extends ResolutionStrategy> T withDefaultNamespace( final T strategy, final Model model ) {
if ( strategy instanceof DefaultNamespace ) {
Optional.ofNullable( model.getNsPrefixURI( "" ) ).ifPresent( ns ->
((DefaultNamespace) strategy).defaultUrn = AspectModelUrn.fromUrn( ns + "DefaultPath" )
);
}

return strategy;
}

@Override
public String toString() {
return super.toString() + ".DefaultNamespace(ns=" + defaultUrn + ')';
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH
*
* See the AUTHORS file(s) distributed with this work for additional
* information regarding authorship.
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*
* SPDX-License-Identifier: MPL-2.0
*/

package org.eclipse.esmf.aspectmodel.resolver.fs;

import java.nio.file.Path;

import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;

/**
* A models root directory that assumes all model files are in the same directory.
*/
public class FlatModelsRoot extends ModelsRoot {
public FlatModelsRoot( final Path root ) {
super( root );
}

@Override
public Path directoryForNamespace( final AspectModelUrn urn ) {
return rootPath();
}

@Override
public String toString() {
return "FlatModelsRoot(rootPath=" + rootPath() + ")";
}
}
Loading