From 6aba208ae458c2f640284bcd8577efe868f430fc Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 24 Oct 2024 11:17:02 +0200 Subject: [PATCH 1/8] Move ModelResolutionException to exceptions package --- .../eclipse/esmf/aspectmodel/loader/AspectModelLoader.java | 2 +- .../esmf/aspectmodel/resolver/AspectModelFileLoader.java | 1 + .../eclipse/esmf/aspectmodel/resolver/ClasspathStrategy.java | 1 + .../eclipse/esmf/aspectmodel/resolver/CommandExecutor.java | 2 ++ .../org/eclipse/esmf/aspectmodel/resolver/EitherStrategy.java | 1 + .../eclipse/esmf/aspectmodel/resolver/FileSystemStrategy.java | 1 + .../esmf/aspectmodel/resolver/FromLoadedFileStrategy.java | 1 + .../eclipse/esmf/aspectmodel/resolver/ResolutionStrategy.java | 1 + .../resolver/{ => exceptions}/ModelResolutionException.java | 4 ++-- .../esmf/aspectmodel/resolver/fs/StructuredModelsRoot.java | 2 +- .../main/java/org/eclipse/esmf/aspectmodel/stats/Usage.java | 2 +- .../aspectmodel/versionupdate/MetaModelVersionMigrator.java | 2 +- .../esmf/aspectmodel/resolver/AspectModelResolverTest.java | 1 + .../esmf/aspectmodel/resolver/github/GitHubStrategy.java | 2 +- 14 files changed, 16 insertions(+), 7 deletions(-) rename core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/{ => exceptions}/ModelResolutionException.java (85%) diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java index 14a84ab3c..e25c5f70b 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java @@ -43,10 +43,10 @@ import org.eclipse.esmf.aspectmodel.resolver.AspectModelFileLoader; import org.eclipse.esmf.aspectmodel.resolver.EitherStrategy; import org.eclipse.esmf.aspectmodel.resolver.FileSystemStrategy; -import org.eclipse.esmf.aspectmodel.resolver.ModelResolutionException; import org.eclipse.esmf.aspectmodel.resolver.ModelSource; import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy; import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategySupport; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.resolver.fs.FlatModelsRoot; import org.eclipse.esmf.aspectmodel.resolver.modelfile.DefaultAspectModelFile; import org.eclipse.esmf.aspectmodel.resolver.modelfile.MetaModelFile; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java index 2de2bacc3..6da84a83e 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java @@ -34,6 +34,7 @@ import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.resolver.exceptions.ParserException; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFile; import org.eclipse.esmf.aspectmodel.resolver.services.TurtleLoader; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ClasspathStrategy.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ClasspathStrategy.java index a0f6dae81..5385ab47d 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ClasspathStrategy.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ClasspathStrategy.java @@ -33,6 +33,7 @@ import java.util.stream.Stream; import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.apache.commons.io.IOUtils; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/CommandExecutor.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/CommandExecutor.java index 3f6c96cb6..707804638 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/CommandExecutor.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/CommandExecutor.java @@ -18,6 +18,8 @@ import java.util.Scanner; import java.util.StringTokenizer; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; + /** * Executes an external resolver via the underlying OS command and returns the stdout from the command as result. */ diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/EitherStrategy.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/EitherStrategy.java index 98560248a..a50c44f1c 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/EitherStrategy.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/EitherStrategy.java @@ -20,6 +20,7 @@ import java.util.stream.Stream; import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import io.vavr.control.Try; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/FileSystemStrategy.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/FileSystemStrategy.java index d8a8c663c..02718eed1 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/FileSystemStrategy.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/FileSystemStrategy.java @@ -22,6 +22,7 @@ import java.util.stream.Stream; import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.resolver.fs.ModelsRoot; import org.eclipse.esmf.aspectmodel.resolver.fs.StructuredModelsRoot; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/FromLoadedFileStrategy.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/FromLoadedFileStrategy.java index d9a67efc6..9eac9b520 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/FromLoadedFileStrategy.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/FromLoadedFileStrategy.java @@ -17,6 +17,7 @@ import java.util.stream.Stream; import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; /** diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ResolutionStrategy.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ResolutionStrategy.java index ac0a9804d..11625080f 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ResolutionStrategy.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ResolutionStrategy.java @@ -14,6 +14,7 @@ package org.eclipse.esmf.aspectmodel.resolver; import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.functions.ThrowingBiFunction; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ModelResolutionException.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/exceptions/ModelResolutionException.java similarity index 85% rename from core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ModelResolutionException.java rename to core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/exceptions/ModelResolutionException.java index 316cd39df..b568fd2ea 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ModelResolutionException.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/exceptions/ModelResolutionException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2023 Robert Bosch Manufacturing Solutions GmbH + * Copyright (c) 2024 Robert Bosch Manufacturing Solutions GmbH * * See the AUTHORS file(s) distributed with this work for additional * information regarding authorship. @@ -11,7 +11,7 @@ * SPDX-License-Identifier: MPL-2.0 */ -package org.eclipse.esmf.aspectmodel.resolver; +package org.eclipse.esmf.aspectmodel.resolver.exceptions; public class ModelResolutionException extends RuntimeException { private static final long serialVersionUID = 1719805029020063645L; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/fs/StructuredModelsRoot.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/fs/StructuredModelsRoot.java index d37b565b9..68c4af705 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/fs/StructuredModelsRoot.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/fs/StructuredModelsRoot.java @@ -24,7 +24,7 @@ import java.util.stream.Stream; import org.eclipse.esmf.aspectmodel.VersionNumber; -import org.eclipse.esmf.aspectmodel.resolver.ModelResolutionException; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; /** diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/stats/Usage.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/stats/Usage.java index 2ec411b52..6bcdc583d 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/stats/Usage.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/stats/Usage.java @@ -18,7 +18,7 @@ import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.RdfUtil; -import org.eclipse.esmf.aspectmodel.resolver.ModelResolutionException; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.resolver.ModelSource; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/versionupdate/MetaModelVersionMigrator.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/versionupdate/MetaModelVersionMigrator.java index 9c292fbfd..66cdcc458 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/versionupdate/MetaModelVersionMigrator.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/versionupdate/MetaModelVersionMigrator.java @@ -20,7 +20,7 @@ import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.VersionNumber; -import org.eclipse.esmf.aspectmodel.resolver.ModelResolutionException; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.resolver.exceptions.InvalidVersionException; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFile; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelResolverTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelResolverTest.java index 2cd9320ce..91fb3cf8d 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelResolverTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelResolverTest.java @@ -27,6 +27,7 @@ import java.util.Arrays; import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.resolver.services.TurtleLoader; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.metamodel.AspectModel; diff --git a/core/esmf-aspect-model-github-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategy.java b/core/esmf-aspect-model-github-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategy.java index b30913a4f..abc09c7cc 100644 --- a/core/esmf-aspect-model-github-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategy.java +++ b/core/esmf-aspect-model-github-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategy.java @@ -14,9 +14,9 @@ package org.eclipse.esmf.aspectmodel.resolver.github; import org.eclipse.esmf.aspectmodel.AspectModelFile; -import org.eclipse.esmf.aspectmodel.resolver.ModelResolutionException; import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy; import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategySupport; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; /** From 34e9a03cad969b46d52ba2941bb5dcc77076e576 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 24 Oct 2024 11:23:35 +0200 Subject: [PATCH 2/8] Fix loading models with multiple paths to the same file Fixes #664 --- .../org/eclipse/esmf/aspectmodel/RdfUtil.java | 29 +++++++++++++++++++ .../aspectmodel/loader/AspectModelLoader.java | 4 +-- .../loader/AspectModelLoaderTest.java | 16 ++++++++++ 3 files changed, 47 insertions(+), 2 deletions(-) diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java index c3ca8eb1d..8585a9688 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/RdfUtil.java @@ -36,6 +36,7 @@ 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.ResIterator; import org.apache.jena.rdf.model.Resource; import org.apache.jena.rdf.model.Statement; import org.apache.jena.vocabulary.RDF; @@ -165,4 +166,32 @@ public static List getNamedElementsReferringTo( final RDFNode node ) { } ).toList(); } } + + /** + * Merges an RDF-model into another on a per-element basis instead of a per-statement basis. This means only those model element + * definitions from the model to merge are merged into the target model that are not already present in the target model. + * This prevents duplicate assertions of statements where the object is a blank node. + * + * @param target the model to merge into + * @param modelToMerge the source model of model element definitions to merge + */ + public static void mergeModel( final Model target, final Model modelToMerge ) { + final Set targetSubjects = Streams.stream( target.listSubjects() ) + .filter( Resource::isURIResource ) + .map( Resource::getURI ) + .collect( toSet() ); + for ( final ResIterator it = modelToMerge.listSubjects(); it.hasNext(); ) { + final Resource resource = it.next(); + if ( resource.isAnon() ) { + continue; + } + if ( !targetSubjects.contains( resource.getURI() ) ) { + final Model modelElementDefinition = getModelElementDefinition( resource ); + target.add( modelElementDefinition ); + } + } + for ( final Map.Entry prefixEntry : modelToMerge.getNsPrefixMap().entrySet() ) { + target.setNsPrefix( prefixEntry.getKey(), prefixEntry.getValue() ); + } + } } diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java index e25c5f70b..93a26d233 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java @@ -434,10 +434,10 @@ public AspectModel emptyModel() { */ public AspectModel loadAspectModelFiles( final Collection inputFiles ) { final Model mergedModel = ModelFactory.createDefaultModel(); - mergedModel.add( MetaModelFile.metaModelDefinitions() ); for ( final AspectModelFile file : inputFiles ) { - mergedModel.add( file.sourceModel() ); + RdfUtil.mergeModel( mergedModel, file.sourceModel() ); } + mergedModel.add( MetaModelFile.metaModelDefinitions() ); final List elements = new ArrayList<>(); final List files = new ArrayList<>(); diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoaderTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoaderTest.java index 93ab1bd4c..e5c03718e 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoaderTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoaderTest.java @@ -29,17 +29,22 @@ import java.util.stream.Collectors; import org.eclipse.esmf.aspectmodel.AspectLoadingException; +import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.resolver.FileSystemStrategy; import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy; import org.eclipse.esmf.metamodel.AbstractEntity; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.metamodel.ComplexType; import org.eclipse.esmf.metamodel.HasDescription; +import org.eclipse.esmf.metamodel.vocabulary.SammNs; import org.eclipse.esmf.samm.KnownVersion; import org.eclipse.esmf.test.InvalidTestAspect; import org.eclipse.esmf.test.TestAspect; import org.eclipse.esmf.test.TestResources; +import org.apache.jena.rdf.model.RDFNode; +import org.apache.jena.rdf.model.Resource; +import org.apache.jena.rdf.model.Statement; import org.junit.jupiter.api.Test; class AspectModelLoaderTest { @@ -199,6 +204,17 @@ void testMergeAspectModels() { assertThat( merged.elements().size() ).isEqualTo( a1.elements().size() + a2.elements().size() ); } + @Test + void testLoadMultipleFilesWithOverlappingRdfStatements() { + final AspectModelFile file1 = TestResources.load( TestAspect.ASPECT_WITH_PROPERTY ).files().iterator().next(); + final AspectModelFile file2 = TestResources.load( TestAspect.ASPECT_WITH_PROPERTY ).files().iterator().next(); + final AspectModel aspectModel = new AspectModelLoader().loadAspectModelFiles( List.of( file1, file2 ) ); + final Resource aspect = aspectModel.mergedModel().createResource( TestAspect.ASPECT_WITH_PROPERTY.getUrn().toString() ); + final List propertiesAssertions = aspectModel.mergedModel() + .listStatements( aspect, SammNs.SAMM.properties(), (RDFNode) null ).toList(); + assertThat( propertiesAssertions ).hasSize( 1 ); + } + /** * Returns the File object for a test model file */ From 86c1ce08d71cbe27a18645d90e09b3ef2101547a Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 24 Oct 2024 11:32:09 +0200 Subject: [PATCH 3/8] Fix race condition in type setup --- .../esmf/aspectmodel/loader/AspectModelLoader.java | 2 ++ .../esmf/aspectmodel/resolver/services/TurtleLoader.java | 9 +++++++-- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java index 93a26d233..f72ff5009 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java @@ -51,6 +51,7 @@ import org.eclipse.esmf.aspectmodel.resolver.modelfile.DefaultAspectModelFile; import org.eclipse.esmf.aspectmodel.resolver.modelfile.MetaModelFile; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFile; +import org.eclipse.esmf.aspectmodel.resolver.services.TurtleLoader; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.aspectmodel.urn.ElementType; import org.eclipse.esmf.aspectmodel.urn.UrnSyntaxException; @@ -113,6 +114,7 @@ public AspectModelLoader( final ResolutionStrategy resolutionStrategy ) { * @param resolutionStrategies the strategies */ public AspectModelLoader( final List resolutionStrategies ) { + TurtleLoader.init(); if ( resolutionStrategies.size() == 1 ) { resolutionStrategy = resolutionStrategies.get( 0 ); } else if ( resolutionStrategies.isEmpty() ) { diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/services/TurtleLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/services/TurtleLoader.java index 572691f39..a30d401c6 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/services/TurtleLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/services/TurtleLoader.java @@ -21,6 +21,7 @@ import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.stream.Collectors; + import javax.annotation.Nullable; import org.eclipse.esmf.aspectmodel.resolver.exceptions.ParserException; @@ -45,6 +46,11 @@ public final class TurtleLoader { private TurtleLoader() { } + public static void init() { + SammXsdType.setupTypeMapping(); + registerTurtle(); + } + /** * Loads a Turtle model from an input stream * @@ -85,8 +91,7 @@ public static Try loadTurtle( final URL url ) { */ public static Try loadTurtle( @Nullable final String modelContent ) { Objects.requireNonNull( modelContent, "Model content must not be null." ); - SammXsdType.setupTypeMapping(); - registerTurtle(); + init(); try ( final InputStream turtleInputStream = new ByteArrayInputStream( modelContent.getBytes( StandardCharsets.UTF_8 ) ) ) { final Model streamModel = RDFParser.create() // Make sure to NOT use FactoryRDFCaching because it will return the same objects for nodes appearing From 53f16ac9b8bb5a40f1f3590dd136c37093167e6f Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 24 Oct 2024 11:32:21 +0200 Subject: [PATCH 4/8] Use Aspect's namespace as empty prefix in merged RDF of AspectModel --- .../eclipse/esmf/aspectmodel/loader/AspectModelLoader.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java index f72ff5009..ea4fc28b1 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/loader/AspectModelLoader.java @@ -56,6 +56,7 @@ import org.eclipse.esmf.aspectmodel.urn.ElementType; import org.eclipse.esmf.aspectmodel.urn.UrnSyntaxException; import org.eclipse.esmf.aspectmodel.versionupdate.MetaModelVersionMigrator; +import org.eclipse.esmf.metamodel.Aspect; import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.metamodel.ModelElement; import org.eclipse.esmf.metamodel.Namespace; @@ -462,6 +463,10 @@ public AspectModel loadAspectModelFiles( final Collection input } setNamespaces( files, elements ); + elements.stream() + .filter( modelElement -> modelElement.is( Aspect.class ) ) + .findFirst() + .ifPresent( aspect -> mergedModel.setNsPrefix( "", aspect.urn().getUrnPrefix() ) ); return new DefaultAspectModel( files, mergedModel, elements ); } From 7cfef3c4c113e6eefc7f3f9424b73ec07baa4014 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 24 Oct 2024 11:34:27 +0200 Subject: [PATCH 5/8] Refactor downloading and GH URL parsing in GitHub model source --- .../resolver/AspectModelFileLoader.java | 10 ++ .../esmf/aspectmodel/resolver/Download.java | 88 ++++++++++++++ .../resolver/GitHubFileLocation.java | 103 ++++++++++++++++ .../resolver/GithubRepository.java | 60 ++++++++++ .../aspectmodel/resolver/ProxyConfig.java | 57 +++++++++ .../resolver/fs/StructuredModelsRoot.java | 9 +- .../resolver/AspectModelResolverTest.java | 6 - .../resolver/GithubLocationTest.java | 97 +++++++++++++++ .../resolver/github/GitHubModelSource.java | 113 ++++-------------- .../resolver/github/GitHubStrategy.java | 34 ++++-- .../resolver/github/GitHubStrategyTest.java | 27 ++++- .../esmf/aspectmodel/urn/AspectModelUrn.java | 6 +- 12 files changed, 489 insertions(+), 121 deletions(-) create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/Download.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/GitHubFileLocation.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/GithubRepository.java create mode 100644 core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ProxyConfig.java create mode 100644 core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/resolver/GithubLocationTest.java diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java index 6da84a83e..0cf87a36e 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelFileLoader.java @@ -16,6 +16,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank; import java.io.BufferedReader; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -97,6 +98,10 @@ public static RawAspectModelFile load( final Model model ) { return new RawAspectModelFile( model, List.of(), Optional.empty() ); } + public static RawAspectModelFile load( final byte[] content ) { + return load( new ByteArrayInputStream( content ) ); + } + public static RawAspectModelFile load( final URL url ) { if ( url.getProtocol().equals( "file" ) ) { try { @@ -104,8 +109,13 @@ public static RawAspectModelFile load( final URL url ) { } catch ( final URISyntaxException exception ) { throw new ModelResolutionException( "Can not load model from file URL", exception ); } + } else if ( url.getProtocol().equals( "http" ) || url.getProtocol().equals( "https" ) ) { + // Downloading from http(s) should take proxy settings into consideration, so we don't just .openStream() here + final byte[] fileContent = new Download().downloadFile( url ); + return load( fileContent ); } try { + // Other URLs (e.g. resource://) we just load using openStream() return load( url.openStream(), Optional.of( url.toURI() ) ); } catch ( final IOException | URISyntaxException exception ) { throw new ModelResolutionException( "Can not load model from URL", exception ); diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/Download.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/Download.java new file mode 100644 index 000000000..3048b52dd --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/Download.java @@ -0,0 +1,88 @@ +/* + * 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; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.net.URL; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.Optional; + +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Convenience class to download a file via HTTP, which the ability to auto-detect and use proxy settings + */ +public class Download { + private static final Logger LOG = LoggerFactory.getLogger( Download.class ); + private final ProxyConfig proxyConfig; + + public Download( final ProxyConfig proxyConfig ) { + this.proxyConfig = proxyConfig; + } + + public Download() { + this( ProxyConfig.detectProxySettings() ); + } + + /** + * Download the file and return the contents as byte array + * + * @param fileUrl the URL + * @return the file contents + */ + public byte[] downloadFile( final URL fileUrl ) { + try { + final HttpClient.Builder clientBuilder = HttpClient.newBuilder() + .version( HttpClient.Version.HTTP_1_1 ) + .followRedirects( HttpClient.Redirect.ALWAYS ) + .connectTimeout( Duration.ofSeconds( 10 ) ); + Optional.ofNullable( proxyConfig.proxy() ).ifPresent( clientBuilder::proxy ); + Optional.ofNullable( proxyConfig.authenticator() ).ifPresent( clientBuilder::authenticator ); + final HttpClient client = clientBuilder.build(); + final HttpRequest request = HttpRequest.newBuilder().uri( fileUrl.toURI() ).build(); + final HttpResponse response = client.send( request, HttpResponse.BodyHandlers.ofByteArray() ); + return response.body(); + } catch ( final InterruptedException | URISyntaxException | IOException exception ) { + throw new ModelResolutionException( "Could not retrieve " + fileUrl, exception ); + } + } + + /** + * Download the file and write it to the file system + * + * @param fileUrl the URL + * @param outputFile the output file to write + * @return the file written + */ + public File downloadFile( final URL fileUrl, final File outputFile ) { + try ( final FileOutputStream outputStream = new FileOutputStream( outputFile ) ) { + final byte[] fileContent = downloadFile( fileUrl ); + outputStream.write( fileContent ); + } catch ( final IOException exception ) { + throw new ModelResolutionException( "Could not write file " + outputFile, exception ); + } + + LOG.info( "Downloaded {} to local file {}", fileUrl.getPath(), outputFile ); + return outputFile; + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/GitHubFileLocation.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/GitHubFileLocation.java new file mode 100644 index 000000000..6a2bff8bb --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/GitHubFileLocation.java @@ -0,0 +1,103 @@ +/* + * 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; + +import java.io.Serial; +import java.util.Arrays; +import java.util.Optional; +import java.util.function.Supplier; + +import io.vavr.control.Try; + +public record GitHubFileLocation( + GithubRepository repositoryLocation, + String directory, + String namespaceMainPart, + String version, + String filename +) { + public static Optional parse( final String url ) { + return Try.of( () -> new GitHubUrlParser( url ).get() ).toJavaOptional(); + } + + private static class ParsingException extends RuntimeException { + @Serial + private static final long serialVersionUID = -4855068216382713797L; + } + + private static class GitHubUrlParser implements Supplier { + private int index = 0; + private final String source; + + private GitHubUrlParser( final String source ) { + this.source = source; + } + + private String readSection() { + final int oldIndex = index; + while ( index < source.length() && source.charAt( index ) != '/' ) { + index++; + } + if ( source.charAt( index ) != '/' ) { + throw new ParsingException(); + } + final String result = source.substring( oldIndex, index ); + index++; // eat the slash + return result; + } + + private void eatToken( final String token ) { + if ( source.startsWith( token, index ) ) { + index += token.length(); + return; + } + throw new ParsingException(); + } + + @Override + public GitHubFileLocation get() { + eatToken( "https://" ); + final String host = readSection(); + final String owner = readSection(); + final String repository = readSection(); + final GithubRepository.Ref ref; + if ( source.substring( index ).startsWith( "blob" ) ) { + eatToken( "blob/" ); + final String blob = readSection(); + ref = blob.matches( "[vV]?\\d+\\.\\d+.*" ) + ? new GithubRepository.Tag( blob ) + : new GithubRepository.Branch( blob ); + } else { + eatToken( "raw/refs/" ); + if ( source.substring( index ).startsWith( "heads" ) ) { + eatToken( "heads/" ); + final String branchName = readSection(); + ref = new GithubRepository.Branch( branchName ); + } else { + eatToken( "tags/" ); + final String tag = readSection(); + ref = new GithubRepository.Tag( tag ); + } + } + final String rest = source.substring( index ); + final String[] parts = rest.split( "/" ); + final String fileName = parts[parts.length - 1]; + final String version = parts[parts.length - 2]; + final String namespaceMainPart = parts[parts.length - 3]; + final String directory = String.join( "/", Arrays.copyOfRange( parts, 0, parts.length - 3 ) ); + final GithubRepository repoLocation = new GithubRepository( host, owner, repository, ref ); + return new GitHubFileLocation( repoLocation, directory, namespaceMainPart, version, fileName ); + } + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/GithubRepository.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/GithubRepository.java new file mode 100644 index 000000000..98ffe25e6 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/GithubRepository.java @@ -0,0 +1,60 @@ +/* + * 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; + +import java.net.MalformedURLException; +import java.net.URL; + +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; + +public record GithubRepository( + String host, + String owner, + String repository, + Ref branchOrTag +) { + public GithubRepository( final String owner, final String repository, final Ref branchOrTag ) { + this( "github.com", owner, repository, branchOrTag ); + } + + public sealed interface Ref { + String name(); + + String refType(); + } + + public record Branch( String name ) implements Ref { + @Override + public String refType() { + return "heads"; + } + } + + public record Tag( String name ) implements Ref { + @Override + public String refType() { + return "tags"; + } + } + + public URL zipLocation() { + final String url = "https://%s/%s/%s/archive/refs/%s/%s.zip".formatted( + host(), owner(), repository(), branchOrTag().refType(), branchOrTag().name() ); + try { + return new URL( url ); + } catch ( final MalformedURLException exception ) { + throw new ModelResolutionException( "Constructed GitHub URL is invalid: " + url ); + } + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ProxyConfig.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ProxyConfig.java new file mode 100644 index 000000000..3d4c4856f --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/ProxyConfig.java @@ -0,0 +1,57 @@ +/* + * 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; + +import java.net.Authenticator; +import java.net.InetSocketAddress; +import java.net.ProxySelector; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.soabase.recordbuilder.core.RecordBuilder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@RecordBuilder +public record ProxyConfig( + ProxySelector proxy, + Authenticator authenticator +) { + private static final Logger LOG = LoggerFactory.getLogger( ProxyConfig.class ); + + public static final ProxyConfig NO_PROXY = new ProxyConfig( null, null ); + + public static ProxyConfig detectProxySettings() { + final String envProxy = System.getenv( "http_proxy" ); + if ( envProxy != null && System.getProperty( "http.proxyHost" ) == null ) { + final Pattern proxyPattern = Pattern.compile( "http://([^:]*):(\\d+)/?" ); + final Matcher matcher = proxyPattern.matcher( envProxy ); + if ( matcher.matches() ) { + final String host = matcher.group( 1 ); + final String port = matcher.group( 2 ); + System.setProperty( "http.proxyHost", host ); + System.setProperty( "http.proxyPort", port ); + } else { + LOG.debug( "The value of the 'http_proxy' environment variable is malformed, ignoring: {}", envProxy ); + } + } + + final String host = System.getProperty( "http.proxyHost" ); + final String port = System.getProperty( "http.proxyPort" ); + if ( host != null && port != null ) { + return new ProxyConfig( ProxySelector.of( new InetSocketAddress( host, Integer.parseInt( port ) ) ), null ); + } + return NO_PROXY; + } +} diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/fs/StructuredModelsRoot.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/fs/StructuredModelsRoot.java index 68c4af705..bb608895a 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/fs/StructuredModelsRoot.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/fs/StructuredModelsRoot.java @@ -67,6 +67,7 @@ public Stream contents() { .flatMap( versionNumber -> AspectModelUrn.from( String.format( "urn:samm:%s:%s", file.getParentFile().getParentFile().getName(), versionNumber ) ) ).isSuccess() ) .sorted( Comparator.comparing( File::getName ) ) + .map( File::getAbsoluteFile ) .map( File::toURI ) .toList() .stream(); @@ -78,8 +79,12 @@ public Stream contents() { @Override public Stream namespaceContents( final AspectModelUrn namespace ) { - final File namespaceDirectory = rootPath().resolve( namespace.getNamespaceMainPart() ).resolve( namespace.getVersion() ).toFile(); - return Arrays.stream( Objects.requireNonNull( namespaceDirectory.listFiles( file -> file.getName().endsWith( ".ttl" ) ) ) ) + final File namespaceDirectory = rootPath() + .resolve( namespace.getNamespaceMainPart() ) + .resolve( namespace.getVersion() ) + .toFile(); + return Arrays.stream( Objects.requireNonNull( namespaceDirectory.listFiles( file -> + file.getName().endsWith( ".ttl" ) ) ) ) .map( File::toURI ); } } diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelResolverTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelResolverTest.java index 91fb3cf8d..f63318752 100644 --- a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelResolverTest.java +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/resolver/AspectModelResolverTest.java @@ -19,12 +19,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import java.io.File; -import java.io.IOException; -import java.io.InputStream; import java.net.URISyntaxException; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.util.Arrays; import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; @@ -35,7 +30,6 @@ import org.eclipse.esmf.samm.KnownVersion; import org.eclipse.esmf.test.TestModel; -import org.apache.commons.io.IOUtils; import org.apache.jena.rdf.model.Model; import org.apache.jena.rdf.model.Resource; import org.apache.jena.vocabulary.RDF; diff --git a/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/resolver/GithubLocationTest.java b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/resolver/GithubLocationTest.java new file mode 100644 index 000000000..acd58a470 --- /dev/null +++ b/core/esmf-aspect-meta-model-java/src/test/java/org/eclipse/esmf/aspectmodel/resolver/GithubLocationTest.java @@ -0,0 +1,97 @@ +/* + * 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; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +public class GithubLocationTest { + @ParameterizedTest + @MethodSource + void testParseUrl( final String url, final GitHubFileLocation parseResult ) { + assertThat( GitHubFileLocation.parse( url ) ).contains( parseResult ); + } + + static Stream testParseUrl() { + final GithubRepository esmfSdkOnMain = new GithubRepository( "eclipse-esmf", "esmf-sdk", new GithubRepository.Branch( "main" ) ); + final GitHubFileLocation aspectWithEntityOnMain = new GitHubFileLocation( esmfSdkOnMain, + "core/esmf-test-aspect-models/src/main/resources/valid", + "org.eclipse.esmf.test", "1.0.0", "AspectWithEntity.ttl" ); + + final GithubRepository esmfSdkOnTag = new GithubRepository( "eclipse-esmf", "esmf-sdk", new GithubRepository.Tag( "v2.9.0" ) ); + final GitHubFileLocation aspectWithEntityOnTag = new GitHubFileLocation( esmfSdkOnTag, + "core/esmf-test-aspect-models/src/main/resources/valid", + "org.eclipse.esmf.test", "1.0.0", "AspectWithEntity.ttl" ); + + final GithubRepository cxOnMain = new GithubRepository( + "eclipse-tractusx", "sldt-semantic-models", new GithubRepository.Branch( "main" ) ); + final GitHubFileLocation cxBatteryPassOnMain = new GitHubFileLocation( cxOnMain, "", "io.catenax.battery.battery_pass", "6.0.0", + "BatteryPass.ttl" ); + + final GithubRepository cxOnTag = new GithubRepository( + "eclipse-tractusx", "sldt-semantic-models", new GithubRepository.Tag( "v24.05" ) ); + final GitHubFileLocation cxBatteryPassOnTag = new GitHubFileLocation( cxOnTag, "", "io.catenax.battery.battery_pass", "6.0.0", + "BatteryPass.ttl" ); + + return Stream.of( + // Regular file on branch with directory + Arguments.arguments( + "https://github.com/eclipse-esmf/esmf-sdk/blob/main/core/esmf-test-aspect-models/src/main/resources/valid/" + + "org.eclipse.esmf.test/1.0.0/AspectWithEntity.ttl", aspectWithEntityOnMain + ), + // Regular file on tag with directory + Arguments.arguments( + "https://github.com/eclipse-esmf/esmf-sdk/blob/v2.9.0/core/esmf-test-aspect-models/src/main/resources/valid/org.eclipse" + + ".esmf.test/1.0.0/AspectWithEntity.ttl", aspectWithEntityOnTag + ), + // Raw file reference on branch with directory + Arguments.arguments( + "https://github.com/eclipse-esmf/esmf-sdk/raw/refs/heads/main/core/esmf-test-aspect-models/src/main/resources/valid/org" + + ".eclipse.esmf.test/1.0.0/AspectWithEntity.ttl", aspectWithEntityOnMain + ), + // Raw file reference on tag with directory + Arguments.arguments( + "https://github.com/eclipse-esmf/esmf-sdk/raw/refs/tags/v2.9" + + ".0/core/esmf-test-aspect-models/src/main/resources/valid/org.eclipse.esmf.test/1.0.0/AspectWithEntity.ttl", + aspectWithEntityOnTag + ), + + // Regular file on branch without directory + Arguments.arguments( + "https://github.com/eclipse-tractusx/sldt-semantic-models/blob/main/io.catenax.battery.battery_pass/6.0.0/BatteryPass" + + ".ttl", cxBatteryPassOnMain + ), + // Regular file on tag without directory + Arguments.arguments( + "https://github.com/eclipse-tractusx/sldt-semantic-models/blob/v24.05/io.catenax.battery.battery_pass/6.0.0/BatteryPass" + + ".ttl", cxBatteryPassOnTag + ), + // Raw file reference on branch without directory + Arguments.arguments( + "https://github.com/eclipse-tractusx/sldt-semantic-models/raw/refs/heads/main/io.catenax.battery.battery_pass/6.0" + + ".0/BatteryPass.ttl", cxBatteryPassOnMain + ), + // Raw file reference on tag without directory + Arguments.arguments( + "https://github.com/eclipse-tractusx/sldt-semantic-models/raw/refs/tags/v24.05/io.catenax.battery.battery_pass/6.0" + + ".0/BatteryPass.ttl", cxBatteryPassOnTag + ) + ); + } +} diff --git a/core/esmf-aspect-model-github-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubModelSource.java b/core/esmf-aspect-model-github-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubModelSource.java index 3ae81d8ec..b83651d21 100644 --- a/core/esmf-aspect-model-github-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubModelSource.java +++ b/core/esmf-aspect-model-github-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubModelSource.java @@ -14,37 +14,27 @@ package org.eclipse.esmf.aspectmodel.resolver.github; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.net.Authenticator; -import java.net.InetSocketAddress; -import java.net.ProxySelector; import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.net.http.HttpClient; -import java.net.http.HttpRequest; -import java.net.http.HttpResponse; import java.nio.file.Files; import java.nio.file.Path; -import java.time.Duration; import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.List; import java.util.Optional; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import java.util.stream.Stream; import java.util.zip.ZipFile; import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.resolver.AspectModelFileLoader; +import org.eclipse.esmf.aspectmodel.resolver.Download; +import org.eclipse.esmf.aspectmodel.resolver.GithubRepository; import org.eclipse.esmf.aspectmodel.resolver.ModelSource; +import org.eclipse.esmf.aspectmodel.resolver.ProxyConfig; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import com.google.common.collect.Streams; -import io.soabase.recordbuilder.core.RecordBuilder; import io.vavr.control.Try; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -54,77 +44,39 @@ */ public class GitHubModelSource implements ModelSource { private static final Logger LOG = LoggerFactory.getLogger( GitHubModelSource.class ); - private static final String GITHUB_BASE = "https://github.com"; - private static final String GITHUB_ZIP_URL = GITHUB_BASE + "/%s/%s/archive/refs/heads/%s.zip"; - private final Config config; + private final ProxyConfig proxyConfig; File repositoryZipFile = null; private List files = null; - protected final String orgName; - protected final String repositoryName; - protected final String branchName; + protected final GithubRepository repository; protected final String directory; /** * Constructor. * - * @param repositoryName the repository name in the form 'org/reponame' - * @param branchName the branch name - * @param directory the directory in the repository in the form 'some/directory', empty for root - * @param config sets proxy configuration + * @param repository the repository this model sources refers to + * @param directory the relative directory inside the repository + * @param proxyConfig the proxy configuration */ - public GitHubModelSource( final String repositoryName, final String branchName, final String directory, final Config config ) { - final String[] split = repositoryName.split( "/" ); - orgName = split[0]; - this.repositoryName = split[1]; - this.branchName = branchName; - this.directory = directory.endsWith( "/" ) ? directory.substring( 0, directory.length() - 1 ) : directory; - this.config = config; + public GitHubModelSource( final GithubRepository repository, final String directory, final ProxyConfig proxyConfig ) { + this.repository = repository; + this.directory = Optional.ofNullable( directory ).map( d -> + d.endsWith( "/" ) ? d.substring( 0, d.length() - 1 ) : d ).orElse( "" ); + this.proxyConfig = proxyConfig; } /** * Constructor. Proxy settings are automatically detected. * - * @param repositoryName the repository name in the form 'org/reponame' - * @param branchName the branch name - * @param directory the directory in the repository in the form 'some/directory', empty for root + * @param repository the repository this model sources refers to + * @param directory the relative directory inside the repository */ - public GitHubModelSource( final String repositoryName, final String branchName, final String directory ) { - this( repositoryName, branchName, directory, detectProxySettings() ); + public GitHubModelSource( final GithubRepository repository, final String directory ) { + this( repository, directory, ProxyConfig.detectProxySettings() ); } - private static Config detectProxySettings() { - final String envProxy = System.getenv( "http_proxy" ); - if ( envProxy != null && System.getProperty( "http.proxyHost" ) == null ) { - final Pattern proxyPattern = Pattern.compile( "http://([^:]*):(\\d+)/?" ); - final Matcher matcher = proxyPattern.matcher( envProxy ); - if ( matcher.matches() ) { - final String host = matcher.group( 1 ); - final String port = matcher.group( 2 ); - System.setProperty( "http.proxyHost", host ); - System.setProperty( "http.proxyPort", port ); - } else { - LOG.debug( "The value of the 'http_proxy' environment variable is malformed, ignoring: {}", envProxy ); - } - } - - final String host = System.getProperty( "http.proxyHost" ); - final String port = System.getProperty( "http.proxyPort" ); - if ( host != null && port != null ) { - return GitHubModelSourceConfigBuilder.builder() - .proxy( ProxySelector.of( new InetSocketAddress( host, Integer.parseInt( port ) ) ) ) - .build(); - } - return GitHubModelSourceConfigBuilder.builder().build(); - } - - @RecordBuilder - public record Config( - ProxySelector proxy, - Authenticator authenticator - ) {} - private String sourceUrl( final String filename ) { - return GITHUB_BASE + "/" + orgName + "/" + repositoryName + "/blob/" + branchName + "/" + filename; + return "https://%s/%s/%s/blob/%s/%s".formatted( repository.host(), repository.owner(), repository.repository(), + repository.branchOrTag().name(), filename ); } private void init() { @@ -132,7 +84,7 @@ private void init() { final Path tempDirectory = Files.createTempDirectory( "esmf" ); final File outputZipFile = tempDirectory.resolve( ZonedDateTime.now( ZoneId.systemDefault() ) .format( DateTimeFormatter.ofPattern( "uuuu-MM-dd.HH.mm.ss" ) ) + ".zip" ).toFile(); - repositoryZipFile = downloadFile( new URL( String.format( GITHUB_ZIP_URL, orgName, repositoryName, branchName ) ), outputZipFile ); + repositoryZipFile = new Download( proxyConfig ).downloadFile( repository.zipLocation(), outputZipFile ); loadFilesFromZip(); final boolean packageIsDeleted = outputZipFile.delete() && tempDirectory.toFile().delete(); if ( packageIsDeleted ) { @@ -143,26 +95,6 @@ private void init() { } } - File downloadFile( final URL fileUrl, final File outputFile ) { - try ( final FileOutputStream outputStream = new FileOutputStream( outputFile ) ) { - final HttpClient.Builder clientBuilder = HttpClient.newBuilder() - .version( HttpClient.Version.HTTP_1_1 ) - .followRedirects( HttpClient.Redirect.ALWAYS ) - .connectTimeout( Duration.ofSeconds( 10 ) ); - Optional.ofNullable( config.proxy() ).ifPresent( clientBuilder::proxy ); - Optional.ofNullable( config.authenticator() ).ifPresent( clientBuilder::authenticator ); - final HttpClient client = clientBuilder.build(); - final HttpRequest request = HttpRequest.newBuilder().uri( fileUrl.toURI() ).build(); - final HttpResponse response = client.send( request, HttpResponse.BodyHandlers.ofByteArray() ); - outputStream.write( response.body() ); - } catch ( final URISyntaxException | IOException | InterruptedException exception ) { - throw new GitHubResolverException( "Could not write file " + outputFile, exception ); - } - - LOG.info( "Downloaded {} repository to local file {}", fileUrl.getPath(), outputFile ); - return outputFile; - } - /** * Loads the AspectModelFiles from the downloaded .zip */ @@ -170,11 +102,12 @@ void loadFilesFromZip() { try ( final ZipFile zipFile = new ZipFile( repositoryZipFile ) ) { LOG.debug( "Loading Aspect Model files from {}", repositoryZipFile ); files = Streams.stream( zipFile.entries().asIterator() ).flatMap( zipEntry -> { - final String pathPrefix = repositoryName + "-" + branchName + "/" + directory; + final String pathPrefix = repository.repository() + "-" + repository.branchOrTag().name() + "/" + directory; if ( !zipEntry.getName().startsWith( pathPrefix ) || !zipEntry.getName().endsWith( ".ttl" ) ) { return Stream.empty(); } - final String path = zipEntry.getName().substring( pathPrefix.length() + 1 ); + final int offset = pathPrefix.endsWith( "/" ) ? 0 : 1; + final String path = zipEntry.getName().substring( pathPrefix.length() + offset ); // Path should now look like org.eclipse.esmf.example/1.0.0/File.ttl final String[] parts = path.split( "/" ); if ( parts.length != 3 || AspectModelUrn.from( "urn:samm:" + parts[0] + ":" + parts[1] ).isFailure() ) { diff --git a/core/esmf-aspect-model-github-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategy.java b/core/esmf-aspect-model-github-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategy.java index abc09c7cc..b460322d3 100644 --- a/core/esmf-aspect-model-github-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategy.java +++ b/core/esmf-aspect-model-github-resolver/src/main/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategy.java @@ -14,46 +14,54 @@ package org.eclipse.esmf.aspectmodel.resolver.github; import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.resolver.GithubRepository; +import org.eclipse.esmf.aspectmodel.resolver.ProxyConfig; import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy; import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategySupport; import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + /** * A resolution strategy to retrieve files from a remote GitHub repository */ public class GitHubStrategy extends GitHubModelSource implements ResolutionStrategy { + private static final Logger LOG = LoggerFactory.getLogger( GitHubStrategy.class ); + /** * Constructor. * - * @param repositoryName the repository name in the form 'org/reponame' - * @param branchName the branch name - * @param directory the directory in the repository in the form 'some/directory', empty for root - * @param config sets proxy configuration + * @param repository the GitHub repository + * @param directory the relative directory inside the repository + * @param proxyConfig the proxy configuration */ - public GitHubStrategy( final String repositoryName, final String branchName, final String directory, final Config config ) { - super( repositoryName, branchName, directory, config ); + public GitHubStrategy( final GithubRepository repository, final String directory, final ProxyConfig proxyConfig ) { + super( repository, directory, proxyConfig ); } /** * Constructor. Proxy settings are automatically detected. * - * @param repositoryName the repository name in the form 'org/reponame' - * @param branchName the branch name - * @param directory the directory in the repository in the form 'some/directory', empty for root + * @param repository the GitHub repository + * @param directory the relative directory inside the repository */ - public GitHubStrategy( final String repositoryName, final String branchName, final String directory ) { - super( repositoryName, branchName, directory ); + public GitHubStrategy( final GithubRepository repository, final String directory ) { + super( repository, directory ); } @Override public AspectModelFile apply( final AspectModelUrn aspectModelUrn, final ResolutionStrategySupport resolutionStrategySupport ) throws ModelResolutionException { return loadContentsForNamespace( aspectModelUrn ) + .peek( aspectModelFile -> { + LOG.debug( "Found aspect model file at {} ", aspectModelFile.sourceLocation() ); + } ) .filter( file -> resolutionStrategySupport.containsDefinition( file, aspectModelUrn ) ) .findFirst() .orElseThrow( () -> new ModelResolutionException( "No model file containing " + aspectModelUrn - + " could be found in GitHub repository: " + orgName + "/" + repositoryName - + " in branch " + branchName ) ); + + " could be found in GitHub repository: " + repository.owner() + "/" + repository.repository() + + " in branch/tag " + repository.branchOrTag().name() ) ); } } diff --git a/core/esmf-aspect-model-github-resolver/src/test/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategyTest.java b/core/esmf-aspect-model-github-resolver/src/test/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategyTest.java index 40aec9ed6..112870440 100644 --- a/core/esmf-aspect-model-github-resolver/src/test/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategyTest.java +++ b/core/esmf-aspect-model-github-resolver/src/test/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategyTest.java @@ -29,8 +29,11 @@ import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; +import org.eclipse.esmf.aspectmodel.resolver.GithubRepository; import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy; +import org.eclipse.esmf.aspectmodel.shacl.violation.Violation; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.aspectmodel.validation.services.AspectModelValidator; import org.eclipse.esmf.metamodel.AspectModel; import org.apache.commons.io.IOUtils; @@ -40,6 +43,7 @@ public class GitHubStrategyTest { Path outputDirectory = null; + private final GithubRepository esmfSdk = new GithubRepository( "eclipse-esmf", "esmf-sdk", new GithubRepository.Branch( "main" ) ); @BeforeEach void beforeEach() throws IOException { @@ -69,8 +73,7 @@ void afterEach() { @Test void testParseGitHubZipExport() throws IOException { - final GitHubModelSource modelSource = new GitHubModelSource( "eclipse-esmf/esmf-sdk", "main", - "core/esmf-test-aspect-models/src/main/resources/valid/" ); + final GitHubModelSource modelSource = new GitHubModelSource( esmfSdk, "core/esmf-test-aspect-models/src/main/resources/valid/" ); final File tempFile = outputDirectory.resolve( "temp.zip" ).toFile(); final InputStream testZipFileInputStream = getClass().getClassLoader().getResourceAsStream( "github-export.zip" ); @@ -86,8 +89,7 @@ void testParseGitHubZipExport() throws IOException { @Test void testDownloadAndLoadZip() { - final GitHubModelSource modelSource = new GitHubModelSource( "eclipse-esmf/esmf-sdk", "main", - "core/esmf-test-aspect-models/src/main/resources/valid/" ); + final GitHubModelSource modelSource = new GitHubModelSource( esmfSdk, "core/esmf-test-aspect-models/src/main/resources/valid/" ); final List files = modelSource.loadContents().toList(); assertThat( files ).isNotEmpty(); assertThat( files ).allMatch( file -> file.sourceLocation().isPresent() @@ -102,7 +104,7 @@ void testDownloadAndLoadZip() { @Test void testResolveFromZipFile() throws IOException { - final ResolutionStrategy gitHubStrategy = new GitHubStrategy( "eclipse-esmf/esmf-sdk", "main", + final ResolutionStrategy gitHubStrategy = new GitHubStrategy( esmfSdk, "core/esmf-test-aspect-models/src/main/resources/valid" ); final File tempFile = outputDirectory.resolve( "temp.zip" ).toFile(); @@ -124,7 +126,7 @@ void testResolveFromZipFile() throws IOException { void testGithubStrategy() { final AspectModelUrn testUrn = AspectModelUrn.fromUrn( "urn:samm:org.eclipse.esmf.test:1.0.0#Aspect" ); - final ResolutionStrategy gitHubStrategy = new GitHubStrategy( "eclipse-esmf/esmf-sdk", "main", + final ResolutionStrategy gitHubStrategy = new GitHubStrategy( esmfSdk, "core/esmf-test-aspect-models/src/main/resources/valid" ); final AspectModel result = new AspectModelLoader( gitHubStrategy ).load( testUrn ); @@ -138,4 +140,17 @@ private void inject( final GitHubModelSource gitHubModelSource, final File tempF gitHubModelSource.repositoryZipFile = tempFile; gitHubModelSource.loadFilesFromZip(); } + + @Test + void testLoadCatenaXBatteryPass() { + final AspectModelUrn batteryPassUrn = AspectModelUrn.fromUrn( "urn:samm:io.catenax.battery.battery_pass:6.0.0#BatteryPass" ); + final GithubRepository sldt = new GithubRepository( "eclipse-tractusx", "sldt-semantic-models", + new GithubRepository.Branch( "main" ) ); + final ResolutionStrategy gitHubStrategy = new GitHubStrategy( sldt, "/" ); + final AspectModel batteryPass = new AspectModelLoader( gitHubStrategy ).load( batteryPassUrn ); + + final AspectModelValidator validator = new AspectModelValidator(); + final List violations = validator.validateModel( batteryPass ); + assertThat( violations ).isEmpty(); + } } diff --git a/core/esmf-aspect-model-urn/src/main/java/org/eclipse/esmf/aspectmodel/urn/AspectModelUrn.java b/core/esmf-aspect-model-urn/src/main/java/org/eclipse/esmf/aspectmodel/urn/AspectModelUrn.java index 99da627e1..3122e2e2c 100644 --- a/core/esmf-aspect-model-urn/src/main/java/org/eclipse/esmf/aspectmodel/urn/AspectModelUrn.java +++ b/core/esmf-aspect-model-urn/src/main/java/org/eclipse/esmf/aspectmodel/urn/AspectModelUrn.java @@ -131,8 +131,7 @@ public static AspectModelUrn fromUrn( final URI urn ) { checkUrn( numberOfUrnParts >= 5, UrnSyntaxException.URN_IS_MISSING_SECTIONS_MESSAGE ); final String protocol = urnParts.get( 0 ); - checkUrn( protocol.equalsIgnoreCase( VALID_PROTOCOL ), UrnSyntaxException.URN_INVALID_PROTOCOL_MESSAGE, - VALID_PROTOCOL ); + checkUrn( protocol.equalsIgnoreCase( VALID_PROTOCOL ), UrnSyntaxException.URN_INVALID_PROTOCOL_MESSAGE, VALID_PROTOCOL ); String namespaceIdentifier = urnParts.get( 1 ); // This is no public constant, because it's an implementation detail @@ -144,8 +143,7 @@ public static AspectModelUrn fromUrn( final URI urn ) { UrnSyntaxException.URN_INVALID_NAMESPACE_IDENTIFIER_MESSAGE, VALID_NAMESPACE_IDENTIFIER ); final String namespace = urnParts.get( NAMESPACE_INDEX ); - checkUrn( NAMESPACE_PATTERN.matcher( namespace ).matches(), - UrnSyntaxException.URN_INVALID_NAMESPACE_MESSAGE, NAMESPACE_REGEX ); + checkUrn( NAMESPACE_PATTERN.matcher( namespace ).matches(), UrnSyntaxException.URN_INVALID_NAMESPACE_MESSAGE, NAMESPACE_REGEX ); final ElementType elementType = getElementType( urnParts ); final boolean isSammUrn = isSammUrn( urn, urnParts, elementType ); From 24865dec1a11780762c1decca78536b4f6fab42a Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 24 Oct 2024 13:22:24 +0200 Subject: [PATCH 6/8] Update samm-cli commands to allow URNs and URLs in addition to files --- .../resolver/services/TurtleLoader.java | 1 - .../eclipse/esmf/aspectmodel/stats/Usage.java | 2 +- .../MetaModelVersionMigrator.java | 2 +- .../resolver/github/GitHubStrategyTest.java | 2 +- .../org/eclipse/esmf/AbstractCommand.java | 116 ++++----------- .../eclipse/esmf/AbstractInputHandler.java | 136 ++++++++++++++++++ .../esmf/AspectModelUrnInputHandler.java | 84 +++++++++++ .../org/eclipse/esmf/FileInputHandler.java | 88 ++++++++++++ .../eclipse/esmf/GitHubUrlInputHandler.java | 82 +++++++++++ .../java/org/eclipse/esmf/InputHandler.java | 58 ++++++++ .../esmf/ResolverConfigurationMixin.java | 13 +- .../eclipse/esmf/aspect/AspectCommand.java | 6 +- .../esmf/aspect/AspectPrettyPrintCommand.java | 63 ++++---- .../esmf/aspect/AspectUsageCommand.java | 20 ++- .../esmf/aspect/AspectValidateCommand.java | 9 +- .../aspect/edit/AspectEditMoveCommand.java | 30 ++-- .../edit/AspectEditNewVersionCommand.java | 5 +- .../esmf/aspect/to/AspectToAasCommand.java | 17 ++- .../aspect/to/AspectToAsyncapiCommand.java | 38 +++-- .../esmf/aspect/to/AspectToHtmlCommand.java | 26 +++- .../esmf/aspect/to/AspectToJavaCommand.java | 41 ++++-- .../esmf/aspect/to/AspectToJsonCommand.java | 15 +- .../esmf/aspect/to/AspectToJsonLdCommand.java | 15 +- .../aspect/to/AspectToJsonSchemaCommand.java | 20 ++- .../aspect/to/AspectToOpenapiCommand.java | 87 ++++++++--- .../esmf/aspect/to/AspectToPngCommand.java | 22 ++- .../esmf/aspect/to/AspectToSqlCommand.java | 41 ++++-- .../esmf/aspect/to/AspectToSvgCommand.java | 21 ++- .../esmf/MainClassProcessLauncher.java | 12 ++ .../java/org/eclipse/esmf/SammCliTest.java | 66 +++++++-- 30 files changed, 906 insertions(+), 232 deletions(-) create mode 100644 tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractInputHandler.java create mode 100644 tools/samm-cli/src/main/java/org/eclipse/esmf/AspectModelUrnInputHandler.java create mode 100644 tools/samm-cli/src/main/java/org/eclipse/esmf/FileInputHandler.java create mode 100644 tools/samm-cli/src/main/java/org/eclipse/esmf/GitHubUrlInputHandler.java create mode 100644 tools/samm-cli/src/main/java/org/eclipse/esmf/InputHandler.java diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/services/TurtleLoader.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/services/TurtleLoader.java index a30d401c6..2836e5ade 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/services/TurtleLoader.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/resolver/services/TurtleLoader.java @@ -21,7 +21,6 @@ import java.nio.charset.StandardCharsets; import java.util.Objects; import java.util.stream.Collectors; - import javax.annotation.Nullable; import org.eclipse.esmf.aspectmodel.resolver.exceptions.ParserException; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/stats/Usage.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/stats/Usage.java index 6bcdc583d..35f1f514f 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/stats/Usage.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/stats/Usage.java @@ -18,8 +18,8 @@ import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.RdfUtil; -import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.resolver.ModelSource; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import com.google.common.collect.Streams; diff --git a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/versionupdate/MetaModelVersionMigrator.java b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/versionupdate/MetaModelVersionMigrator.java index 66cdcc458..185dd6183 100644 --- a/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/versionupdate/MetaModelVersionMigrator.java +++ b/core/esmf-aspect-meta-model-java/src/main/java/org/eclipse/esmf/aspectmodel/versionupdate/MetaModelVersionMigrator.java @@ -20,8 +20,8 @@ import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.VersionNumber; -import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.resolver.exceptions.InvalidVersionException; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFile; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; import org.eclipse.esmf.aspectmodel.urn.ElementType; diff --git a/core/esmf-aspect-model-github-resolver/src/test/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategyTest.java b/core/esmf-aspect-model-github-resolver/src/test/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategyTest.java index 112870440..cbab1a467 100644 --- a/core/esmf-aspect-model-github-resolver/src/test/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategyTest.java +++ b/core/esmf-aspect-model-github-resolver/src/test/java/org/eclipse/esmf/aspectmodel/resolver/github/GitHubStrategyTest.java @@ -142,7 +142,7 @@ private void inject( final GitHubModelSource gitHubModelSource, final File tempF } @Test - void testLoadCatenaXBatteryPass() { + void testLoadCatenaxBatteryPass() { final AspectModelUrn batteryPassUrn = AspectModelUrn.fromUrn( "urn:samm:io.catenax.battery.battery_pass:6.0.0#BatteryPass" ); final GithubRepository sldt = new GithubRepository( "eclipse-tractusx", "sldt-semantic-models", new GithubRepository.Branch( "main" ) ); diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java index 719201ff5..8369cbb8f 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractCommand.java @@ -28,7 +28,6 @@ import java.util.Optional; import java.util.Set; import java.util.function.Consumer; -import java.util.stream.Collectors; import org.eclipse.esmf.aspectmodel.edit.AspectChangeManager; import org.eclipse.esmf.aspectmodel.edit.AspectChangeManagerConfig; @@ -37,113 +36,52 @@ import org.eclipse.esmf.aspectmodel.edit.ChangeReportFormatter; import org.eclipse.esmf.aspectmodel.generator.LanguageCollector; import org.eclipse.esmf.aspectmodel.generator.diagram.AspectModelDiagramGenerator; -import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; -import org.eclipse.esmf.aspectmodel.resolver.ExternalResolverStrategy; -import org.eclipse.esmf.aspectmodel.resolver.FileSystemStrategy; -import org.eclipse.esmf.aspectmodel.resolver.ModelResolutionException; -import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy; -import org.eclipse.esmf.aspectmodel.resolver.fs.StructuredModelsRoot; -import org.eclipse.esmf.aspectmodel.resolver.github.GitHubStrategy; import org.eclipse.esmf.aspectmodel.serializer.AspectSerializer; -import org.eclipse.esmf.aspectmodel.shacl.violation.Violation; -import org.eclipse.esmf.aspectmodel.validation.services.AspectModelValidator; -import org.eclipse.esmf.aspectmodel.validation.services.DetailedViolationFormatter; -import org.eclipse.esmf.aspectmodel.validation.services.ViolationFormatter; import org.eclipse.esmf.exception.CommandException; import org.eclipse.esmf.metamodel.Aspect; import org.eclipse.esmf.metamodel.AspectModel; -import io.vavr.control.Either; -import org.apache.commons.io.FilenameUtils; - @SuppressWarnings( "UseOfSystemOutOrSystemErr" ) public abstract class AbstractCommand implements Runnable { - protected Path modelsRootForFile( final File file ) { - return file.toPath().getParent().getParent().getParent(); - } + private boolean details; + private ResolverConfigurationMixin resolverConfig; - protected AspectModel loadAspectModelOrFail( final String input, final ResolverConfigurationMixin resolverConfig ) { - return loadAspectModelOrFail( input, resolverConfig, false ); + protected void setDetails( final boolean details ) { + this.details = details; } - protected File getInputFile( final String modelFileName ) { - final File inputFile = new File( modelFileName ); - return inputFile.isAbsolute() - ? inputFile - : Path.of( System.getProperty( "user.dir" ) ).resolve( inputFile.toPath() ).toFile().getAbsoluteFile(); + protected void setResolverConfig( final ResolverConfigurationMixin resolverConfig ) { + this.resolverConfig = resolverConfig; } - protected AspectModelLoader getAspectModelLoader( final Optional modelFile, final ResolverConfigurationMixin resolverConfig ) { - final List strategies = new ArrayList<>(); - if ( modelFile.isPresent() ) { - strategies.add( new FileSystemStrategy( modelsRootForFile( modelFile.get().getAbsoluteFile() ) ) ); - } else { - strategies.add( AspectModelLoader.DEFAULT_STRATEGY.get() ); - } - for ( final String modelsRoot : resolverConfig.modelsRoots ) { - strategies.add( new FileSystemStrategy( new StructuredModelsRoot( Path.of( modelsRoot ) ) ) ); - } - if ( !resolverConfig.commandLine.isBlank() ) { - strategies.add( new ExternalResolverStrategy( resolverConfig.commandLine ) ); - } - if ( resolverConfig.gitHubResolutionOptions != null && resolverConfig.gitHubResolutionOptions.enableGitHubResolution ) { - strategies.add( new GitHubStrategy( - resolverConfig.gitHubResolutionOptions.gitHubName, - resolverConfig.gitHubResolutionOptions.gitHubBranch, - resolverConfig.gitHubResolutionOptions.gitHubDirectory ) ); - } - return new AspectModelLoader( strategies ); + protected boolean inputIsFile( final String input ) { + return new File( input ).exists(); } - protected AspectModel loadAspectModelOrFail( final File modelFile, final ResolverConfigurationMixin resolverConfig, - final boolean details ) { - final File absoluteFile = modelFile.getAbsoluteFile(); - - final Either, AspectModel> validModelOrViolations = new AspectModelValidator().loadModel( () -> - getAspectModelLoader( Optional.of( modelFile ), resolverConfig ).load( absoluteFile ) ); - if ( validModelOrViolations.isLeft() ) { - final List violations = validModelOrViolations.getLeft(); - if ( details ) { - System.out.println( new DetailedViolationFormatter().apply( violations ) ); - } else { - System.out.println( new ViolationFormatter().apply( violations ) ); - } - System.exit( 1 ); - return null; - } - - return validModelOrViolations.get(); + protected File absoluteFile( final File inputFile ) { + return inputFile.isAbsolute() + ? inputFile + : Path.of( System.getProperty( "user.dir" ) ).resolve( inputFile.toPath() ).toFile().getAbsoluteFile(); } - protected AspectModel loadAspectModelOrFail( final String modelFileName, final ResolverConfigurationMixin resolverConfig, - final boolean details ) { - return loadAspectModelOrFail( getInputFile( modelFileName ), resolverConfig, details ); + protected InputHandler getInputHandler( final File input ) { + return new FileInputHandler( input.getAbsolutePath(), resolverConfig, details ); } - protected Aspect loadAspectOrFail( final String modelFileName, final ResolverConfigurationMixin resolverConfig ) { - final File inputFile = new File( modelFileName ); - final AspectModel aspectModel = loadAspectModelOrFail( modelFileName, resolverConfig ); - final List aspects = aspectModel.aspects(); - if ( aspects.isEmpty() ) { - throw new CommandException( new ModelResolutionException( "No Aspects were found in the model" ) ); - } - if ( aspects.size() == 1 ) { - return aspectModel.aspect(); + protected InputHandler getInputHandler( final String input ) { + if ( FileInputHandler.appliesToInput( input ) ) { + return new FileInputHandler( input, resolverConfig, details ); + } else if ( AspectModelUrnInputHandler.appliesToInput( input ) ) { + return new AspectModelUrnInputHandler( input, resolverConfig, details ); + } else if ( GitHubUrlInputHandler.appliesToInput( input ) ) { + return new GitHubUrlInputHandler( input, resolverConfig, details ); } - final String expectedAspectName = FilenameUtils.removeExtension( inputFile.getName() ); - return aspectModel.aspects().stream() - .filter( aspect -> aspect.getName().equals( expectedAspectName ) ) - .findFirst() - .orElseThrow( () -> new ModelResolutionException( - "Found multiple Aspects in the file " + inputFile.getAbsolutePath() + ", but none is called '" - + expectedAspectName + "': " + aspects.stream().map( Aspect::getName ) - .collect( Collectors.joining( ", " ) ) ) ); + throw new CommandException( "Can not find file: " + input ); } - protected void generateDiagram( final String inputFileName, final AspectModelDiagramGenerator.Format targetFormat, - final String outputFileName, - final String languageTag, final ResolverConfigurationMixin resolverConfig ) throws IOException { - final Aspect aspect = loadAspectOrFail( inputFileName, resolverConfig ); + protected void generateDiagram( final String input, final AspectModelDiagramGenerator.Format targetFormat, + final String outputFileName, final String languageTag ) throws IOException { + final Aspect aspect = getInputHandler( input ).loadAspect(); final AspectModelDiagramGenerator generator = new AspectModelDiagramGenerator( aspect ); final Set targetFormats = new HashSet<>(); targetFormats.add( targetFormat ); @@ -173,8 +111,8 @@ protected OutputStream getStreamForFile( final String outputFileName ) { ensureDirectoryExists( outputFileName ); try { return new FileOutputStream( outputFileName ); - } catch ( final FileNotFoundException e ) { - throw new CommandException( e ); + } catch ( final FileNotFoundException exception ) { + throw new CommandException( exception ); } } else { return new ProtectedOutputStream( System.out ); diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractInputHandler.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractInputHandler.java new file mode 100644 index 000000000..6d7ac0212 --- /dev/null +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/AbstractInputHandler.java @@ -0,0 +1,136 @@ +/* + * 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; + +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; + +import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; +import org.eclipse.esmf.aspectmodel.resolver.ExternalResolverStrategy; +import org.eclipse.esmf.aspectmodel.resolver.FileSystemStrategy; +import org.eclipse.esmf.aspectmodel.resolver.GithubRepository; +import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy; +import org.eclipse.esmf.aspectmodel.resolver.exceptions.ModelResolutionException; +import org.eclipse.esmf.aspectmodel.resolver.fs.StructuredModelsRoot; +import org.eclipse.esmf.aspectmodel.resolver.github.GitHubStrategy; +import org.eclipse.esmf.aspectmodel.shacl.violation.Violation; +import org.eclipse.esmf.aspectmodel.validation.services.AspectModelValidator; +import org.eclipse.esmf.aspectmodel.validation.services.DetailedViolationFormatter; +import org.eclipse.esmf.aspectmodel.validation.services.ViolationFormatter; +import org.eclipse.esmf.exception.CommandException; +import org.eclipse.esmf.metamodel.Aspect; +import org.eclipse.esmf.metamodel.AspectModel; + +import io.vavr.control.Either; + +/** + * Base functionality for all InputHandler implementations + */ +@SuppressWarnings( "UseOfSystemOutOrSystemErr" ) +public abstract class AbstractInputHandler implements InputHandler { + protected final String input; + protected final ResolverConfigurationMixin resolverConfig; + protected final boolean details; + + public AbstractInputHandler( final String input, final ResolverConfigurationMixin resolverConfig, final boolean details ) { + this.input = input; + this.resolverConfig = resolverConfig; + this.details = details; + } + + /** + * Returns the list of resolution strategies specific to this input type + * + * @return the resolution strategies + */ + protected abstract List resolutionStrategies(); + + /** + * Returns the expected Aspect name depending on the input, e.g. the file base name. + * + * @return the expected Aspect name + */ + protected abstract String expectedAspectName(); + + protected List configuredStrategies() { + final List strategies = new ArrayList<>(); + if ( resolverConfig == null ) { + return strategies; + } + final List modelsRoots = resolverConfig.modelsRoots == null + ? List.of() + : resolverConfig.modelsRoots; + for ( final String modelsRoot : modelsRoots ) { + strategies.add( new FileSystemStrategy( new StructuredModelsRoot( Path.of( modelsRoot ) ) ) ); + } + if ( resolverConfig.commandLine != null && !resolverConfig.commandLine.isBlank() ) { + strategies.add( new ExternalResolverStrategy( resolverConfig.commandLine ) ); + } + if ( resolverConfig.gitHubResolutionOptions != null && resolverConfig.gitHubResolutionOptions.gitHubName != null ) { + final String[] parts = resolverConfig.gitHubResolutionOptions.gitHubName.split( "/" ); + final String owner = parts[0]; + final String repositoryName = parts[1]; + final GithubRepository.Ref branchOrTag = resolverConfig.gitHubResolutionOptions.gitHubTag != null + ? new GithubRepository.Tag( resolverConfig.gitHubResolutionOptions.gitHubTag ) + : new GithubRepository.Branch( resolverConfig.gitHubResolutionOptions.gitHubBranch ); + final GithubRepository repository = new GithubRepository( owner, repositoryName, branchOrTag ); + strategies.add( new GitHubStrategy( repository, resolverConfig.gitHubResolutionOptions.gitHubDirectory ) ); + } + return strategies; + } + + @Override + public AspectModelLoader aspectModelLoader() { + return new AspectModelLoader( resolutionStrategies() ); + } + + protected AspectModel applyAspectModelLoader( final Function loader ) { + final Either, AspectModel> validModelOrViolations = new AspectModelValidator().loadModel( () -> + loader.apply( aspectModelLoader() ) ); + if ( validModelOrViolations.isLeft() ) { + final List violations = validModelOrViolations.getLeft(); + if ( details ) { + System.out.println( new DetailedViolationFormatter().apply( violations ) ); + } else { + System.out.println( new ViolationFormatter().apply( violations ) ); + } + System.exit( 1 ); + return null; + } + return validModelOrViolations.get(); + } + + @Override + public Aspect loadAspect() { + final AspectModel aspectModel = loadAspectModel(); + + final String expectedAspectName = expectedAspectName(); + if ( aspectModel.aspects().isEmpty() ) { + throw new CommandException( new ModelResolutionException( "No Aspects were found in the model" ) ); + } + if ( aspectModel.aspects().size() == 1 ) { + return aspectModel.aspect(); + } + return aspectModel.aspects().stream() + .filter( aspect -> aspect.getName().equals( expectedAspectName ) ) + .findFirst() + .orElseThrow( () -> new ModelResolutionException( + "Found multiple Aspects in the input " + input + ", but none is called '" + + expectedAspectName + "': " + aspectModel.aspects().stream().map( Aspect::getName ) + .collect( Collectors.joining( ", " ) ) ) ); + } +} diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/AspectModelUrnInputHandler.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/AspectModelUrnInputHandler.java new file mode 100644 index 000000000..d43382f62 --- /dev/null +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/AspectModelUrnInputHandler.java @@ -0,0 +1,84 @@ +/* + * 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; + +import java.net.URI; +import java.util.List; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.exception.CommandException; +import org.eclipse.esmf.metamodel.AspectModel; +import org.eclipse.esmf.metamodel.ModelElement; + +import io.vavr.control.Try; + +/** + * The AspectModelUrnInputHandler knows how to load Aspect Models if the given input is an Aspect Model URN + */ +public class AspectModelUrnInputHandler extends AbstractInputHandler { + private final AspectModelUrn urn; + + public AspectModelUrnInputHandler( final String input, final ResolverConfigurationMixin resolverConfig, final boolean details ) { + super( input, resolverConfig, details ); + urn = urnFromInput( input ); + } + + private AspectModelUrn urnFromInput( final String input ) { + return AspectModelUrn.from( input ).getOrElseThrow( cause -> { + throw new CommandException( "Aspect Model URN " + input + " is invalid", cause ); + } ); + } + + @Override + protected List resolutionStrategies() { + if ( !urn.getName().isEmpty() + && resolverConfig != null + && resolverConfig.gitHubResolutionOptions != null + && resolverConfig.gitHubResolutionOptions.gitHubName == null + && ( resolverConfig.modelsRoots == null || resolverConfig.modelsRoots.isEmpty() ) + ) { + throw new CommandException( "When resolving a URN, at least one models root directory or GitHub repository must be set" ); + } + return configuredStrategies(); + } + + @Override + protected String expectedAspectName() { + return urn.getName(); + } + + public static boolean appliesToInput( final String input ) { + return input.startsWith( "urn:samm:" ); + } + + @Override + public AspectModel loadAspectModel() { + return applyAspectModelLoader( aspectModelLoader -> aspectModelLoader.load( urn ) ); + } + + @Override + public URI inputUri() { + return URI.create( urn.toString() ); + } + + @Override + public AspectModelFile loadAspectModelFile() { + final AspectModel aspectModel = loadAspectModel(); + return Try.of( () -> aspectModel.getElementByUrn( urn ) ) + .map( ModelElement::getSourceFile ) + .getOrElseThrow( () -> new CommandException( "Could not load: " + input ) ); + } +} diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/FileInputHandler.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/FileInputHandler.java new file mode 100644 index 000000000..055c15433 --- /dev/null +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/FileInputHandler.java @@ -0,0 +1,88 @@ +/* + * 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; + +import java.io.File; +import java.net.URI; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.resolver.FileSystemStrategy; +import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy; +import org.eclipse.esmf.exception.CommandException; +import org.eclipse.esmf.metamodel.AspectModel; + +import org.apache.commons.io.FilenameUtils; + +/** + * The FileInputHandler knows how to load Aspect Models if the given input is a local file + */ +public class FileInputHandler extends AbstractInputHandler { + private final File inputFile; + + FileInputHandler( final String input, final ResolverConfigurationMixin resolverConfig, final boolean details ) { + super( input, resolverConfig, details ); + inputFile = absoluteFile( new File( input ) ); + } + + @Override + public AspectModel loadAspectModel() { + return applyAspectModelLoader( aspectModelLoader -> aspectModelLoader.load( inputFile ) ); + } + + @Override + public URI inputUri() { + return absoluteFile( new File( input ) ).toURI(); + } + + private static File absoluteFile( final File inputFile ) { + return inputFile.isAbsolute() + ? inputFile + : Path.of( System.getProperty( "user.dir" ) ).resolve( inputFile.toPath() ).toFile().getAbsoluteFile(); + } + + @Override + protected List resolutionStrategies() { + final List strategies = new ArrayList<>(); + final File file = absoluteFile( inputFile ); + strategies.add( new FileSystemStrategy( modelsRootForFile( file ) ) ); + strategies.addAll( configuredStrategies() ); + return strategies; + } + + public static Path modelsRootForFile( final File file ) { + return file.toPath().getParent().getParent().getParent(); + } + + @Override + protected String expectedAspectName() { + return FilenameUtils.removeExtension( inputFile.getName() ); + } + + public static boolean appliesToInput( final String input ) { + return absoluteFile( new File( input ) ).exists(); + } + + @Override + public AspectModelFile loadAspectModelFile() { + final AspectModel aspectModel = loadAspectModel(); + return aspectModel.files() + .stream() + .filter( file -> file.sourceLocation().map( location -> location.equals( inputUri() ) ).orElse( false ) ) + .findFirst() + .orElseThrow( () -> new CommandException( "Could not load: " + inputUri() ) ); + } +} diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/GitHubUrlInputHandler.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/GitHubUrlInputHandler.java new file mode 100644 index 000000000..154247d2b --- /dev/null +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/GitHubUrlInputHandler.java @@ -0,0 +1,82 @@ +/* + * 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; + +import java.net.URI; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.resolver.GitHubFileLocation; +import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy; +import org.eclipse.esmf.aspectmodel.resolver.github.GitHubStrategy; +import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; +import org.eclipse.esmf.exception.CommandException; +import org.eclipse.esmf.metamodel.AspectModel; + +/** + * The GitHubUrlInputHandler knows how to load an Aspect Model from a given URL which points to a location in a models root + * stored in a GitHub repository + */ +public class GitHubUrlInputHandler extends AbstractInputHandler { + private final String url; + private final GitHubFileLocation location; + + public GitHubUrlInputHandler( final String input, final ResolverConfigurationMixin resolverConfig, final boolean details ) { + super( input, resolverConfig, details ); + location = GitHubFileLocation.parse( input ).orElseThrow( () -> + new CommandException( "Input URL format is not supported: " + input ) ); + url = input; + } + + @Override + protected List resolutionStrategies() { + final List strategies = new ArrayList<>(); + strategies.add( new GitHubStrategy( location.repositoryLocation(), location.directory() ) ); + strategies.addAll( configuredStrategies() ); + return strategies; + } + + @Override + protected String expectedAspectName() { + return location.filename().replace( ".ttl", "" ); + } + + public static boolean appliesToInput( final String input ) { + return input.startsWith( "https://github.com/" ); + } + + @Override + public AspectModel loadAspectModel() { + final AspectModelUrn urn = AspectModelUrn.from( + "urn:samm:%s:%s#%s".formatted( location.namespaceMainPart(), location.version(), expectedAspectName() ) ) + .getOrElseThrow( () -> new CommandException( "Could not construct valid Aspect Model URN from input URL: " + url ) ); + return aspectModelLoader().load( urn ); + } + + @Override + public URI inputUri() { + return URI.create( url ); + } + + @Override + public AspectModelFile loadAspectModelFile() { + final AspectModel aspectModel = loadAspectModel(); + return aspectModel.files() + .stream() + .filter( file -> file.sourceLocation().map( location -> location.equals( inputUri() ) ).orElse( false ) ) + .findFirst() + .orElseThrow( () -> new CommandException( "Could not load: " + inputUri() ) ); + } +} diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/InputHandler.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/InputHandler.java new file mode 100644 index 000000000..918018667 --- /dev/null +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/InputHandler.java @@ -0,0 +1,58 @@ +/* + * 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; + +import java.net.URI; +import java.util.NoSuchElementException; + +import org.eclipse.esmf.aspectmodel.AspectModelFile; +import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; +import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy; +import org.eclipse.esmf.metamodel.Aspect; +import org.eclipse.esmf.metamodel.AspectModel; + +/** + * An InputHandler knows how to load Aspect Models, Aspects etc. depending on a certain type of given input. + */ +public interface InputHandler { + AspectModel loadAspectModel(); + + /** + * Loads the single Aspect given in the input if there is one, otherwise will return a {@link NoSuchElementException}. + * + * @return the Aspect + */ + Aspect loadAspect(); + + /** + * Loads the specific file given by the input + * + * @return the AspectModelFile + */ + AspectModelFile loadAspectModelFile(); + + /** + * Returns the AspectModelLoader initialized with the {@link ResolutionStrategy}s according to the input + * + * @return the AspectModelLoader + */ + AspectModelLoader aspectModelLoader(); + + /** + * Returns the canonical URI representation for the input source + * + * @return the URI representing the input location + */ + URI inputUri(); +} diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/ResolverConfigurationMixin.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/ResolverConfigurationMixin.java index 6aeb31311..61076ea93 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/ResolverConfigurationMixin.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/ResolverConfigurationMixin.java @@ -39,13 +39,7 @@ public static class GitHubResolutionOptions { @CommandLine.Option( names = { "--github", "-gh" }, required = true, - description = "Enable loading Aspect Models from GitHub" ) - public boolean enableGitHubResolution = false; - - @CommandLine.Option( - names = { "--github-name", "-ghn" }, - required = true, - description = "Set the GitHub repository name. Example: eclipse-esmf/esmf-sdk." ) + description = "Enable loading Aspect Models from GitHub and set the GitHub repository name. Example: eclipse-esmf/esmf-sdk." ) public String gitHubName; @CommandLine.Option( @@ -57,5 +51,10 @@ public static class GitHubResolutionOptions { names = { "--github-branch", "-ghb" }, description = "Set the GitHub branch (default: ${DEFAULT-VALUE}" ) public String gitHubBranch = "main"; + + @CommandLine.Option( + names = { "--github-tag", "-ght" }, + description = "Set the GitHub tag (default: ${DEFAULT-VALUE}" ) + public String gitHubTag = null; } } diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectCommand.java index 3734e3108..df338f1c6 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectCommand.java @@ -40,7 +40,11 @@ public class AspectCommand extends AbstractCommand { @CommandLine.Mixin private LoggingMixin loggingMixin; - @CommandLine.Parameters( paramLabel = "INPUT", description = "Input file name of the Aspect Model .ttl file", arity = "1", index = "0" ) + @CommandLine.Parameters( + paramLabel = "INPUT", + description = "Input Aspect Model file, URN or URL", + arity = "1", + index = "0" ) private String input; public String getInput() { diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java index aefb27543..dcd58362d 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectPrettyPrintCommand.java @@ -14,19 +14,16 @@ package org.eclipse.esmf.aspect; import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; +import java.io.IOException; import java.io.OutputStream; -import java.io.PrintWriter; -import java.net.URI; +import java.nio.charset.StandardCharsets; import org.eclipse.esmf.AbstractCommand; import org.eclipse.esmf.LoggingMixin; import org.eclipse.esmf.ResolverConfigurationMixin; import org.eclipse.esmf.aspectmodel.AspectModelFile; -import org.eclipse.esmf.aspectmodel.serializer.PrettyPrinter; +import org.eclipse.esmf.aspectmodel.serializer.AspectSerializer; import org.eclipse.esmf.exception.CommandException; -import org.eclipse.esmf.metamodel.AspectModel; import picocli.CommandLine; @@ -46,41 +43,51 @@ public class AspectPrettyPrintCommand extends AbstractCommand { @CommandLine.Mixin private ResolverConfigurationMixin resolverConfiguration; - @CommandLine.Option( names = { "--output", "-o" }, description = "Output file path (default: stdout)" ) + @CommandLine.Option( + names = { "--output", "-o" }, + description = "Output file path (default: stdout)" ) String outputFilePath = "-"; - @CommandLine.Option( names = { "--overwrite", "-w" }, description = "Overwrite the input file" ) + @CommandLine.Option( + names = { "--overwrite", "-w" }, + description = "Overwrite the input file" ) boolean overwrite; + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--details" }, + description = "Print detailed reports on errors" ) + private boolean details = false; + @CommandLine.ParentCommand private AspectCommand parentCommand; + @SuppressWarnings( "UseOfSystemOutOrSystemErr" ) @Override public void run() { - final File inputFile = new File( parentCommand.getInput() ).getAbsoluteFile(); - final AspectModel aspectModel = loadAspectModelOrFail( parentCommand.getInput(), resolverConfiguration ); + setDetails( details ); + setResolverConfig( resolverConfiguration ); - for ( final AspectModelFile sourceFile : aspectModel.files() ) { - if ( !sourceFile.sourceLocation().map( uri -> uri.equals( inputFile.toURI() ) ).orElse( false ) ) { - continue; - } + final String input = parentCommand.getInput(); + final AspectModelFile aspectModelFile = getInputHandler( input ).loadAspectModelFile(); - OutputStream outputStream = null; - if ( overwrite ) { - final URI fileUri = sourceFile.sourceLocation().orElseThrow(); - try { - outputStream = new FileOutputStream( new File( fileUri ) ); - } catch ( final FileNotFoundException exception ) { - throw new CommandException( "Can not write to " + fileUri ); - } - } - if ( outputStream == null ) { - outputStream = getStreamForFile( outputFilePath ); + if ( outputFilePath.equals( "-" ) && !overwrite ) { + final String formattedModel = AspectSerializer.INSTANCE.aspectModelFileToString( aspectModelFile ); + System.out.println( formattedModel ); + } else if ( overwrite ) { + AspectSerializer.INSTANCE.write( aspectModelFile ); + } else { + final File inputFile = absoluteFile( new File( aspectModelFile.sourceLocation().orElseThrow() ) ); + final File outputFile = absoluteFile( new File( outputFilePath ) ); + if ( inputFile.equals( outputFile ) ) { + throw new CommandException( "Can't overwrite existing file. To force overwrite, use --overwrite." ); } - try ( final PrintWriter printWriter = new PrintWriter( outputStream ) ) { - new PrettyPrinter( sourceFile, printWriter ).print(); - printWriter.flush(); + final String formattedModel = AspectSerializer.INSTANCE.aspectModelFileToString( aspectModelFile ); + try ( final OutputStream out = getStreamForFile( outputFilePath ) ) { + out.write( formattedModel.getBytes( StandardCharsets.UTF_8 ) ); + } catch ( final IOException exception ) { + throw new CommandException( "Could not write to output file" ); } } } diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectUsageCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectUsageCommand.java index 3aa9db97f..ebb950f7a 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectUsageCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectUsageCommand.java @@ -13,14 +13,13 @@ package org.eclipse.esmf.aspect; -import java.io.File; import java.net.URI; import java.util.Arrays; import java.util.Comparator; import java.util.List; -import java.util.Optional; import org.eclipse.esmf.AbstractCommand; +import org.eclipse.esmf.InputHandler; import org.eclipse.esmf.LoggingMixin; import org.eclipse.esmf.ResolverConfigurationMixin; import org.eclipse.esmf.aspectmodel.AspectModelFile; @@ -48,6 +47,12 @@ public class AspectUsageCommand extends AbstractCommand { @CommandLine.ParentCommand public AspectCommand parentCommand; + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--details" }, + description = "Print detailed reports on errors" ) + private boolean details = false; + @CommandLine.Mixin private LoggingMixin loggingMixin; @@ -56,15 +61,18 @@ public class AspectUsageCommand extends AbstractCommand { @Override public void run() { + setDetails( details ); + setResolverConfig( resolverConfiguration ); + final String input = parentCommand.getInput(); - final Optional inputFile = Optional.of( new File( input ) ).filter( File::exists ); - final AspectModelLoader aspectModelLoader = getAspectModelLoader( inputFile, resolverConfiguration ); + final InputHandler inputHandler = getInputHandler( input ); + final AspectModelLoader aspectModelLoader = inputHandler.aspectModelLoader(); final Usage usage = new Usage( aspectModelLoader ); final Try inputAspectModelUrn = AspectModelUrn.from( input ); final List references = inputAspectModelUrn.map( usage::referencesTo ) .getOrElse( () -> { - final URI targetUri = inputFile.map( File::toURI ).orElse( URI.create( input.replace( "\\", "/" ) ) ); + final URI targetUri = inputHandler.inputUri(); final AspectModelFile fileToCheck = aspectModelLoader.loadContents() .filter( file -> file.sourceLocation().map( targetUri::equals ).orElse( false ) ) .findFirst() @@ -78,7 +86,7 @@ public void run() { && resolverConfiguration != null && resolverConfiguration.modelsRoots.isEmpty() && ( resolverConfiguration.gitHubResolutionOptions == null - || !resolverConfiguration.gitHubResolutionOptions.enableGitHubResolution ) ) { + || resolverConfiguration.gitHubResolutionOptions.gitHubName == null ) ) { System.out.println( "Did you forget to set a models root or GitHub resolution?" ); } System.exit( 0 ); diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectValidateCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectValidateCommand.java index 674478689..c178fc6db 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectValidateCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/AspectValidateCommand.java @@ -51,12 +51,17 @@ public class AspectValidateCommand extends AbstractCommand { private ResolverConfigurationMixin resolverConfiguration; @SuppressWarnings( "FieldCanBeLocal" ) - @CommandLine.Option( names = { "--details", "-d" }, description = "Print detailed reports about violations" ) + @CommandLine.Option( + names = { "--details" }, + description = "Print detailed reports about errors and violations" ) private boolean details = false; @Override public void run() { - final AspectModel aspectModel = loadAspectModelOrFail( parentCommand.getInput(), resolverConfiguration, details ); + setDetails( details ); + setResolverConfig( resolverConfiguration ); + + final AspectModel aspectModel = getInputHandler( parentCommand.getInput() ).loadAspectModel(); final AspectModelValidator validator = new AspectModelValidator(); final List violations = validator.validateModel( aspectModel ); diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java index 657b1b370..f4825d129 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditMoveCommand.java @@ -13,6 +13,8 @@ package org.eclipse.esmf.aspect.edit; +import static org.eclipse.esmf.FileInputHandler.modelsRootForFile; + import java.io.File; import java.net.URI; import java.nio.file.Paths; @@ -112,21 +114,27 @@ public class AspectEditMoveCommand extends AbstractCommand { @Override public void run() { + setDetails( details ); + setResolverConfig( resolverConfiguration ); + final String input = parentCommand.parentCommand.getInput(); + if ( !inputIsFile( input ) ) { + throw new CommandException( "The edit command only works with local files." ); + } + final File inputFile = absoluteFile( new File( input ) ); // Move to other/new file in same namespace if ( targetNamespace == null ) { - final File targetFileRelativeToInput = getInputFile( input ).toPath().getParent().resolve( targetFile ).toFile(); + final File targetFileRelativeToInput = inputFile.toPath().getParent().resolve( targetFile ).toFile(); if ( targetFileRelativeToInput.exists() ) { moveElementToExistingFile( targetFileRelativeToInput ); } else { - moveElementToNewFile(); + moveElementToNewFile( inputFile ); } return; } // Move to other/new file in other namespace - final File inputFile = getInputFile( input ); final AspectModelUrn targetNamespaceUrn = AspectModelUrn.from( targetNamespace ) .getOrElseThrow( () -> new CommandException( "Target namespace is invalid: " + targetNamespace ) ); final File targetFileInOtherNamespace = modelsRootForFile( inputFile ) @@ -144,16 +152,16 @@ public void run() { /** * Supports the case {@code samm aspect Aspect.ttl edit move MyAspect newFile.ttl} */ - private void moveElementToNewFile() { + private void moveElementToNewFile( final File inputFile ) { final String input = parentCommand.parentCommand.getInput(); - final AspectModel aspectModel = loadAspectModelOrFail( input, resolverConfiguration ); + final AspectModel aspectModel = getInputHandler( input ).loadAspectModel(); // Do refactoring final ModelElement modelElement = determineModelElementToMove( aspectModel ); if ( targetFile.contains( File.separator ) ) { throw new CommandException( "The target file name should not contain a path; only a file name." ); } - final URI targetFileUri = getInputFile( input ).toPath().getParent().resolve( targetFile ).toUri(); + final URI targetFileUri = inputFile.toPath().getParent().resolve( targetFile ).toUri(); final List headerCommentForNewFile = copyHeader ? modelElement.getSourceFile().headerComment() : List.of(); @@ -173,7 +181,7 @@ private void moveElementToNewFile() { */ private void moveElementToOtherNamespaceNewFile( final AspectModelUrn targetNamespaceUrn, final File targetFileInNewNamespace ) { final String input = parentCommand.parentCommand.getInput(); - final AspectModel aspectModel = loadAspectModelOrFail( input, resolverConfiguration ); + final AspectModel aspectModel = getInputHandler( input ).loadAspectModel(); // Do refactoring final ModelElement modelElement = determineModelElementToMove( aspectModel ); @@ -202,8 +210,8 @@ private void moveElementToOtherNamespaceNewFile( final AspectModelUrn targetName * Support the case {@code samm aspect Aspect.ttl edit move MyAspect existingFile.ttl} */ private void moveElementToExistingFile( final File targetFileRelativeToInput ) { - final AspectModel sourceAspectModel = loadAspectModelOrFail( parentCommand.parentCommand.getInput(), resolverConfiguration ); - final AspectModel targetAspectModel = loadAspectModelOrFail( targetFileRelativeToInput, resolverConfiguration, false ); + final AspectModel sourceAspectModel = getInputHandler( parentCommand.parentCommand.getInput() ).loadAspectModel(); + final AspectModel targetAspectModel = getInputHandler( targetFileRelativeToInput ).loadAspectModel(); // Create a consistent in-memory representation of both the source and target models. // On this Aspect Model we can perform the refactoring operation @@ -229,8 +237,8 @@ private void moveElementToExistingFile( final File targetFileRelativeToInput ) { * Supports the case {@code samm aspect Aspect.ttl edit move MyAspect existingFile.ttl urn:samm:com.example.othernamespace:1.0.0} */ private void moveElementToOtherNamespaceExistingFile( final AspectModelUrn targetNamespaceUrn, final File targetFileInOtherNamespace ) { - final AspectModel sourceAspectModel = loadAspectModelOrFail( parentCommand.parentCommand.getInput(), resolverConfiguration ); - final AspectModel targetAspectModel = loadAspectModelOrFail( targetFileInOtherNamespace, resolverConfiguration, false ); + final AspectModel sourceAspectModel = getInputHandler( parentCommand.parentCommand.getInput() ).loadAspectModel(); + final AspectModel targetAspectModel = getInputHandler( targetFileInOtherNamespace ).loadAspectModel(); // Create a consistent in-memory representation of both the source and target models. // On this Aspect Model we can perform the refactoring operation diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditNewVersionCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditNewVersionCommand.java index 9b2f4eb94..2e84e4568 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditNewVersionCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/edit/AspectEditNewVersionCommand.java @@ -99,6 +99,9 @@ static class VersionPart { @Override public void run() { + setDetails( details ); + setResolverConfig( resolverConfiguration ); + final IncreaseVersion increaseVersion; if ( versionPart.increaseMajor ) { increaseVersion = IncreaseVersion.MAJOR; @@ -110,7 +113,7 @@ public void run() { final String input = parentCommand.parentCommand.getInput(); final Optional inputFile = Optional.of( new File( input ) ).filter( File::exists ); - final AspectModelLoader aspectModelLoader = getAspectModelLoader( inputFile, resolverConfiguration ); + final AspectModelLoader aspectModelLoader = getInputHandler( input ).aspectModelLoader(); final AspectModel aspectModel = inputFile.map( aspectModelLoader::load ).orElseGet( () -> { final AspectModelUrn urn = AspectModelUrn.from( input ).getOrElseThrow( () -> diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAasCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAasCommand.java index 9714eeefd..cd9a0b95b 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAasCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAasCommand.java @@ -46,15 +46,23 @@ public class AspectToAasCommand extends AbstractCommand { description = "Output file path" ) private String outputFilePath = "-"; - @CommandLine.Option( names = { "--format", "-f" }, + @CommandLine.Option( + names = { "--format", "-f" }, description = "The file format the AAS is to be generated in. Valid options are \"${COMPLETION-CANDIDATES}\". Default is " + "\"${DEFAULT-VALUE}\"." ) private AasFileFormat format = AasFileFormat.XML; - @CommandLine.Option( names = { "--aspect-data", "-a" }, + @CommandLine.Option( + names = { "--aspect-data", "-a" }, description = "A file containing Aspect JSON data." ) private File aspectData = null; + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--details" }, + description = "Print detailed reports on errors" ) + private boolean details = false; + @CommandLine.ParentCommand private AspectToCommand parentCommand; @@ -80,7 +88,10 @@ private JsonNode loadAspectData() { @Override public void run() { - final Aspect aspect = loadAspectOrFail( parentCommand.parentCommand.getInput(), resolverConfiguration ); + setDetails( details ); + setResolverConfig( resolverConfiguration ); + + final Aspect aspect = getInputHandler( parentCommand.parentCommand.getInput() ).loadAspect(); final JsonNode loadedAspectData = loadAspectData(); // we intentionally override the name of the generated artifact here to the name explicitly // desired by the user (outputFilePath), as opposed to what the model thinks it should be diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAsyncapiCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAsyncapiCommand.java index 64b0f37c3..9894d8c9c 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAsyncapiCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToAsyncapiCommand.java @@ -56,31 +56,50 @@ public class AspectToAsyncapiCommand extends AbstractCommand { private static final ObjectMapper YAML_MAPPER = new YAMLMapper().enable( YAMLGenerator.Feature.MINIMIZE_QUOTES ); private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); - @CommandLine.Option( names = { "--json", "-j" }, + @CommandLine.Option( + names = { "--json", "-j" }, description = "Generate AsyncAPI JSON specification for an Aspect Model (when not given, YAML is generated as default format)" ) private boolean generateJsonAsyncApiSpec = false; - @CommandLine.Option( names = { "--separate-files", "-sf" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--separate-files", "-sf" }, description = "Write separate files for the root document and referenced schemas." ) private boolean writeSeparateFiles = false; - @CommandLine.Option( names = { "--output", "-o" }, description = "Output path; if --separate-files is given, this must be a directory." ) + @CommandLine.Option( + names = { "--output", "-o" }, + description = "Output path; if --separate-files is given, this must be a directory." ) private String outputFilePath = "-"; - @CommandLine.Option( names = { "--application-id", "-ai" }, description = "Use this param for provide application id." ) + @CommandLine.Option( + names = { "--application-id", "-ai" }, + description = "Use this param for provide application id." ) private String applicationId; - @CommandLine.Option( names = { "--channel-address", "-ca" }, description = "Use this param make possible provide channel address." ) + @CommandLine.Option( + names = { "--channel-address", "-ca" }, + description = "Use this param make possible provide channel address." ) private String channelAddress; - @CommandLine.Option( names = { "--semantic-version", "-sv" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--semantic-version", "-sv" }, description = "Use the full semantic version from the Aspect Model as the version for the Aspect API." ) private boolean useSemanticApiVersion = false; - @CommandLine.Option( names = { "--language", "-l" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--language", "-l" }, description = "The language from the model for which the AsyncAPI specification should be generated (default: en)" ) private String language = "en"; + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--details" }, + description = "Print detailed reports on errors" ) + private boolean details = false; + @CommandLine.ParentCommand private AspectToCommand parentCommand; @@ -92,9 +111,12 @@ public class AspectToAsyncapiCommand extends AbstractCommand { @Override public void run() { + setDetails( details ); + setResolverConfig( resolverConfiguration ); + final Locale locale = Optional.ofNullable( language ).map( Locale::forLanguageTag ).orElse( Locale.ENGLISH ); final AspectModelAsyncApiGenerator generator = new AspectModelAsyncApiGenerator(); - final Aspect aspect = loadAspectOrFail( parentCommand.parentCommand.getInput(), resolverConfiguration ); + final Aspect aspect = getInputHandler( parentCommand.parentCommand.getInput() ).loadAspect(); final AsyncApiSchemaGenerationConfig config = AsyncApiSchemaGenerationConfigBuilder.builder() .useSemanticVersion( useSemanticApiVersion ) .applicationId( applicationId ) diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToHtmlCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToHtmlCommand.java index 9b12854a5..807642b93 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToHtmlCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToHtmlCommand.java @@ -38,20 +38,31 @@ public class AspectToHtmlCommand extends AbstractCommand { public static final String COMMAND_NAME = "html"; - @CommandLine.Option( names = { "--output", "-o" }, description = "Output file path (default: stdout)" ) + @CommandLine.Option( + names = { "--output", "-o" }, + description = "Output file path (default: stdout)" ) private String outputFilePath = "-"; - @CommandLine.Option( names = { "--css", - "-c" }, description = "CSS file with custom styles to be included in the generated HTML documentation" ) + @CommandLine.Option( + names = { "--css", "-c" }, + description = "CSS file with custom styles to be included in the generated HTML documentation" ) private String customCssFile; - @CommandLine.Option( names = { "--language", - "-l" }, description = "The language from the model for which the html should be generated (default: en)" ) + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--language", "-l" }, + description = "The language from the model for which the html should be generated (default: en)" ) private String language = "en"; @CommandLine.ParentCommand private AspectToCommand parentCommand; + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--details" }, + description = "Print detailed reports on errors" ) + private boolean details = false; + @CommandLine.Mixin private LoggingMixin loggingMixin; @@ -60,8 +71,11 @@ public class AspectToHtmlCommand extends AbstractCommand { @Override public void run() { + setDetails( details ); + setResolverConfig( resolverConfiguration ); + try { - final Aspect aspect = loadAspectOrFail( parentCommand.parentCommand.getInput(), resolverConfiguration ); + final Aspect aspect = getInputHandler( parentCommand.parentCommand.getInput() ).loadAspect(); final AspectModelDocumentationGenerator generator = new AspectModelDocumentationGenerator( aspect ); final Map generationArgs = new HashMap<>(); generationArgs.put( AspectModelDocumentationGenerator.HtmlGenerationOption.STYLESHEET, "" ); diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJavaCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJavaCommand.java index 14f638e02..db3d3afa9 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJavaCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJavaCommand.java @@ -39,27 +39,47 @@ public class AspectToJavaCommand extends AbstractCommand { public static final String COMMAND_NAME = "java"; - @CommandLine.Option( names = { "--no-jackson", - "-nj" }, description = "Disable Jackson annotation generation in generated Java classes." ) + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--no-jackson", "-nj" }, + description = "Disable Jackson annotation generation in generated Java classes." ) private boolean disableJacksonAnnotations = false; - @CommandLine.Option( names = { "--template-library-file", "-tlf" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--template-library-file", "-tlf" }, description = "The path and name of the Velocity template file containing the macro library." ) private String templateLib = ""; - @CommandLine.Option( names = { "--package-name", "-pn" }, description = "Package to use for generated Java classes" ) + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--package-name", "-pn" }, + description = "Package to use for generated Java classes" ) private String packageName = ""; - @CommandLine.Option( names = { "--execute-library-macros", - "-elm" }, description = "Execute the macros provided in the Velocity macro library." ) + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--execute-library-macros", "-elm" }, + description = "Execute the macros provided in the Velocity macro library." ) private boolean executeLibraryMacros = false; - @CommandLine.Option( names = { "--output-directory", "-d" }, description = "Output directory to write files to" ) + @CommandLine.Option( + names = { "--output-directory", "-d" }, + description = "Output directory to write files to" ) private String outputPath = "."; - @CommandLine.Option( names = { "--static", "-s" }, description = "Generate Java domain classes for a Static Meta Model" ) + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--static", "-s" }, + description = "Generate Java domain classes for a Static Meta Model" ) private boolean generateStaticMetaModelJavaClasses = false; + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--details" }, + description = "Print detailed reports on errors" ) + private boolean details = false; + @CommandLine.ParentCommand private AspectToCommand parentCommand; @@ -71,7 +91,10 @@ public class AspectToJavaCommand extends AbstractCommand { @Override public void run() { - final Aspect aspect = loadAspectOrFail( parentCommand.parentCommand.getInput(), resolverConfiguration ); + setDetails( details ); + setResolverConfig( resolverConfiguration ); + + final Aspect aspect = getInputHandler( parentCommand.parentCommand.getInput() ).loadAspect(); final JavaGenerator javaGenerator = generateStaticMetaModelJavaClasses ? getStaticModelGenerator( aspect ) : getModelGenerator( aspect ); diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonCommand.java index 3c6045f90..f2c4e6235 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonCommand.java @@ -33,9 +33,17 @@ public class AspectToJsonCommand extends AbstractCommand { public static final String COMMAND_NAME = "json"; - @CommandLine.Option( names = { "--output", "-o" }, description = "Output file path" ) + @CommandLine.Option( + names = { "--output", "-o" }, + description = "Output file path" ) private String outputFilePath = "-"; + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--details" }, + description = "Print detailed reports on errors" ) + private boolean details = false; + @CommandLine.ParentCommand private AspectToCommand parentCommand; @@ -47,8 +55,11 @@ public class AspectToJsonCommand extends AbstractCommand { @Override public void run() { + setDetails( details ); + setResolverConfig( resolverConfiguration ); + final AspectModelJsonPayloadGenerator generator = new AspectModelJsonPayloadGenerator( - loadAspectOrFail( parentCommand.parentCommand.getInput(), resolverConfiguration ) ); + getInputHandler( parentCommand.parentCommand.getInput() ).loadAspect() ); try { // we intentionally override the name of the generated artifact here to the name explicitly desired by the user (outputFilePath), // as opposed to what the model thinks it should be called (name) diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonLdCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonLdCommand.java index a9fe82054..b373c7201 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonLdCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonLdCommand.java @@ -33,9 +33,17 @@ public class AspectToJsonLdCommand extends AbstractCommand { public static final String COMMAND_NAME = "jsonld"; - @CommandLine.Option( names = { "--output", "-o" }, description = "Output file path" ) + @CommandLine.Option( + names = { "--output", "-o" }, + description = "Output file path" ) private String outputFilePath = "-"; + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--details" }, + description = "Print detailed reports on errors" ) + private boolean details = false; + @CommandLine.ParentCommand private AspectToCommand parentCommand; @@ -47,8 +55,11 @@ public class AspectToJsonLdCommand extends AbstractCommand { @Override public void run() { + setDetails( details ); + setResolverConfig( resolverConfiguration ); + final AspectModelToJsonLdGenerator generator = new AspectModelToJsonLdGenerator( - loadAspectOrFail( parentCommand.parentCommand.getInput(), resolverConfiguration ) ); + getInputHandler( parentCommand.parentCommand.getInput() ).loadAspect() ); try { // we intentionally override the name of the generated artifact here to the name explicitly desired by the user (outputFilePath), // as opposed to what the model thinks it should be called (name) diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonSchemaCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonSchemaCommand.java index da74761a0..7f090af12 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonSchemaCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToJsonSchemaCommand.java @@ -40,13 +40,24 @@ public class AspectToJsonSchemaCommand extends AbstractCommand { public static final String COMMAND_NAME = "schema"; - @CommandLine.Option( names = { "--output", "-o" }, description = "Output file path (default: stdout)" ) + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--output", "-o" }, + description = "Output file path (default: stdout)" ) private String outputFilePath = "-"; - @CommandLine.Option( names = { "--language", "-l" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--language", "-l" }, description = "The language from the model for which the OpenAPI specification should be generated (default: en)" ) private String language = "en"; + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--details" }, + description = "Print detailed reports on errors" ) + private boolean details = false; + @CommandLine.ParentCommand private AspectToCommand parentCommand; @@ -58,7 +69,10 @@ public class AspectToJsonSchemaCommand extends AbstractCommand { @Override public void run() { - final Aspect aspect = loadAspectOrFail( parentCommand.parentCommand.getInput(), resolverConfiguration ); + setDetails( details ); + setResolverConfig( resolverConfiguration ); + + final Aspect aspect = getInputHandler( parentCommand.parentCommand.getInput() ).loadAspect(); final Locale locale = Optional.ofNullable( language ).map( Locale::forLanguageTag ).orElse( Locale.ENGLISH ); final JsonSchemaGenerationConfig config = JsonSchemaGenerationConfigBuilder.builder() .locale( locale ) diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToOpenapiCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToOpenapiCommand.java index f1980182d..560c7bc48 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToOpenapiCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToOpenapiCommand.java @@ -63,85 +63,123 @@ public class AspectToOpenapiCommand extends AbstractCommand { private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); private static final ObjectMapper YAML_MAPPER = new YAMLMapper().enable( YAMLGenerator.Feature.MINIMIZE_QUOTES ); - @CommandLine.Option( names = { "--api-base-url", "-b" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--api-base-url", "-b" }, description = "The base url for the Aspect API used in the OpenAPI specification.", required = true ) private String aspectApiBaseUrl = ""; - @CommandLine.Option( names = { "--json", "-j" }, + @CommandLine.Option( + names = { "--json", "-j" }, description = "Generate OpenAPI JSON specification for an Aspect Model (when not given, YAML is generated as default format)" ) boolean generateJsonOpenApiSpec = false; - @CommandLine.Option( names = { "--comment", "-c" }, + @CommandLine.Option( + names = { "--comment", "-c" }, description = "Generate $comment OpenAPI keyword for samm:see attributes in the model." ) boolean generateCommentForSeeAttributes = false; - @CommandLine.Option( names = { "--parameter-file", "-p" }, + @CommandLine.Option( + names = { "--parameter-file", "-p" }, description = "The path to a file including the parameter for the Aspect API endpoints. When --json is given, this file " + "should contain the parameter definition in JSON, otherwise it should contain the definition in YAML." ) private String aspectParameterFile; - @CommandLine.Option( names = { "--semantic-version", "-sv" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--semantic-version", "-sv" }, description = "Use the full semantic version from the Aspect Model as the version for the Aspect API." ) private boolean useSemanticApiVersion = false; - @CommandLine.Option( names = { "--resource-path", "-r" }, description = "The resource path for the Aspect API endpoints." ) + @CommandLine.Option( + names = { "--resource-path", "-r" }, + description = "The resource path for the Aspect API endpoints." ) private String aspectResourcePath; - @CommandLine.Option( names = { "--include-query-api", "-q" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--include-query-api", "-q" }, description = "Include the path for the Query Aspect API Endpoint in the OpenAPI specification." ) private boolean includeQueryApi = false; - @CommandLine.Option( names = { "--include-crud", "-cr" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--include-crud", "-cr" }, description = "Include all CRUD operations (POST/PUT/PATCH) in the OpenAPI specification." ) private boolean includeFullCrud = false; - @CommandLine.Option( names = { "--include-post", "-post" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--include-post", "-post" }, description = "Include POST operation in the OpenAPI specification." ) private boolean includePost = false; - @CommandLine.Option( names = { "--include-put", "-put" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--include-put", "-put" }, description = "Include PUT operation in the OpenAPI specification." ) private boolean includePut = false; - @CommandLine.Option( names = { "--include-patch", "-patch" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--include-patch", "-patch" }, description = "Include PATCH operation in the OpenAPI specification." ) private boolean includePatch = false; - @CommandLine.Option( names = { "--paging-none", "-pn" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--paging-none", "-pn" }, description = "Exclude paging information for the Aspect API Endpoint in the OpenAPI specification." ) private boolean excludePaging = false; - @CommandLine.Option( names = { "--paging-cursor-based", "-pc" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--paging-cursor-based", "-pc" }, description = "In case there is more than one paging possibility, it has to be cursor based paging." ) private boolean aspectCursorBasedPaging = false; - @CommandLine.Option( names = { "--paging-offset-based", "-po" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--paging-offset-based", "-po" }, description = "In case there is more than one paging possibility, it has to be offset based paging." ) private boolean aspectOffsetBasedPaging = false; - @CommandLine.Option( names = { "--paging-time-based", "-pt" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--paging-time-based", "-pt" }, description = "In case there is more than one paging possibility, it has to be time based paging." ) private boolean aspectTimeBasedPaging = false; - @CommandLine.Option( names = { "--separate-files", "-sf" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--separate-files", "-sf" }, description = "Write separate files for the root document and referenced schemas." ) private boolean writeSeparateFiles = false; - @CommandLine.Option( names = { "--output", "-o" }, description = "Output path; if --separate-files is given, this must be a directory." ) + @CommandLine.Option( + names = { "--output", "-o" }, + description = "Output path; if --separate-files is given, this must be a directory." ) private String outputFilePath = "-"; - @CommandLine.Option( names = { "--language", "-l" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--language", "-l" }, description = "The language from the model for which the OpenAPI specification should be generated (default: en)." ) private String language = "en"; @CommandLine.Option( names = { "--template-file", "-t" }, - description = - "The path to the file with a template for the resulting specification, " - + "including values undefined by the aspect's OpenAPI specification. " - + "The template can be in JSON or YAML format." ) + description = "The path to the file with a template for the resulting specification, " + + "including values undefined by the aspect's OpenAPI specification. " + + "The template can be in JSON or YAML format." ) private String templateFilePath; + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--details" }, + description = "Print detailed reports on errors" ) + private boolean details = false; + @CommandLine.ParentCommand private AspectToCommand parentCommand; @@ -153,9 +191,12 @@ public class AspectToOpenapiCommand extends AbstractCommand { @Override public void run() { + setDetails( details ); + setResolverConfig( resolverConfiguration ); + final Locale locale = Optional.ofNullable( language ).map( Locale::forLanguageTag ).orElse( Locale.ENGLISH ); final AspectModelOpenApiGenerator generator = new AspectModelOpenApiGenerator(); - final Aspect aspect = loadAspectOrFail( parentCommand.parentCommand.getInput(), resolverConfiguration ); + final Aspect aspect = getInputHandler( parentCommand.parentCommand.getInput() ).loadAspect(); final ObjectMapper objectMapper = new ObjectMapper(); final OpenApiSchemaGenerationConfig config = OpenApiSchemaGenerationConfigBuilder.builder() .useSemanticVersion( useSemanticApiVersion ) diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToPngCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToPngCommand.java index a4bfed648..c64d190ce 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToPngCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToPngCommand.java @@ -33,15 +33,25 @@ public class AspectToPngCommand extends AbstractCommand { public static final String COMMAND_NAME = "png"; - @CommandLine.Option( names = { "--output", "-o" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--output", "-o" }, description = "Output file path (default: stdout; as PNG is a binary format, it is strongly recommended to output the result to " + "a file by using the -o option or the console redirection operator '>')" ) private String outputFilePath = "-"; - @CommandLine.Option( names = { "--language", - "-l" }, description = "The language from the model for which the diagram should be generated (default: en)" ) + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--language", "-l" }, + description = "The language from the model for which the diagram should be generated (default: en)" ) private String language = "en"; + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--details" }, + description = "Print detailed reports on errors" ) + private boolean details = false; + @CommandLine.ParentCommand private AspectToCommand parentCommand; @@ -53,9 +63,11 @@ public class AspectToPngCommand extends AbstractCommand { @Override public void run() { + setDetails( details ); + setResolverConfig( resolverConfiguration ); + try { - generateDiagram( parentCommand.parentCommand.getInput(), AspectModelDiagramGenerator.Format.PNG, outputFilePath, language, - resolverConfiguration ); + generateDiagram( parentCommand.parentCommand.getInput(), AspectModelDiagramGenerator.Format.PNG, outputFilePath, language ); } catch ( final IOException e ) { throw new CommandException( e ); } diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSqlCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSqlCommand.java index b45a4b027..b6861f7a3 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSqlCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSqlCommand.java @@ -43,34 +43,48 @@ public class AspectToSqlCommand extends AbstractCommand { public static final String COMMAND_NAME = "sql"; - @CommandLine.Option( names = { "--output", "-o" }, description = "Output file path" ) + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--output", "-o" }, + description = "Output file path" ) private String outputFilePath = "-"; - @CommandLine.Option( names = { "--dialect", "-d" }, + @CommandLine.Option( + names = { "--dialect", "-d" }, description = "The SQL dialect to generate for (default: ${DEFAULT-VALUE}" ) private SqlGenerationConfig.Dialect dialect = SqlGenerationConfig.Dialect.DATABRICKS; - @CommandLine.Option( names = { "--mapping-strategy", "-s" }, + @CommandLine.Option( + names = { "--mapping-strategy", "-s" }, description = "The mapping strategy to use (default: ${DEFAULT-VALUE}" ) private SqlGenerationConfig.MappingStrategy strategy = SqlGenerationConfig.MappingStrategy.DENORMALIZED; - @CommandLine.Option( names = { "--language", "-l" }, + @CommandLine.Option( + names = { "--language", "-l" }, description = "The language from the model for which comments should be generated (default: ${DEFAULT-VALUE})" ) private String language = DatabricksSqlGenerationConfig.DEFAULT_COMMENT_LANGUAGE.getLanguage(); - @CommandLine.Option( names = { "--include-table-comment", "-tc" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--include-table-comment", "-tc" }, description = "Include table comment in the generated SQL script (default: ${DEFAULT-VALUE})" ) private boolean includeTableComment = DatabricksSqlGenerationConfig.DEFAULT_INCLUDE_TABLE_COMMENT; - @CommandLine.Option( names = { "--include-column-comments", "-cc" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--include-column-comments", "-cc" }, description = "Include column comments in the generated SQL script (default: ${DEFAULT-VALUE})" ) private boolean includeColumnComments = DatabricksSqlGenerationConfig.DEFAULT_INCLUDE_COLUMN_COMMENTS; - @CommandLine.Option( names = { "--table-command-prefix", "-tcp" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--table-command-prefix", "-tcp" }, description = "The prefix to use for Databricks table creation commands (default: ${DEFAULT-VALUE})" ) private String tableCommandPrefix = DatabricksSqlGenerationConfig.DEFAULT_TABLE_COMMAND_PREFIX; - @CommandLine.Option( names = { "--decimal-precision", "-dp" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--decimal-precision", "-dp" }, description = "The precision to use for Databricks decimal columns, between 1 and 38. (default: ${DEFAULT-VALUE})" ) private int decimalPrecision = DatabricksSqlGenerationConfig.DECIMAL_DEFAULT_PRECISION; @@ -80,6 +94,12 @@ public class AspectToSqlCommand extends AbstractCommand { converter = DatabricksColumnDefinitionTypeConverter.class ) private List customColumns; + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--details" }, + description = "Print detailed reports on errors" ) + private boolean details = false; + @CommandLine.ParentCommand private AspectToCommand parentCommand; @@ -98,7 +118,10 @@ public DatabricksColumnDefinition convert( final String value ) throws Exception @Override public void run() { - final Aspect aspect = loadAspectOrFail( parentCommand.parentCommand.getInput(), resolverConfiguration ); + setDetails( details ); + setResolverConfig( resolverConfiguration ); + + final Aspect aspect = getInputHandler( parentCommand.parentCommand.getInput() ).loadAspect(); final DatabricksSqlGenerationConfig generatorConfig = DatabricksSqlGenerationConfigBuilder.builder() .commentLanguage( Locale.forLanguageTag( language ) ) diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSvgCommand.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSvgCommand.java index 951cfe262..042d80674 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSvgCommand.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/aspect/to/AspectToSvgCommand.java @@ -33,13 +33,24 @@ public class AspectToSvgCommand extends AbstractCommand { public static final String COMMAND_NAME = "svg"; - @CommandLine.Option( names = { "--output", "-o" }, description = "Output file path" ) + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--output", "-o" }, + description = "Output file path" ) private String outputFilePath = "-"; - @CommandLine.Option( names = { "--language", "-l" }, + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--language", "-l" }, description = "The language from the model for which the diagram should be generated (default: en)" ) private String language = "en"; + @SuppressWarnings( "FieldCanBeLocal" ) + @CommandLine.Option( + names = { "--details" }, + description = "Print detailed reports on errors" ) + private boolean details = false; + @CommandLine.ParentCommand private AspectToCommand parentCommand; @@ -51,9 +62,11 @@ public class AspectToSvgCommand extends AbstractCommand { @Override public void run() { + setResolverConfig( resolverConfiguration ); + setResolverConfig( resolverConfiguration ); + try { - generateDiagram( parentCommand.parentCommand.getInput(), AspectModelDiagramGenerator.Format.SVG, outputFilePath, language, - resolverConfiguration ); + generateDiagram( parentCommand.parentCommand.getInput(), AspectModelDiagramGenerator.Format.SVG, outputFilePath, language ); } catch ( final IOException e ) { throw new CommandException( e ); } diff --git a/tools/samm-cli/src/test/java/org/eclipse/esmf/MainClassProcessLauncher.java b/tools/samm-cli/src/test/java/org/eclipse/esmf/MainClassProcessLauncher.java index 232d67237..01cb458b0 100644 --- a/tools/samm-cli/src/test/java/org/eclipse/esmf/MainClassProcessLauncher.java +++ b/tools/samm-cli/src/test/java/org/eclipse/esmf/MainClassProcessLauncher.java @@ -22,6 +22,7 @@ import java.io.PrintStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.net.URLPermission; import java.nio.charset.StandardCharsets; import java.security.Permission; import java.util.stream.Collectors; @@ -125,6 +126,17 @@ public void checkPermission( final Permission permission ) { } } + @Override + public void checkPermission( final Permission permission, final Object context ) { + // Unblock HTTP GETs from unit tests + if ( permission instanceof URLPermission ) { + return; + } + if ( delegateSecurityManager != null ) { + delegateSecurityManager.checkPermission( permission, context ); + } + } + @Override public void checkExit( final int i ) { exitCode = i; diff --git a/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java b/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java index 32d14668e..1e8d32eaa 100644 --- a/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java +++ b/tools/samm-cli/src/test/java/org/eclipse/esmf/SammCliTest.java @@ -40,6 +40,7 @@ import org.eclipse.esmf.test.InvalidTestAspect; import org.eclipse.esmf.test.TestAspect; import org.eclipse.esmf.test.TestModel; +import org.eclipse.esmf.test.TestSharedModel; import org.apache.commons.io.FileUtils; import org.apache.tika.config.TikaConfig; @@ -56,6 +57,7 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.ValueSource; /** * The tests for the CLI that are executed by Maven Surefire. They work using the {@link MainClassProcessLauncher}, i.e. directly call @@ -67,6 +69,8 @@ class SammCliTest { protected ProcessLauncher sammCli; private final TestModel testModel = TestAspect.ASPECT_WITH_ENTITY; private final String defaultInputFile = inputFile( testModel ).getAbsolutePath(); + private final String defaultInputUrn = testModel.getUrn().toString(); + private final String defaultModelsRoot = inputFile( testModel ).toPath().getParent().getParent().getParent().toFile().getAbsolutePath(); Path outputDirectory = null; @@ -90,8 +94,8 @@ void afterEach() { System.err.println( "Could not delete file " + file ); } } ); - } catch ( final IOException e ) { - throw new RuntimeException( e ); + } catch ( final IOException exception ) { + throw new RuntimeException( exception ); } } } @@ -168,6 +172,27 @@ void testAspectPrettyPrintToStdout() { assertThat( result.stderr() ).isEmpty(); } + @Test + void testAspectFromUrnPrettyPrintToStdout() { + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", defaultInputUrn, "prettyprint", + "--models-root", defaultModelsRoot ); + assertThat( result.stdout() ).contains( "@prefix" ); + assertThat( result.stderr() ).isEmpty(); + } + + /** + * This test makes sure that also files without a samm:Aspect declaration can be pretty printed. + */ + @ParameterizedTest + @ValueSource( strings = { "TestEntityWithCollection", "testCollectionProperty", "TestCollection" } ) + void testSharedFileFromUrnPrettyPrintToStdout( final String elementName ) { + final String urn = TestSharedModel.TEST_NAMESPACE + elementName; + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", urn, "prettyprint", + "--models-root", defaultModelsRoot ); + assertThat( result.stdout() ).contains( "@prefix" ); + assertThat( result.stderr() ).isEmpty(); + } + @Test void testAspectPrettyPrintOverwrite() throws IOException { final File targetFile = outputFile( "output.ttl" ); @@ -191,6 +216,32 @@ void testAspectValidateValidModel() { assertThat( result.stderr() ).isEmpty(); } + @Test + void testAspectFromUrnValidateValidModel() { + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", defaultInputUrn, "validate", "--models-root", + defaultModelsRoot ); + assertThat( result.stdout() ).contains( "Input model is valid" ); + assertThat( result.stderr() ).isEmpty(); + } + + @Test + void testAspectFromGitHubWithFullUrlValidateModel() { + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", + "https://github.com/eclipse-esmf/esmf-sdk/blob/main/core/esmf-test-aspect-models/src/main/resources/valid/org.eclipse.esmf" + + ".test/1.0.0/AspectWithEntity.ttl", "validate" ); + assertThat( result.stdout() ).contains( "Input model is valid" ); + assertThat( result.stderr() ).isEmpty(); + } + + @Test + void testAspectFromGitHubWithExplicitRepoValidateModel() { + final String remoteModelsDirectory = "core/esmf-test-aspect-models/src/main/resources/valid"; + final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", + defaultInputUrn, "validate", "--github", "eclipse-esmf/esmf-sdk", "--github-directory", remoteModelsDirectory ); + assertThat( result.stdout() ).contains( "Input model is valid" ); + assertThat( result.stderr() ).isEmpty(); + } + @ParameterizedTest @EnumSource( TestAspect.class ) @EnabledIfSystemProperty( named = "packaging-type", matches = "native" ) @@ -1389,15 +1440,12 @@ void testAspectUsageFromUrn() { assertThat( result.stdout() ).contains( TestModel.TEST_NAMESPACE + "testProperty" ); } - // Running this test from within the regular JUnit test harness yields a java.lang.SecurityException for - // java.net.URLPermission, therefore it is executed only in the native build @Test - @EnabledIfSystemProperty( named = "packaging-type", matches = "native" ) void testAspectUsageWithGitHubResolution() { final String remoteModelsDirectory = "core/esmf-test-aspect-models/src/main/resources/valid"; final String urnToCheck = TestModel.TEST_NAMESPACE + "testProperty"; final ExecutionResult result = sammCli.runAndExpectSuccess( "--disable-color", "aspect", urnToCheck, "usage", - "--github", "--github-name", "eclipse-esmf/esmf-sdk", "--github-directory", remoteModelsDirectory ); + "--github", "eclipse-esmf/esmf-sdk", "--github-directory", remoteModelsDirectory ); assertThat( result.stderr() ).isEmpty(); assertThat( result.stdout() ).contains( TestModel.TEST_NAMESPACE + "testProperty" ); } @@ -1406,7 +1454,7 @@ void testAspectUsageWithGitHubResolution() { * Returns the File object for a test model file */ private File inputFile( final TestModel testModel ) { - final boolean isValid = !(testModel instanceof InvalidTestAspect); + final boolean isValid = !( testModel instanceof InvalidTestAspect ); final String resourcePath = String.format( "%s/../../core/esmf-test-aspect-models/src/main/resources/%s/org.eclipse.esmf.test/1.0.0/%s.ttl", System.getProperty( "user.dir" ), isValid ? "valid" : "invalid", testModel.getName() ); @@ -1457,8 +1505,8 @@ private String resolverCommand() { // are not resolved to the file system but to the jar) try { final String resolverScript = new File( - System.getProperty( "user.dir" ) + "/target/test-classes/model_resolver" + (OS.WINDOWS.isCurrentOs() - ? ".bat" : ".sh") ).getCanonicalPath(); + System.getProperty( "user.dir" ) + "/target/test-classes/model_resolver" + ( OS.WINDOWS.isCurrentOs() + ? ".bat" : ".sh" ) ).getCanonicalPath(); final String modelsRoot = new File( System.getProperty( "user.dir" ) + "/target/classes/valid" ).getCanonicalPath(); final String metaModelVersion = KnownVersion.getLatest().toString().toLowerCase(); return resolverScript + " " + modelsRoot + " " + metaModelVersion; From da3a216a29fecd9e8467eecc6f1c598c6447d62d Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Thu, 24 Oct 2024 13:52:32 +0200 Subject: [PATCH 7/8] Update documentation for samm-cli --- .../modules/tooling-guide/pages/samm-cli.adoc | 62 ++++++++++++++----- 1 file changed, 47 insertions(+), 15 deletions(-) diff --git a/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc b/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc index 5c2826b77..90a5e7e55 100644 --- a/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc +++ b/documentation/developer-guide/modules/tooling-guide/pages/samm-cli.adoc @@ -11,8 +11,9 @@ latest release [on GitHub](https://github.com/eclipse-esmf/esmf-sdk/releases/lat [[samm-cli-getting-started]] == Running the SAMM CLI -For the executable jar, call `java -jar samm-cli-{esmf-sdk-version}.jar` followed by one of the following subcommands (e.g., `java -jar samm-cli-{esmf-sdk-version}.jar help`). -In the following sections, `samm` will be used as the command name. +For the executable jar, call `java -jar samm-cli-{esmf-sdk-version}.jar` followed by one of the +following subcommands (e.g., `java -jar samm-cli-{esmf-sdk-version}.jar help`). In the following +sections, `samm` will be used as the command name. [TIP] ==== @@ -26,27 +27,53 @@ alias samm='java -jar /location/to/samm-cli-{esmf-sdk-version}.jar' ==== . *For the Windows native executable:* -* *Download*: Download via above link or visit the repository of the Java SDK which contains the SAMM CLI at the Github https://github.com/eclipse-esmf/esmf-sdk/releases[releases page] and download executable file for Windows. +* *Download*: Download via above link or visit the repository of the Java SDK which contains the + SAMM CLI at the Github https://github.com/eclipse-esmf/esmf-sdk/releases[releases page] and + download executable file for Windows. * *Extract*: extract it to a location of your choice. * *Open* `samm.exe`: This will open a command prompt in this directory, where you can enter further `samm` commands. * *Input*: Make sure to read the below documentation and provide model files in the correct xref:models-directory-structure[directory structure]. . *For the Linux native executable:* -* *Download*: Download via above link or visit the repository of the Java SDK which contains the SAMM CLI at the Github https://github.com/eclipse-esmf/esmf-sdk/releases[releases page] and download executable file for Linux. +* *Download*: Download via above link or visit the repository of the Java SDK which contains the + SAMM CLI at the Github https://github.com/eclipse-esmf/esmf-sdk/releases[releases page] and + download executable file for Linux. * *Extract*: Unpack the archive to your preferred directory. * *Open Terminal*: Open the Terminal and navigate to the directory with the application. -* *Input*: Make sure to read the below documentation and provide model files in the correct xref:models-directory-structure[directory structure]. +* *Input*: Make sure to read the below documentation and provide model files in the correct + xref:models-directory-structure[directory structure]. + * *Execute*: Run the application using the command `./samm`. -Successful execution of a command is signaled by returning 0. In case of a logical or other internal error the error code 1 is being returned. -Missing or wrong command parameters result in error code 2 being returned. +Successful execution of a command is signaled by returning 0. In case of a logical or other internal +error the error code 1 is being returned. Missing or wrong command parameters result in error code 2 +being returned. -To get an overview of all commands and subcommands, use `samm help`. -To get help for a certain subcommand, use `help` before the subcommand name, e.g., `samm help aspect`, `samm help aspect validate` or `samm help aas to aspect`. -Each subcommand can have its own set of options which allow the user to further fine-tune the execution of the command. -The available options and their meaning can also be seen in the help text of the individual subcommands. +To get an overview of all commands and subcommands, use `samm help`. To get help for a certain +subcommand, use `help` before the subcommand name, e.g., `samm help aspect`, `samm help aspect +validate` or `samm help aas to aspect`. Each subcommand can have its own set of options which allow +the user to further fine-tune the execution of the command. The available options and their meaning +can also be seen in the help text of the individual subcommands. === List of commands + +[TIP] +==== +In place of `` in the command descriptions below, you can provide either one of: + +* a relative file name, such as `AspectModel.ttl` or `./somewhere/AspectModel.ttl` (on Linux and +MacOS) or `.\somewhere\AspectModel.ttl` (on Windows), +* an absolut file name, such as +`/home/me/aspect-models/org.eclipse.esmf.example/1.0.0/AspectModel.ttl` (on Linux and MacOS) or +`C:\Users\me\aspect-models\org.eclipse.esmf.example\1.0.0\AspectModel.ttl` (on Windows), +* a URL pointing to a .ttl file in a publically accessible GitHub repository, such as +`\https://github.com/eclipse-tractusx/sldt-semantic-models/blob/main/io.catenax.battery.battery_pass/6.0.0/BatteryPass.ttl` +or `\https://github.com/eclipse-esmf/esmf-aspect-model-editor/blob/main/core/apps/ame/src/assets/aspect-models/org.eclipse.examples/1.0.0/Movement.ttl`, +* an Aspect Model URN, such as `urn:samm:org.eclipse.esmf.example:1.0.0#Aspect`, however note that +this also requires setting a models source, see xref:configuration-of-model-resolution[configuration +of model resolution] for more information. +==== + [width="100%",options="header",cols="20,50,30"] |=== | Command | Description/Options | Examples @@ -209,6 +236,7 @@ The available options and their meaning can also be seen in the help text of the |=== +[[configuration-of-model-resolution]] === Configuration of model resolution When a loaded file refers to model elements defined elsewhere, the model resolver looks up the files @@ -228,10 +256,14 @@ to load. You can configure where such lookup should be done: language/script language, including complex logic if necessary. * Using the `--github` switch, you can configure a location in a remote GitHub repository as models - root. Using the switch requires the additional switch `--github-name` to set the remote - repository (e.g., `eclipse-esmf/esmf-sdk`). Optionally, you can also provide `--github-directory` - to set the remote directory and `--github-branch` to set the branch name. - + root, e.g., `eclipse-esmf/esmf-sdk`. Optionally, you can also provide `--github-directory` + to set the remote directory and `--github-branch` or `--github-tag` to set the branch name or tag, + respectively. + +NOTE: When using an Aspect Model URN as input to a command (such as +`urn:samm:org.eclipse.esmf.example:1.0.0#Aspect`), you must also provide at least one of the +switches mentioned above (`--models-root`, `--custom-resolver` or `--github`), otherwise samm-cli +will not know where to find the corresonding file(s). [[using-the-cli-to-create-a-json-openapi-specification]] == Using the CLI to create a JSON OpenAPI Specification From b790075999793f16fdd495498b1bbb382cfce5e3 Mon Sep 17 00:00:00 2001 From: "Textor Andreas (BCI/ESW17)" Date: Fri, 25 Oct 2024 13:32:14 +0200 Subject: [PATCH 8/8] Defensively check existence of file --- .../src/main/java/org/eclipse/esmf/FileInputHandler.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/tools/samm-cli/src/main/java/org/eclipse/esmf/FileInputHandler.java b/tools/samm-cli/src/main/java/org/eclipse/esmf/FileInputHandler.java index 055c15433..1fd3196a1 100644 --- a/tools/samm-cli/src/main/java/org/eclipse/esmf/FileInputHandler.java +++ b/tools/samm-cli/src/main/java/org/eclipse/esmf/FileInputHandler.java @@ -73,7 +73,12 @@ protected String expectedAspectName() { } public static boolean appliesToInput( final String input ) { - return absoluteFile( new File( input ) ).exists(); + try { + return absoluteFile( new File( input ) ).exists(); + } catch ( final Exception exception ) { + // This could file with e.g. a InvalidPathException or with platform-specific exceptions when the input is indeed not a valid file + return false; + } } @Override