Skip to content

Commit 7eb4259

Browse files
authored
Merge pull request #287 from bci-oss/278-handle-models-with-multiple-aspects
Handle models with any number of Aspects in artifact generators
2 parents d2dce4d + 899f0af commit 7eb4259

File tree

61 files changed

+708
-570
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+708
-570
lines changed

core/sds-aspect-meta-model-interface/src/main/java/io/openmanufacturing/sds/aspectmodel/resolver/AspectMetaModelResourceResolver.java

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,20 +43,6 @@ public interface AspectMetaModelResourceResolver {
4343
*/
4444
Try<VersionedModel> mergeMetaModelIntoRawModel( final Model rawModel, final VersionNumber version );
4545

46-
/**
47-
* Parses an Aspect (meta) model URN into an {@link AspectModelUrn}
48-
*
49-
* @param uri The Aspect (meta) model URN
50-
* @return The {@link AspectModelUrn} if parsing succeeds, an {@link UrnSyntaxException} otherwise
51-
*/
52-
default Try<AspectModelUrn> getAspectModelUrn( final String uri ) {
53-
try {
54-
return Try.success( AspectModelUrn.fromUrn( uri ) );
55-
} catch ( final UrnSyntaxException exception ) {
56-
return Try.failure( exception );
57-
}
58-
}
59-
6046
/**
6147
* Retrieves the meta model version an Aspect model uses
6248
*
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
/*
2+
* Copyright (c) 2023 Robert Bosch Manufacturing Solutions GmbH
3+
*
4+
* See the AUTHORS file(s) distributed with this work for additional
5+
* information regarding authorship.
6+
*
7+
* This Source Code Form is subject to the terms of the Mozilla Public
8+
* License, v. 2.0. If a copy of the MPL was not distributed with this
9+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
10+
*
11+
* SPDX-License-Identifier: MPL-2.0
12+
*/
13+
14+
package io.openmanufacturing.sds.metamodel;
15+
16+
import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;
17+
18+
/**
19+
* The AspectContext wraps a loaded/resolved Aspect Model and a single Aspect that was instantiated from this model, i.e.,
20+
* which must be defined in the RDF model.
21+
* @param rdfModel the RDF model
22+
* @param aspect the Aspect
23+
*/
24+
public record AspectContext(VersionedModel rdfModel, Aspect aspect) {
25+
}

core/sds-aspect-meta-model-java/src/main/java/io/openmanufacturing/sds/metamodel/loader/AspectModelLoader.java

Lines changed: 43 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,13 @@
1313

1414
package io.openmanufacturing.sds.metamodel.loader;
1515

16+
import java.io.File;
1617
import java.util.ArrayList;
1718
import java.util.List;
1819
import java.util.Map;
1920
import java.util.Optional;
2021
import java.util.Set;
22+
import java.util.function.Predicate;
2123
import java.util.stream.Collectors;
2224

2325
import org.apache.jena.rdf.model.Model;
@@ -41,6 +43,7 @@
4143
import io.openmanufacturing.sds.aspectmodel.versionupdate.MigratorService;
4244
import io.openmanufacturing.sds.aspectmodel.vocabulary.BAMM;
4345
import io.openmanufacturing.sds.metamodel.Aspect;
46+
import io.openmanufacturing.sds.metamodel.AspectContext;
4447
import io.openmanufacturing.sds.metamodel.ModelElement;
4548
import io.openmanufacturing.sds.metamodel.ModelNamespace;
4649
import io.openmanufacturing.sds.metamodel.NamedElement;
@@ -79,7 +82,7 @@ private AspectModelLoader() {
7982
*
8083
* @see AspectModelResolver
8184
*/
82-
@Deprecated
85+
@Deprecated( forRemoval = true )
8386
public static Try<Aspect> fromVersionedModel( final VersionedModel versionedModel ) {
8487
return getSingleAspect( versionedModel );
8588
}
@@ -103,12 +106,12 @@ private static void validateNamespaceOfCustomUnits( final BAMM bamm, final Model
103106
* Creates an {@link Aspect} instance from a Turtle input model.
104107
*
105108
* @param versionedModel The RDF model representation of the Aspect model
106-
* @return The Aspect *
109+
* @return The Aspect
107110
* @deprecated use {@link #getSingleAspectUnchecked(VersionedModel)} instead to retain the same semantics, but better use
108111
* {@link #getElementsUnchecked(VersionedModel)} instead to also properly handle models that contain not exactly one Aspect
109112
* @see #fromVersionedModel(VersionedModel)
110113
*/
111-
@Deprecated
114+
@Deprecated( forRemoval = true )
112115
public static Aspect fromVersionedModelUnchecked( final VersionedModel versionedModel ) {
113116
return fromVersionedModel( versionedModel ).getOrElseThrow( cause -> {
114117
LOG.error( "Could not load Aspect", cause );
@@ -228,12 +231,7 @@ public static List<Aspect> getAspectsUnchecked( final VersionedModel versionedMo
228231
* @return the single Aspect contained in the model
229232
*/
230233
public static Try<Aspect> getSingleAspect( final VersionedModel versionedModel ) {
231-
return getAspects( versionedModel ).flatMap( aspects -> {
232-
if ( aspects.size() != 1 ) {
233-
return Try.failure( new InvalidRootElementCountException( "Aspect model does not contain exactly one Aspect" ) );
234-
}
235-
return Try.success( aspects.get( 0 ) );
236-
} );
234+
return getSingleAspect( versionedModel, aspect -> true );
237235
}
238236

239237
/**
@@ -252,4 +250,40 @@ public static Aspect getSingleAspectUnchecked( final VersionedModel versionedMod
252250
throw new AspectLoadingException( cause );
253251
} );
254252
}
253+
254+
/**
255+
* Similar to {@link #getSingleAspect(VersionedModel)}, except that a predicate can be provided to select which of potentially
256+
* multiple aspects should be selected
257+
* @param versionedModel the RDF model reprensentation of the Aspect model
258+
* @param selector the predicate to select an Aspect
259+
* @return the selected Aspect, or a failure if 0 or more than 1 matching Aspects were found
260+
*/
261+
public static Try<Aspect> getSingleAspect( final VersionedModel versionedModel, final Predicate<Aspect> selector ) {
262+
return getAspects( versionedModel ).flatMap( allAspects -> {
263+
final List<Aspect> aspects = allAspects.stream().filter( selector::test ).toList();
264+
return switch ( aspects.size() ) {
265+
case 1 -> Try.success( aspects.iterator().next() );
266+
case 0 -> Try.failure( new InvalidRootElementCountException( "No Aspects were found in the model" ) );
267+
default -> Try.failure( new AspectLoadingException( "Multiple Aspects were found in the resolved model" ) );
268+
};
269+
} );
270+
}
271+
272+
/**
273+
* Convenience method to create an {@link AspectContext} directly from a model file. This method makes the following assumptions:
274+
* <ul>
275+
* <li>The model file is located in a directory structure as required by the {@link io.openmanufacturing.sds.aspectmodel.resolver.FileSystemStrategy}</li>
276+
* <li>The closure of the loaded model contains exactly one Aspect</li>
277+
* <li>The Aspect has the same name as the file's basename</li>
278+
* </ul>
279+
* The method is intended for use in tests and comparable use cases, not as a general replacement for loading Aspect Models, since it does not
280+
* handle model files with less or more than one Aspect.
281+
* @param input the model file
282+
* @return the loaded Aspect Context
283+
*/
284+
public static Try<AspectContext> getAspectContext( final File input ) {
285+
return AspectModelResolver.loadAndResolveModel( input ).flatMap( versionedModel ->
286+
getSingleAspect( versionedModel, aspect -> aspect.getName().equals( input.getName() ) )
287+
.map( aspect -> new AspectContext( versionedModel, aspect ) ) );
288+
}
255289
}

core/sds-aspect-meta-model-java/src/main/java/io/openmanufacturing/sds/metamodel/loader/MetaModelBaseAttributes.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -203,15 +203,15 @@ private static String getSyntheticName( final Resource modelElement, final Model
203203
throw new AspectLoadingException( "At least one anonymous node in the model does not have a parent with a regular name." );
204204
}
205205
final String parentModelElementUri = namedParent.getURI();
206-
final String parentModelElementName = metaModelResourceResolver.getAspectModelUrn( parentModelElementUri )
206+
final String parentModelElementName = AspectModelUrn.from( parentModelElementUri )
207207
.toJavaOptional()
208208
.map( AspectModelUrn::getName )
209209
.map( StringUtils::capitalize )
210210
.orElse( "" );
211211

212212
final Resource modelElementType = getModelElementType( modelElement, bamm );
213213
final String modelElementTypeUri = modelElementType.getURI();
214-
final String modelElementTypeName = metaModelResourceResolver.getAspectModelUrn( modelElementTypeUri )
214+
final String modelElementTypeName = AspectModelUrn.from( modelElementTypeUri )
215215
.toJavaOptional()
216216
.map( AspectModelUrn::getName )
217217
.orElse( "" );

core/sds-aspect-meta-model-resolver/src/main/java/io/openmanufacturing/sds/aspectmodel/resolver/services/SdsAspectMetaModelResourceResolver.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ public Set<VersionNumber> getUsedMetaModelVersions( final Model model ) {
220220
.map( RDFNode::asResource )
221221
.map( Resource::getURI )
222222
.filter( uri -> uri.startsWith( bammUrnStart ) )
223-
.flatMap( uri -> getAspectModelUrn( uri ).toJavaStream() )
223+
.flatMap( uri -> AspectModelUrn.from( uri ).toJavaStream() )
224224
.filter( urn -> (urn.getElementType().equals( ElementType.META_MODEL ) || urn.getElementType().equals( ElementType.CHARACTERISTIC )) )
225225
.map( AspectModelUrn::getVersion )
226226
.map( VersionNumber::parse )

core/sds-aspect-meta-model-resolver/src/test/java/io/openmanufacturing/sds/aspectmodel/resolver/services/SdsAspectMetaModelResourceResolverTest.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@
2727
import org.junit.jupiter.params.provider.MethodSource;
2828

2929
import io.openmanufacturing.sds.aspectmetamodel.KnownVersion;
30-
import io.openmanufacturing.sds.aspectmodel.VersionNumber;
3130
import io.openmanufacturing.sds.aspectmodel.MissingMetaModelVersionException;
31+
import io.openmanufacturing.sds.aspectmodel.VersionNumber;
3232
import io.openmanufacturing.sds.aspectmodel.urn.AspectModelUrn;
3333
import io.openmanufacturing.sds.test.MetaModelVersions;
3434
import io.vavr.control.Try;
@@ -82,20 +82,18 @@ public void testGetBammVersionInvalidMetaModelUrnElementExpectFailure( final Kno
8282
@ParameterizedTest
8383
@MethodSource( "allVersions" )
8484
public void testGetAspectModelUrnExpectSuccess( final KnownVersion metaModelVersion ) {
85-
final Try<AspectModelUrn> aspectModelUrn = aspectMetaModelResourceResolver.getAspectModelUrn(
85+
final Try<AspectModelUrn> aspectModelUrn = AspectModelUrn.from(
8686
"urn:bamm:io.openmanufacturing:meta-model:" + metaModelVersion.toVersionString() + "#Aspect" );
8787
assertThat( aspectModelUrn ).isSuccess();
8888
org.assertj.core.api.Assertions.assertThat( aspectModelUrn.get().getUrn().toString() )
89-
.isEqualTo( "urn:bamm:io.openmanufacturing:meta-model:" + metaModelVersion
90-
.toVersionString() + "#Aspect" );
89+
.isEqualTo( "urn:bamm:io.openmanufacturing:meta-model:" + metaModelVersion
90+
.toVersionString() + "#Aspect" );
9191
}
9292

9393
@ParameterizedTest
9494
@MethodSource( "allVersions" )
9595
public void testGetAspectModelUrnInvalidUrnExpectFailure( final KnownVersion metaModelVersion ) {
96-
final Try<AspectModelUrn> aspectModelUrn = aspectMetaModelResourceResolver
97-
.getAspectModelUrn(
98-
"urn:foo:io.openmanufacturing:meta-model:" + metaModelVersion.toVersionString() );
96+
final Try<AspectModelUrn> aspectModelUrn = AspectModelUrn.from( "urn:foo:io.openmanufacturing:meta-model:" + metaModelVersion.toVersionString() );
9997
assertThat( aspectModelUrn ).isFailure();
10098
}
10199

@@ -120,7 +118,7 @@ public void testLoadMetaModelExpectSuccess( final KnownVersion metaModelVersion
120118
final Model model = aspectMetaModelResourceResolver.loadMetaModel( metaModelVersion ).get();
121119

122120
org.assertj.core.api.Assertions.assertThat( model.contains( ResourceFactory.createResource(
123-
"urn:bamm:io.openmanufacturing:meta-model:" + metaModelVersion.toVersionString() + "#value" ),
121+
"urn:bamm:io.openmanufacturing:meta-model:" + metaModelVersion.toVersionString() + "#value" ),
124122
RDF.type, (RDFNode) null ) ).isTrue();
125123
}
126124
}

core/sds-aspect-model-aas-generator/src/test/java/io/openmanufacturing/sds/aspectmodel/aas/AspectModelAASGeneratorTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -264,7 +264,7 @@ private AssetAdministrationShellEnvironment getAssetAdministrationShellFromAspec
264264

265265
private Aspect loadAspect( final TestAspect testAspect ) {
266266
final VersionedModel model = TestResources.getModel( testAspect, KnownVersion.getLatest() ).get();
267-
return AspectModelLoader.fromVersionedModelUnchecked( model );
267+
return AspectModelLoader.getSingleAspectUnchecked( model );
268268
}
269269

270270
private AssetAdministrationShellEnvironment loadAASX( final ByteArrayOutputStream byteStream ) throws DeserializationException {

core/sds-aspect-model-document-generators/src/main/java/io/openmanufacturing/sds/aspectmodel/generator/AspectModelHelper.java

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
import java.util.Set;
2222
import java.util.stream.Collectors;
2323

24-
import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;
24+
import io.openmanufacturing.sds.aspectmetamodel.KnownVersion;
2525
import io.openmanufacturing.sds.metamodel.Aspect;
2626
import io.openmanufacturing.sds.metamodel.ModelElement;
2727
import io.openmanufacturing.sds.metamodel.ComplexType;
@@ -35,14 +35,14 @@
3535
import io.openmanufacturing.sds.metamodel.visitor.AspectStreamTraversalVisitor;
3636

3737
public class AspectModelHelper {
38-
private final VersionedModel modelVersion;
38+
private final KnownVersion metaModelVersion;
3939

40-
public AspectModelHelper( final VersionedModel modelVersion ) {
41-
this.modelVersion = modelVersion;
40+
public AspectModelHelper( final KnownVersion metaModelVersion ) {
41+
this.metaModelVersion = metaModelVersion;
4242
}
4343

44-
public VersionedModel getModelVersion() {
45-
return modelVersion;
44+
public KnownVersion getMetaModelVersion() {
45+
return metaModelVersion;
4646
}
4747

4848
public static List<Property> sortPropertiesByPreferredName( final List<Property> properties, final Locale locale ) {

core/sds-aspect-model-document-generators/src/main/java/io/openmanufacturing/sds/aspectmodel/generator/docu/AspectModelDocumentationGenerator.java

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,10 @@
4040
import io.openmanufacturing.sds.aspectmodel.generator.LanguageCollector;
4141
import io.openmanufacturing.sds.aspectmodel.generator.TemplateEngine;
4242
import io.openmanufacturing.sds.aspectmodel.generator.diagram.AspectModelDiagramGenerator;
43-
import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;
4443
import io.openmanufacturing.sds.metamodel.Aspect;
44+
import io.openmanufacturing.sds.metamodel.AspectContext;
4545
import io.openmanufacturing.sds.metamodel.NamedElement;
4646
import io.openmanufacturing.sds.metamodel.Scalar;
47-
import io.openmanufacturing.sds.metamodel.loader.AspectModelLoader;
4847
import io.openmanufacturing.sds.metamodel.visitor.AspectStreamTraversalVisitor;
4948

5049
/**
@@ -76,17 +75,15 @@ public String getArtifactFilename( final String artifactName ) {
7675
}
7776
}
7877

79-
private final Aspect aspect;
80-
private final VersionedModel versionedModel;
78+
private final AspectContext context;
8179
private Locale selectedLanguage = null;
8280

83-
public AspectModelDocumentationGenerator( final VersionedModel versionedModel ) {
84-
this.versionedModel = versionedModel;
85-
aspect = AspectModelLoader.fromVersionedModelUnchecked( versionedModel );
81+
public AspectModelDocumentationGenerator( final AspectContext context ) {
82+
this.context = context;
8683
}
8784

88-
public AspectModelDocumentationGenerator( final String language, final VersionedModel versionedModel ) {
89-
this( versionedModel );
85+
public AspectModelDocumentationGenerator( final String language, final AspectContext context ) {
86+
this( context );
9087
selectedLanguage = Locale.forLanguageTag( language );
9188
}
9289

@@ -160,7 +157,7 @@ private void generateInternal( final Function<String, OutputStream> nameMapper,
160157
}
161158

162159
private void generateHtmlDocu( final Function<String, OutputStream> nameMapper, final Format format, final Locale desiredLanguage ) {
163-
final Set<Locale> languagesInModel = LanguageCollector.collectUsedLanguages( aspect );
160+
final Set<Locale> languagesInModel = LanguageCollector.collectUsedLanguages( context.aspect() );
164161
if ( !languagesInModel.contains( desiredLanguage ) ) {
165162
throw new RuntimeException( String.format( "The model does not contain the desired language: %s.", desiredLanguage.toString() ) );
166163
}
@@ -169,15 +166,15 @@ private void generateHtmlDocu( final Function<String, OutputStream> nameMapper,
169166
}
170167

171168
private void generateHtmlDocu( final Function<String, OutputStream> nameMapper, final Format format ) {
172-
final Set<Locale> languagesInModel = LanguageCollector.collectUsedLanguages( aspect );
169+
final Set<Locale> languagesInModel = LanguageCollector.collectUsedLanguages( context.aspect() );
173170
final Set<Locale> languages = languagesInModel.isEmpty() ? Set.of( Locale.ENGLISH ) : languagesInModel;
174171
generateHtmlDocu( nameMapper, format, languages );
175172
}
176173

177174
private void generateHtmlDocu( final Function<String, OutputStream> nameMapper, final Format format, final Set<Locale> languages ) {
178-
final Map<String, Object> context = new HashMap<>();
179-
context.put( "aspectModel", aspect );
180-
context.put( "aspectModelHelper", new AspectModelHelper( versionedModel ) );
175+
final Map<String, Object> configuration = new HashMap<>();
176+
configuration.put( "aspectModel", context.aspect() );
177+
configuration.put( "aspectModelHelper", new AspectModelHelper( context.aspect().getMetaModelVersion() ) );
181178

182179
final Properties engineConfiguration = new Properties();
183180
engineConfiguration.put( "file.resource.loader.path", ".," + DOCU_TEMPLATE_ROOT_DIR + "/html" );
@@ -194,13 +191,13 @@ private void generateHtmlDocu( final Function<String, OutputStream> nameMapper,
194191
final Predicate<Locale> byLanguage = locale -> selectedLanguage == null || locale.getLanguage().equals( selectedLanguage.getLanguage() );
195192
languages.stream().filter( byLanguage ).forEach( language -> {
196193

197-
logMissingTranslations( aspect, language );
194+
logMissingTranslations( context.aspect(), language );
198195

199-
context.put( "i18n", new I18nLanguageBundle( language ) );
200-
context.put( "Scalar", Scalar.class );
201-
final TemplateEngine templateEngine = new TemplateEngine( context, engineConfiguration );
196+
configuration.put( "i18n", new I18nLanguageBundle( language ) );
197+
configuration.put( "Scalar", Scalar.class );
198+
final TemplateEngine templateEngine = new TemplateEngine( configuration, engineConfiguration );
202199

203-
final String artifactName = getArtifactName( aspect, language );
200+
final String artifactName = getArtifactName( context.aspect(), language );
204201
if ( nameMapper instanceof BufferingNameMapper ) {
205202
((BufferingNameMapper) nameMapper).setLanguageForArtifact( artifactName, language );
206203
}
@@ -244,7 +241,7 @@ private String byteArrayOutputStreamToString( final ByteArrayOutputStream output
244241
}
245242

246243
private String insertAspectModelDiagram( final String html, final Locale language ) throws IOException {
247-
final AspectModelDiagramGenerator diagramGenerator = new AspectModelDiagramGenerator( versionedModel );
244+
final AspectModelDiagramGenerator diagramGenerator = new AspectModelDiagramGenerator( context.rdfModel() );
248245
final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
249246
diagramGenerator.generateDiagram( AspectModelDiagramGenerator.Format.SVG, language, buffer );
250247
final String encodedImage = "data:image/svg+xml;base64," +

core/sds-aspect-model-document-generators/src/main/java/io/openmanufacturing/sds/aspectmodel/generator/json/AspectModelJsonPayloadGenerator.java

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,6 @@
5858
import io.openmanufacturing.sds.aspectmodel.generator.NumericTypeTraits;
5959
import io.openmanufacturing.sds.aspectmodel.jackson.AspectModelJacksonModule;
6060
import io.openmanufacturing.sds.aspectmodel.resolver.services.DataType;
61-
import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;
6261
import io.openmanufacturing.sds.aspectmodel.vocabulary.BAMM;
6362
import io.openmanufacturing.sds.characteristic.Collection;
6463
import io.openmanufacturing.sds.characteristic.Either;
@@ -70,6 +69,7 @@
7069
import io.openmanufacturing.sds.constraint.RegularExpressionConstraint;
7170
import io.openmanufacturing.sds.metamodel.AbstractEntity;
7271
import io.openmanufacturing.sds.metamodel.Aspect;
72+
import io.openmanufacturing.sds.metamodel.AspectContext;
7373
import io.openmanufacturing.sds.metamodel.Characteristic;
7474
import io.openmanufacturing.sds.metamodel.ComplexType;
7575
import io.openmanufacturing.sds.metamodel.Constraint;
@@ -81,7 +81,6 @@
8181
import io.openmanufacturing.sds.metamodel.Value;
8282
import io.openmanufacturing.sds.metamodel.datatypes.Curie;
8383
import io.openmanufacturing.sds.metamodel.impl.BoundDefinition;
84-
import io.openmanufacturing.sds.metamodel.loader.AspectModelLoader;
8584

8685
public class AspectModelJsonPayloadGenerator extends AbstractGenerator {
8786
/**
@@ -102,8 +101,8 @@ public AspectModelJsonPayloadGenerator( final Aspect aspect ) {
102101
this( aspect, new BAMM( aspect.getMetaModelVersion() ), new Random() );
103102
}
104103

105-
public AspectModelJsonPayloadGenerator( final VersionedModel versionedModel ) {
106-
this( AspectModelLoader.fromVersionedModelUnchecked( versionedModel ) );
104+
public AspectModelJsonPayloadGenerator( final AspectContext context ) {
105+
this( context.aspect() );
107106
}
108107

109108
public AspectModelJsonPayloadGenerator( final Aspect aspect, final Random randomStrategy ) {

0 commit comments

Comments
 (0)