diff --git a/aspect-model-editor-service/pom.xml b/aspect-model-editor-service/pom.xml index 1199ded8..ffdbc08e 100644 --- a/aspect-model-editor-service/pom.xml +++ b/aspect-model-editor-service/pom.xml @@ -39,10 +39,6 @@ org.eclipse.esmf esmf-native-support - - org.apache.commons - commons-compress - diff --git a/aspect-model-editor-service/src/main/java/org/eclipse/esmf/ame/services/ModelService.java b/aspect-model-editor-service/src/main/java/org/eclipse/esmf/ame/services/ModelService.java index 7e48a448..728f30a3 100644 --- a/aspect-model-editor-service/src/main/java/org/eclipse/esmf/ame/services/ModelService.java +++ b/aspect-model-editor-service/src/main/java/org/eclipse/esmf/ame/services/ModelService.java @@ -16,6 +16,7 @@ import java.io.File; import java.io.IOException; import java.net.URI; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; import java.util.List; @@ -85,9 +86,8 @@ public AspectModelResult getModel( final AspectModelUrn aspectModelUrn, final @N loadModelFromUrn( aspectModelUrn ); validateModel( aspectModel ); - return aspectModel.files().stream().filter( a -> a.elements().stream().anyMatch( - e -> ( e instanceof DefaultScalarValue && ( (DefaultScalarValue) e ).getType() - .equals( new DefaultScalar( aspectModelUrn.toString() ) ) ) || e.urn().equals( aspectModelUrn ) ) ).findFirst() + return aspectModel.files().stream().filter( file -> containsElement( file, aspectModelUrn ) ) + .filter( aspectModelFile -> aspectModelFile.sourceLocation().isPresent() ).filter( this::hasValidCasing ).findFirst() .map( aspectModelFile -> new AspectModelResult( aspectModelFile.filename(), AspectSerializer.INSTANCE.aspectModelFileToString( aspectModelFile ) ) ) .orElseThrow( () -> new FileNotFoundException( "Aspect Model not found" ) ); @@ -96,6 +96,29 @@ public AspectModelResult getModel( final AspectModelUrn aspectModelUrn, final @N } } + private boolean containsElement( final AspectModelFile file, final AspectModelUrn aspectModelUrn ) { + return file.elements().stream().anyMatch( e -> ( e instanceof DefaultScalarValue && ( (DefaultScalarValue) e ).getType() + .equals( new DefaultScalar( aspectModelUrn.toString() ) ) ) || e.urn().equals( aspectModelUrn ) ); + } + + private boolean hasValidCasing( final AspectModelFile aspectModelFile ) { + try { + final URI sourceLocation = aspectModelFile.sourceLocation().orElseThrow( () -> new IOException( "Source location not present" ) ); + final Path file = Path.of( sourceLocation ); + + if ( !Files.exists( file ) ) { + return false; + } + + final Path realPath = file.toRealPath(); + final Path providedPath = file.toAbsolutePath().normalize(); + + return realPath.getFileName().toString().equals( providedPath.getFileName().toString() ); + } catch ( final IOException e ) { + return false; + } + } + private AspectModel loadModelFromUrn( final AspectModelUrn aspectModelUrn ) { final Supplier aspectModelSupplier = ModelUtils.getAspectModelSupplier( aspectModelUrn, aspectModelLoader ); return aspectModelSupplier.get(); @@ -254,8 +277,7 @@ public boolean checkElementExists( final AspectModelUrn aspectModelUrn, final St try { System.out.println( loadModelFromUrn( aspectModelUrn ).files() ); - return loadModelFromUrn( aspectModelUrn ).files().stream() - .anyMatch( f -> !fileName.equals( f.filename().orElse( "" ) ) ); + return loadModelFromUrn( aspectModelUrn ).files().stream().anyMatch( f -> !fileName.equals( f.filename().orElse( "" ) ) ); } catch ( final ModelResolutionException e ) { return false; } diff --git a/aspect-model-editor-service/src/main/java/org/eclipse/esmf/ame/services/utils/ModelGroupingUtils.java b/aspect-model-editor-service/src/main/java/org/eclipse/esmf/ame/services/utils/ModelGroupingUtils.java index 6f239949..cabeccae 100644 --- a/aspect-model-editor-service/src/main/java/org/eclipse/esmf/ame/services/utils/ModelGroupingUtils.java +++ b/aspect-model-editor-service/src/main/java/org/eclipse/esmf/ame/services/utils/ModelGroupingUtils.java @@ -15,23 +15,24 @@ import java.io.File; import java.net.URI; -import java.util.AbstractMap; -import java.util.Comparator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.nio.file.Path; +import java.util.*; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.jena.rdf.model.*; +import org.apache.jena.vocabulary.RDF; import org.eclipse.esmf.ame.services.models.Model; import org.eclipse.esmf.ame.services.models.Version; import org.eclipse.esmf.aspectmodel.AspectModelFile; import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader; import org.eclipse.esmf.aspectmodel.resolver.AspectModelFileLoader; +import org.eclipse.esmf.aspectmodel.resolver.modelfile.RawAspectModelFile; import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn; -import org.eclipse.esmf.metamodel.AspectModel; import org.eclipse.esmf.metamodel.ModelElement; +import org.eclipse.esmf.metamodel.vocabulary.SAMM; +import org.eclipse.esmf.metamodel.vocabulary.SAMMC; +import org.eclipse.esmf.metamodel.vocabulary.SAMME; import org.eclipse.esmf.metamodel.vocabulary.SammNs; import org.eclipse.esmf.samm.KnownVersion; @@ -42,7 +43,7 @@ * * @param aspectModelLoader the loader for aspect models */ -public record ModelGroupingUtils( AspectModelLoader aspectModelLoader ) { +public record ModelGroupingUtils(AspectModelLoader aspectModelLoader) { /** * Constructs a ModelGrouper with the given base model path. */ @@ -52,7 +53,7 @@ public record ModelGroupingUtils( AspectModelLoader aspectModelLoader ) { /** * Groups model URIs by namespace and version, setting the existing field as specified. * - * @param uriStream a stream of model URIs + * @param uriStream a stream of model URIs * @param onlyAspectModels get only Aspect Models with Aspects as namespace list. * @return a map where the keys are namespaces and the values are lists of maps containing versions and their associated models */ @@ -63,48 +64,155 @@ public Map> groupModelsByNamespaceAndVersion( final Stream /** * Groups model URIs by namespace and version, setting the existing field as specified. * - * @param files a List of model Files + * @param files a List of model Files * @param onlyAspectModels get only Aspect Models with Aspects as namespace list. * @return a map where the keys are namespaces and the values are lists of maps containing versions and their associated models */ public Map> groupModelsByNamespaceAndVersion( final List files, final boolean onlyAspectModels ) { - return files.stream().map( file -> { - final Optional metaModelVersionFromFile = Try.of( - () -> AspectModelFileLoader.load( file ).sourceModel().getNsPrefixMap().get( SammNs.SAMM.getShortForm() ) ) - .flatMap( AspectModelUrn::from ).toJavaOptional().map( AspectModelUrn::getVersion ) - .flatMap( KnownVersion::fromVersionString ); - final AspectModel loadedModel = aspectModelLoader.load( file ); - return new AbstractMap.SimpleEntry<>( loadedModel, metaModelVersionFromFile ); - } ).flatMap( - entry -> entry.getKey().files().stream().flatMap( file -> extractModelElement( file, onlyAspectModels ) ).map( modelElement -> { - assert entry.getValue().orElse( null ) != null; - return createModel( modelElement, entry.getValue().orElse( null ) ); - } ) ).collect( Collectors.groupingBy( model -> model.aspectModelUrn().getNamespaceMainPart() ) ).entrySet().stream() - .sorted( Map.Entry.comparingByKey() ) - .collect( Collectors.toMap( Map.Entry::getKey, entry -> groupByVersion( entry.getValue() ), ( v1, v2 ) -> { - throw new RuntimeException( String.format( "Duplicate key for values %s and %s", v1, v2 ) ); - }, LinkedHashMap::new ) ); + final List allModels = loadAndExtractModels( files, onlyAspectModels ); + final Map> modelsByNamespace = groupByNamespace( allModels ); + + return modelsByNamespace.entrySet().stream() + .sorted( Map.Entry.comparingByKey() ) + .collect( Collectors.toMap( + Map.Entry::getKey, + entry -> groupByVersion( entry.getValue() ), + this::throwOnDuplicateKey, + LinkedHashMap::new ) ); + } + + private List loadAndExtractModels( final List files, final boolean onlyAspectModels ) { + return files.stream() + .map( this::loadModelWithVersion ) + .flatMap( entry -> extractModelsFromEntry( entry, onlyAspectModels ) ) + .toList(); + } + + private Map.Entry> loadModelWithVersion( final File file ) { + final RawAspectModelFile rawFile = AspectModelFileLoader.load( file ); + final Optional metaModelVersion = extractMetaModelVersion( rawFile ); + + return new AbstractMap.SimpleEntry<>( rawFile, metaModelVersion ); + } + + private Optional extractMetaModelVersion( final RawAspectModelFile rawFile ) { + return Try.of( () -> rawFile.sourceModel().getNsPrefixMap().get( SammNs.SAMM.getShortForm() ) ) + .flatMap( AspectModelUrn::from ) + .toJavaOptional() + .map( AspectModelUrn::getVersion ) + .flatMap( KnownVersion::fromVersionString ); + } + + private Stream extractModelsFromEntry(final Map.Entry> entry, final boolean onlyAspectModels) { + final KnownVersion version = entry.getValue() + .orElseThrow(() -> new IllegalStateException("Meta model version is required")); + final RawAspectModelFile rawFile = entry.getKey(); + + final String filename = extractFilename(rawFile); + final List resources = collectMetaModelResources(version); + final Resource firstNonBlankSubject = findFirstNonBlankSubject(rawFile.sourceModel(), resources, filename); + + final Model model = new Model(filename, AspectModelUrn.fromUrn(firstNonBlankSubject.getURI()), + version.toVersionString(), true); + + return Stream.of(model); + } + + private String extractFilename(final RawAspectModelFile rawFile) { + return rawFile.sourceLocation() + .map(uri -> Path.of(uri).getFileName().toString()) + .orElse("unnamed file"); + } + + private List collectMetaModelResources(final KnownVersion version) { + final SAMM samm = new SAMM(version); + final SAMMC sammc = new SAMMC(version); + final SAMME samme = new SAMME(version, samm); + + return Stream.of( + Stream.of(samm.Aspect(), samm.Property(), samm.Operation(), samm.Event(), + samm.Entity(), samm.Value(), samm.Characteristic(), samm.Constraint(), + samm.AbstractEntity(), samm.AbstractProperty()), + samme.allEntities(), + sammc.allCharacteristics(), + sammc.allConstraints(), + sammc.allCollections() + ).flatMap(s -> s).toList(); + } + + private Resource findFirstNonBlankSubject(final org.apache.jena.rdf.model.Model sourceModel, final List resources, final String filename) { + return resources.stream() + .flatMap(resource -> sourceModel.listStatements(null, RDF.type, resource).toList().stream()) + .map(Statement::getSubject) + .filter(subject -> !subject.isAnon()) + .findFirst() + .orElseThrow(() -> new IllegalStateException("No non-blank subject found in " + filename)); + } + + private Map> groupByNamespace( final List models ) { + return models.stream() + .collect( Collectors.groupingBy( model -> + model.aspectModelUrn().getNamespaceMainPart() ) ); } private Stream extractModelElement( final AspectModelFile file, final boolean onlyAspectModels ) { + + final Optional aspectElement = file.aspects().stream() + .map( ModelElement.class::cast ) + .findFirst(); + if ( onlyAspectModels ) { - return file.aspects().stream().map( ModelElement.class::cast ).findFirst().stream(); + return aspectElement.stream(); } - return file.aspects().stream().map( ModelElement.class::cast ).findFirst() - .or( () -> file.elements().stream().filter( element -> !element.isAnonymous() ).findAny() ).stream(); + return aspectElement + .or( () -> findFirstNonAnonymousElement( file ) ) + .stream(); } - private Model createModel( final ModelElement element, final KnownVersion version ) { - final String filename = element.getSourceFile().filename().orElse( "unnamed file" ); - return new Model( filename, element.urn(), version.toVersionString(), true ); + private Optional findFirstNonAnonymousElement( final AspectModelFile file ) { + return file.elements().stream() + .filter( element -> !element.isAnonymous() ) + .findAny(); } private List groupByVersion( final List models ) { + final Map uniqueModels = removeDuplicateModels( models ); + final Map> modelsByVersion = groupModelsByVersionString( uniqueModels ); + + return modelsByVersion.entrySet().stream() + .sorted( Map.Entry.comparingByKey() ) + .map( this::createVersionEntry ) + .toList(); + } + + private Map removeDuplicateModels( final List models ) { return models.stream() - .collect( Collectors.toMap( Model::aspectModelUrn, model -> model, ( existing, duplicate ) -> existing, LinkedHashMap::new ) ) - .values().stream().collect( Collectors.groupingBy( model -> model.aspectModelUrn().getVersion() ) ).entrySet().stream() - .sorted( Map.Entry.comparingByKey() ).map( entry -> new Version( entry.getKey(), - entry.getValue().stream().sorted( Comparator.comparing( Model::model ) ).toList() ) ).toList(); + .collect( Collectors.toMap( + Model::aspectModelUrn, + model -> model, + ( existing, duplicate ) -> existing, + LinkedHashMap::new ) ); } + + private Map> groupModelsByVersionString( + final Map uniqueModels ) { + + return uniqueModels.values().stream() + .collect( Collectors.groupingBy( model -> + model.aspectModelUrn().getVersion() ) ); + } + + private Version createVersionEntry( final Map.Entry> entry ) { + final List sortedModels = entry.getValue().stream() + .sorted( Comparator.comparing( Model::model ) ) + .toList(); + return new Version( entry.getKey(), sortedModels ); + } + + private T throwOnDuplicateKey( final T v1, final T v2 ) { + throw new RuntimeException( + String.format( "Duplicate key for values %s and %s", v1, v2 ) ); + } + } diff --git a/aspect-model-editor-service/src/main/java/org/eclipse/esmf/ame/services/utils/ModelUtils.java b/aspect-model-editor-service/src/main/java/org/eclipse/esmf/ame/services/utils/ModelUtils.java index edc734f9..f31678b3 100644 --- a/aspect-model-editor-service/src/main/java/org/eclipse/esmf/ame/services/utils/ModelUtils.java +++ b/aspect-model-editor-service/src/main/java/org/eclipse/esmf/ame/services/utils/ModelUtils.java @@ -233,7 +233,7 @@ public AspectModel get() { * @return the loaded {@link AspectModel} */ public static AspectModel loadModelFromFile( final Path modelPath, final String filePath, final AspectModelLoader aspectModelLoader ) { - final Path path = Paths.get( filePath ).normalize(); + final Path path = Paths.get( filePath.replace( ":", File.separator ) ).normalize(); final String[] pathParts = StreamSupport.stream( path.spliterator(), false ).map( Path::toString ).toArray( String[]::new ); final Path aspectModelPath = constructModelPath( modelPath, pathParts[0], pathParts[1], pathParts[2] ); return aspectModelLoader.load( aspectModelPath.toFile() ); diff --git a/aspect-model-editor-web/pom.xml b/aspect-model-editor-web/pom.xml index 0582862e..6039d642 100644 --- a/aspect-model-editor-web/pom.xml +++ b/aspect-model-editor-web/pom.xml @@ -52,10 +52,6 @@ - - org.apache.velocity - velocity-engine-core - io.micronaut micronaut-http-client diff --git a/aspect-model-editor-web/src/main/java/org/eclipse/esmf/ame/api/ModelController.java b/aspect-model-editor-web/src/main/java/org/eclipse/esmf/ame/api/ModelController.java index d705f235..13b0b9f3 100644 --- a/aspect-model-editor-web/src/main/java/org/eclipse/esmf/ame/api/ModelController.java +++ b/aspect-model-editor-web/src/main/java/org/eclipse/esmf/ame/api/ModelController.java @@ -75,11 +75,9 @@ private AspectModelUrn parseAspectModelUrn( final Optional urn ) { */ @Get() @Produces( MediaTypeExtension.TEXT_TURTLE_VALUE ) - public HttpResponse getModel( @Header( URN ) final Optional urn, - @Header( "file-path" ) final Optional filePath ) { + public HttpResponse getModel( @Header( URN ) final Optional urn ) { final AspectModelUrn aspectModelUrn = parseAspectModelUrn( urn ); - final String path = filePath.orElse( null ); - return HttpResponse.ok( modelService.getModel( aspectModelUrn, path ).content() ); + return HttpResponse.ok( modelService.getModel( aspectModelUrn, null ).content() ); } /** @@ -93,8 +91,7 @@ public HttpResponse getModel( @Header( URN ) final Optional urn, * @return True if the element exists in a different file, false otherwise */ @Get( uri = "check-element", consumes = MediaType.APPLICATION_JSON ) - public HttpResponse checkElementExists( @Header( URN ) final Optional urn, - @QueryValue() final String fileName ) { + public HttpResponse checkElementExists( @Header( URN ) final Optional urn, @QueryValue() final String fileName ) { final AspectModelUrn aspectModelUrn = parseAspectModelUrn( urn ); return HttpResponse.ok( modelService.checkElementExists( aspectModelUrn, fileName ) ); } @@ -111,7 +108,7 @@ public HttpResponse> getModelsBatch( @Body final List - 2.11.1 + 2.13.0 4.7.6 @@ -84,13 +84,9 @@ 24.1.2 3.0.2 6.0.0 - 3.12.0 0.9.23 - 1.3 2.17.2 2.17.2 - 1.26.0 - 2.4 1.5.0 @@ -322,11 +318,6 @@ ${jakarta-version} provided - - org.apache.commons - commons-exec - ${commons-exec-version} - commons-codec commons-codec @@ -348,16 +339,6 @@ jackson-core ${jackson-core-version} - - org.apache.commons - commons-compress - ${commons-compress-version} - - - org.apache.velocity - velocity-engine-core - ${velocity-version} - org.graphper graph-support-core