Java Tooling for Working with Aspect Models
General Considerations
In this section, the Java APIs for working with Aspect Models are described. All of the components described in the
following subsections can either be included in your project using a dedicated dependency (as described in the
respective subsection), or you can use the aspect-model-starter
artifact that aggregates all necessary dependencies:
- Maven
-
<dependency> <groupId>org.eclipse.esmf</groupId> <artifactId>esmf-aspect-model-starter</artifactId> <version>2.7.0</version> </dependency>
- Gradle Groovy DSL
-
implementation 'org.eclipse.esmf:esmf-aspect-model-starter:2.7.0'
- Gradle Kotlin DSL
-
implementation("org.eclipse.esmf:esmf-aspect-model-starter:2.7.0")
The error handling in many APIs is done using the
Try
type provided by the Java
library Vavr. This is similar to a java.lang.Optional
in that a successful result can be
processed using .map()
or .flatMap()
, but if an error occurs, the object can also provide the
original exception using the getCause()
method. Compared with throwing exceptions, using a
Try<T>
as a return type instead has the advantage of enabling composable error handling. Please
see the Vavr User Guide for more information.
You generally have multiple options for handling Try<T>
results, similar to dealing with Optional
:
Show used imports
import java.util.ArrayList;
import java.util.List;
import io.vavr.collection.Stream;
import io.vavr.control.Try;
final Try<List<String>> tryList = someMethodReturningTryOfListOfString();
// Option 1: forEach to execute code on the value
tryList.forEach( valueList -> {
final List<String> result1 = new ArrayList<>();
for ( final String element : valueList ) {
result1.add( element.toUpperCase() );
}
} );
// Option 2: map/flatMap values
final List<String> result2 = tryList.toStream().flatMap( Stream::ofAll )
.map( String::toUpperCase ).toJavaList();
// Option 3: "extract" value and throw on failure
final List<String> valueList = tryList.getOrElseThrow( () ->
new RuntimeException( tryList.getCause() ) );
final List<String> result3 = new ArrayList<>();
for ( final String element : valueList ) {
result3.add( element.toUpperCase() );
}
Getting the right version
The tooling can be made available in two different versions: release
and milestone
. The release version represents a stable version of the product
and can be referenced in your projects in the usual way from the Maven Central repository. There is also the possibility to have the intermediate builds
made available, these are called milestone
builds. Instead of Maven Central, these are released via
GitHub Packages mechanism.
To be able to use the artifacts released in this way in your projects, first the right repository has to be added to your pom.xml
file:
<repositories>
<repository>
<id>github</id>
<name>ESMF SDK</name>
<url>https://maven.pkg.github.com/eclipse-esmf/esmf-sdk</url>
<releases><enabled>true</enabled></releases>
<snapshots><enabled>true</enabled></snapshots>
</repository>
</repositories>
Then the desired dependencies can be referenced in the usual way. For an example, please refer to Github - Installing a package.
JDK requirements
The esmf-sdk components are built with Java 17 and require a JDK >= 17 such as Adoptium Temurin.
The esmf-sdk can also be used with a Java 17-compatible GraalVM JDK. With GraalVM you need to make the Graal JavaScript component available, as parts of the SDK such as the Aspect Model validation component require embedded JavaScript execution:
-
For using the esmf-sdk in the GraalVM itself, install the JS component using the GraalVM Updater:
gu install js
-
For using the esmf-sdk with GraalVM
native-image
, be sure to use the--language:js
option when building the native image; for more information see polyglot programming.
Parsing Aspect Model URNs
The aspect-model-urn
artifact provides a way to parse and validate Aspect model element URNs as described in the
specification.
Show used imports
import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;
final AspectModelUrn urn = AspectModelUrn.fromUrn( "urn:samm:com.example:1.0.0#Example" );
final String namespace = urn.getNamespace(); // com.example
final String name = urn.getName(); // Example
final String version = urn.getVersion(); // 1.0.0
final String urnPrefix = urn.getUrnPrefix(); // urn:samm:com.example:1.0.0#
To include the artifact, use the following dependencies:
- Maven
-
<dependency> <groupId>org.eclipse.esmf</groupId> <artifactId>esmf-aspect-model-urn</artifactId> <version>2.7.0</version> </dependency>
- Gradle Groovy DSL
-
implementation 'org.eclipse.esmf:esmf-aspect-model-urn:2.7.0'
- Gradle Kotlin DSL
-
implementation("org.eclipse.esmf:esmf-aspect-model-urn:2.7.0")
Loading and Saving Aspect Models
Aspect models are, like their Meta Model, described using the Resource Description Format (RDF, [rdf11]) and the Terse RDF Triple Language syntax (TTL, [turtle]). There are two ways of working with Aspect models: Either the model is loaded as an RDF model where the abstraction level directly corresponds to the RDF/Turtle serialization, or the RDF is parsed into a native Java Aspect model representation where the abstraction level corresponds to the SAMM concepts. Both approaches have different use cases and advantages and both are supported by the Aspect model Java tooling:
Working on the RDF level | Using the Java Aspect model |
---|---|
|
|
As a rule of thumb, if your use case mostly consists of consuming Aspect models, you should prefer the Java Aspect model, if you create or edit Aspect models, this is better done using the RDF API.
Understanding Model Resolution
Loading an Aspect model in either the RDF-based or the Java Aspect model-based APIs does not only comprise parsing the Turtle file, but also the resolution of elements on the RDF level:
-
An Aspect model refers to meta model elements (everything that starts with
samm:
), and can refer to namespaces of shared elements (samm-c
,samm-e
andunit
) -
It can also refer to elements from other Aspect models or model elements defined in separate Turtle files
You use the model resolver to load an Aspect model, which takes care of the first point: The used meta model elements
and elements from shared namespaces are automatically resolved and added to the loaded RDF model. For the second point,
you need to provide the model resolver with the information on where to find other Aspect models: In memory, in a local
file system, in a remote model repository etc. This is done using a resolution strategy, which is a function from some
type T
to a Try
of a RDF model. Several commonly used resolution strategies are readily available:
-
The
FileSystemStrategy
resolves ajava.nio.file.Path
for a file relative to the models directory -
The
ClassPathStrategy
resolves a model from resources in the Java class path -
The
EitherStrategy
can be used to chain two different strategies of different types
To implement custom resolution strategies (e.g., to resolve models against a different blob storage API), you can base
your implementation on the AbstractResolutionStrategy
. Using the model resolver requires at least one resolution
strategy that can resolve AspectModelUrn
s (because references in an Aspect model to external model elements use
their respective URNs) and can use another resolution strategy, for example for file paths. The following sections show
examples for the different variants.
Loading an Aspect Model to work on the RDF Level
The following example shows how to use the AspectModelResolver
to load an Aspect Model
Show used imports
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.vocabulary.RDF;
import org.eclipse.esmf.samm.KnownVersion;
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.aspectmodel.resolver.FileSystemStrategy;
import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy;
import org.eclipse.esmf.aspectmodel.resolver.services.VersionedModel;
import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;
import org.eclipse.esmf.aspectmodel.vocabulary.SAMM;
import io.vavr.control.Try;
final File modelFile = new File( "aspect-models/org.eclipse.esmf.examples.movement/1.0.0/Movement.ttl" ); (1)
final Try<VersionedModel> tryModel = AspectModelResolver.loadAndResolveModel( modelFile ); (2)
// Let's do something with the loaded model on RDF level
tryModel.forEach( versionedModel -> { (3)
final SAMM samm = new SAMM( KnownVersion.fromVersionString( versionedModel.getMetaModelVersion().toString() ).get() );
final Model rdfModel = versionedModel.getModel();
final List<Statement> result = rdfModel.listStatements( null, RDF.type, samm.Aspect() ).toList();(4)
} );
1 | The file must be located in a directory according to the models directory structure. |
2 | You can check if loading succeeded using versionedModel.isSuccess() and
versionedModel.isFailure() . If loading fails, versionedModel.getCause() provides more
information. |
3 | The code inside forEach is only executed if the model could be loaded successfully. |
4 | List all statements in the RDF model that declare a samm:Aspect . |
If you want to choose which model resolution strategy is used, you use the resolveAspectModel()
method of the AspectModelResolver
. In this case, you need to provide the resolution strategy and
the initial input (InputStream
, Model
or a String
containing RDF/Turtle), or one or more URNs
of model elements to resolve - have a look at the methods provided by AspectModelResolver
.
Show used imports
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Statement;
import org.apache.jena.vocabulary.RDF;
import org.eclipse.esmf.samm.KnownVersion;
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.aspectmodel.resolver.FileSystemStrategy;
import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy;
import org.eclipse.esmf.aspectmodel.resolver.services.VersionedModel;
import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;
import org.eclipse.esmf.aspectmodel.vocabulary.SAMM;
import io.vavr.control.Try;
// The directory containing the models folder structure,
// see models directory structure
final Path modelsRoot = Paths.get( "aspect-models" );
final ResolutionStrategy fileSystemStrategy = new FileSystemStrategy( modelsRoot );
final Try<VersionedModel> tryModel = new AspectModelResolver().resolveAspectModel( fileSystemStrategy,
AspectModelUrn.fromUrn( "urn:samm:org.eclipse.esmf.examples.movement:1.0.0#Movement" ) );
To include the model resolver artifact, use the following dependencies:
- Maven
-
<dependency> <groupId>org.eclipse.esmf</groupId> <artifactId>esmf-aspect-model-resolver</artifactId> <version>2.7.0</version> </dependency>
- Gradle Groovy DSL
-
implementation 'org.eclipse.esmf:esmf-aspect-model-resolver:2.7.0'
- Gradle Kotlin DSL
-
implementation("org.eclipse.esmf:esmf-aspect-model-resolver:2.7.0")
Loading an Aspect Model to work on the Java Aspect Model Level
If you have retrieved a Try<VersionedModel>
as shown above, you can use the AspectModelLoader
to
turn the RDF graph representation of the Aspect Model into a type-safe Java object. You can use the
getElements()
method to get all model elements from the Aspect Model, or, if you expect the model
to contain exactly one Aspect (for example in unit tests), you can use the getSingleAspect()
method.
Show used imports
import java.io.File;
import java.util.List;
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.metamodel.NamedElement;
import org.eclipse.esmf.metamodel.loader.AspectModelLoader;
import io.vavr.collection.Stream;
final File modelFile = new File( "aspect-models/org.eclipse.esmf.examples.movement/1.0.0/Movement.ttl" );
final List<String> result = AspectModelResolver.loadAndResolveModel( modelFile )
.flatMap( AspectModelLoader::getElements )
.toStream()
.flatMap( Stream::ofAll )
.filter( element -> element.is( NamedElement.class ) )
.map( element -> element.as( NamedElement.class ) )
.map( modelElement -> String.format( "Model element: %s has URN %s%n",
modelElement.getName(), modelElement.getAspectModelUrn() ) )
.toJavaList();
To include the Java Aspect model artifact, use the following dependencies:
- Maven
-
<dependency> <groupId>org.eclipse.esmf</groupId> <artifactId>esmf-aspect-meta-model-java</artifactId> <version>2.7.0</version> </dependency>
- Gradle Groovy DSL
-
implementation 'org.eclipse.esmf:esmf-aspect-meta-model-java:2.7.0'
- Gradle Kotlin DSL
-
implementation("org.eclipse.esmf:esmf-aspect-meta-model-java:2.7.0")
Serializing a Java Aspect Model into a RDF/Turtle Aspect Model
There are two serialization components available: One to turn a Java Aspect model into a Jena RDF model (the reverse
operation of what the AspectModelLoader
does) and one to serialize the resulting Jena RDF model in Turtle syntax,
formatted according to the guidelines described in the SAMM specification.
Show used imports
import java.io.File;
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.aspectmodel.serializer.AspectSerializer;
import org.eclipse.esmf.metamodel.Aspect;
import org.eclipse.esmf.metamodel.loader.AspectModelLoader;
// Aspect as created by the AspectModelLoader
final Aspect aspect = // ...
// A String that contains the pretty printed Aspect Model
String aspectString = AspectSerializer.INSTANCE.apply( aspect );
To include the serializer artifact, use the following dependencies:
- Maven
-
<dependency> <groupId>org.eclipse.esmf</groupId> <artifactId>esmf-aspect-model-serializer</artifactId> <version>2.7.0</version> </dependency>
- Gradle Groovy DSL
-
implementation 'org.eclipse.esmf:esmf-aspect-model-serializer:2.7.0'
- Gradle Kotlin DSL
-
implementation("org.eclipse.esmf:esmf-aspect-model-serializer:2.7.0")
Validating Aspect Models
Aspect Models are validated using the AspectModelValidator
. Validation returns a list of
Violation
s. A violation has a human-readable message and a unique error code and provides access
to the EvaluationContext
which contains references to the model element that caused the
violation and the SHACL shape that triggered the violation.
Each possible type of violation is a subtype of the Violation
interface and provides additional
context information specific to this type, for example, the MinLengthViolation
provides int min
(the allowed length) and int actual
(the length that was encountered in the model).
Consider the following example:
Show used imports
import java.util.List;
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.aspectmodel.resolver.services.VersionedModel;
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 io.vavr.control.Try;
import java.io.File;
// Try<VersionedModel> as returned by the AspectModelResolver
final Try<VersionedModel> tryModel = // ...
final List<Violation> violations = new AspectModelValidator().validateModel( tryModel );
if ( violations.isEmpty() ) {
// Aspect Model is valid!
return;
}
final String validationReport = new ViolationFormatter().apply( violations ); (1)
final String detailedReport = new DetailedViolationFormatter().apply( violations );
class MyViolationVisitor implements Violation.Visitor<String> { (2)
// ...
}
// Turn the list of Violations into a list of custom descriptions
final Violation.Visitor<String> visitor = new MyViolationVisitor();
final List<String> result = violations.stream()
.map( violation -> violation.accept( visitor ) )
.toList();
1 | To format the validation result into an human-readable form, use the ViolationFormatter or the
DetailedViolationFormatter . Note that those are only intended for text-based interfaces such as
CLIs. |
2 | Every application dealing with validation results that needs to transform the results into some
different structure should instead implement the Violation.Visitor Interface with a suitable target
type (String used as an example here) and use the visitor to handle the different types of
Violations in a type-safe way. |
To include the model validator, use the following dependencies:
- Maven
-
<dependency> <groupId>org.eclipse.esmf</groupId> <artifactId>esmf-aspect-model-validator</artifactId> <version>2.7.0</version> </dependency>
- Gradle Groovy DSL
-
implementation 'org.eclipse.esmf:esmf-aspect-model-validator:2.7.0'
- Gradle Kotlin DSL
-
implementation("org.eclipse.esmf:esmf-aspect-model-validator:2.7.0")
Generating Documentation for Aspect Models
Different types of artifacts can be generated from an Aspect model. All corresponding generators are included in the following dependency:
- Maven
-
<dependency> <groupId>org.eclipse.esmf</groupId> <artifactId>esmf-aspect-model-document-generators</artifactId> <version>2.7.0</version> </dependency>
- Gradle Groovy DSL
-
implementation 'org.eclipse.esmf:esmf-aspect-model-document-generators:2.7.0'
- Gradle Kotlin DSL
-
implementation("org.eclipse.esmf:esmf-aspect-model-document-generators:2.7.0")
The documentation generation APIs provide methods that take as an argument a Function<String, java.io.OutputStream>
.
This is a mapping function that takes a file name as an input (which is determined by the respective generator) and
returns a corresponding OutputStream
, for example (but not necessarily) a FileOutputStream
. By providing this
function when calling the generator method, you can control where the output is written to, even when the generator
creates multiple files. For the code examples in the following subsections, we assume that the following method is
defined for calling the generators:
OutputStream outputStreamForName( final String aspectFileName ) {
// Create an OutputStream for the file name, e.g. a FileOutputStream
}
Generating SVG or PNG Diagrams
Using the AspectModelDiagramGenerator
, automatically layouted diagrams can be created for Aspect models in the formats
PNG, SVG and Graphviz/DOT.
Show used imports
import java.io.OutputStream;
import java.util.Locale;
import java.util.Set;
import org.eclipse.esmf.aspectmodel.generator.diagram.AspectModelDiagramGenerator;
import org.eclipse.esmf.aspectmodel.generator.diagram.AspectModelDiagramGenerator.Format;
import org.eclipse.esmf.metamodel.AspectContext;
import org.eclipse.esmf.metamodel.loader.AspectModelLoader;
import java.io.File;
import java.io.IOException;
// AspectContext as returned by the AspectModelLoader
final AspectContext model = // ...
final AspectModelDiagramGenerator generator = new AspectModelDiagramGenerator( model ); (1)
// Variant 1: Generate a diagram in SVG format using @en descriptions and preferredNames from the model
final OutputStream output = outputStreamForName( "diagram.svg" );
generator.generateDiagram( Format.SVG, Locale.ENGLISH, output ); (2)
output.close();
// Variant 2: Generate diagrams in multiple formats, for all languages that are present in the model.
generator.generateDiagrams( Set.of( Format.PNG, Format.SVG ), this::outputStreamForName ); (3)
1 | The diagram generator is initialized with the loaded model. |
2 | The simple call for one output format and one language (i.e., descriptions and preferredNames of one locale) takes one output stream to write the image to. |
3 | It is also possible to generate multiple diagrams, one for each combination of output format and language. For that, the set of target formats is given as well as a mapping function. |
Generating HTML Documentation
A HTML reference documentation for an Aspect model can be generated as shown in the following code sample. The documentation contains an overview diagram and describes the model elements as specified in the Aspect model. Preferred names and descriptions in the respective language from the Aspect model are shown in the resulting document as part of each model element.
Show used imports
import java.nio.file.Paths;
import java.util.Map;
import org.eclipse.esmf.aspectmodel.generator.docu.AspectModelDocumentationGenerator;
import org.eclipse.esmf.aspectmodel.generator.docu.AspectModelDocumentationGenerator.HtmlGenerationOption;
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.aspectmodel.resolver.FileSystemStrategy;
import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy;
import org.eclipse.esmf.aspectmodel.resolver.services.VersionedModel;
import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;
import org.eclipse.esmf.metamodel.Aspect;
import org.eclipse.esmf.metamodel.AspectContext;
import org.eclipse.esmf.metamodel.loader.AspectModelLoader;
import java.io.IOException;
final AspectModelUrn targetAspect
= AspectModelUrn.fromUrn( "urn:samm:org.eclipse.esmf.examples.movement:1.0.0#Movement" );
// VersionedModel as returned by the AspectModelResolver
final VersionedModel model = // ...
final Aspect aspect = AspectModelLoader.getAspects( model ).toJavaStream() (1)
.flatMap( aspects -> aspects.stream() )
.filter( theAspect -> theAspect.getAspectModelUrn().map( urn -> urn.equals( targetAspect ) ).orElse( false ) )
.findFirst().orElseThrow();
final AspectModelDocumentationGenerator generator = (2)
new AspectModelDocumentationGenerator( new AspectContext( model, aspect ) );
final Map<HtmlGenerationOption, String> options = Map.of(); (3)
generator.generate( this::outputStreamForName, options );
1 | Load and select the Aspect from the loaded model for which generation should be generated. |
2 | The HTML generator is initialized with the context consisting of the loaded RDF model and the selected Aspect. |
3 | HTML generation can be controlled via a map of options; using an empty map implies default values. |
Generating Sample JSON Payload
The sample JSON payload generator is used to create a valid JSON payload for an Aspect model as it could be returned by
an Aspect that implements that Aspect model. This follows the
mapping rules as defined in the Meta Model specification.
The generator uses samm:exampleValue
s of Properties if present, otherwise random values corresponding to the
respective data types are generated.
Show used imports
import java.io.File;
import java.io.IOException;
import org.eclipse.esmf.aspectmodel.generator.json.AspectModelJsonPayloadGenerator;
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.metamodel.Aspect;
import org.eclipse.esmf.metamodel.loader.AspectModelLoader;
// Aspect as created by the AspectModelLoader
final Aspect aspect = // ...
final AspectModelJsonPayloadGenerator generator = new AspectModelJsonPayloadGenerator( aspect );
// Variant 1: Direct generation into a String
final String jsonString = generator.generateJson();
// Variant 2: Generate using mapping function
generator.generateJson( this::outputStreamForName );
Generating JSON Schema
The JSON schema generator creates a JSON Schema that describes the payload for an Aspect model as it could be returned by an Aspect that implements that Aspect model.
Show used imports
import java.io.ByteArrayOutputStream;
import java.util.Locale;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.eclipse.esmf.aspectmodel.generator.jsonschema.AspectModelJsonSchemaGenerator;
import org.eclipse.esmf.aspectmodel.generator.jsonschema.JsonSchemaGenerationConfig;
import org.eclipse.esmf.aspectmodel.generator.jsonschema.JsonSchemaGenerationConfigBuilder;
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.metamodel.Aspect;
import org.eclipse.esmf.metamodel.loader.AspectModelLoader;
import java.io.File;
import java.io.IOException;
// Aspect as created by the AspectModelLoader
final Aspect aspect = // ...
final JsonSchemaGenerationConfig config = JsonSchemaGenerationConfigBuilder.builder()
.locale( Locale.ENGLISH )
.build();
final JsonNode jsonSchema = AspectModelJsonSchemaGenerator.INSTANCE.apply( aspect, config ).getContent();
// If needed, print or pretty print it into a string
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final ObjectMapper objectMapper = new ObjectMapper();
objectMapper.writerWithDefaultPrettyPrinter().writeValue( out, jsonSchema );
final String result = out.toString();
Generating OpenAPI Specification
The OpenAPI specification generator creates either a JSON Schema or a Yaml Spec that specifies an Aspect regarding to the OpenApi specification. The currently used versions corresponds Draft 4 of the JSON Schema specification, and 3.0.1.
Show used imports
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import org.eclipse.esmf.aspectmodel.generator.openapi.AspectModelOpenApiGenerator;
import org.eclipse.esmf.aspectmodel.generator.openapi.OpenApiSchemaGenerationConfig;
import org.eclipse.esmf.aspectmodel.generator.openapi.OpenApiSchemaGenerationConfigBuilder;
import org.eclipse.esmf.aspectmodel.generator.openapi.PagingOption;
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.metamodel.Aspect;
import org.eclipse.esmf.metamodel.loader.AspectModelLoader;
import org.junit.jupiter.api.Test;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
public class GenerateOpenApi {
@Test
public void generateYaml() throws JsonProcessingException {
final File modelFile = new File( "aspect-models/org.eclipse.esmf.examples.movement/1.0.0/Movement.ttl" );
// Aspect as created by the AspectModelLoader
final Aspect aspect = // ...
// exclude the actual loading from the example to reduce noise
AspectModelResolver.loadAndResolveModel( modelFile ).flatMap( AspectModelLoader::getSingleAspect ).get();
//language=yaml
final OpenApiSchemaGenerationConfig config = OpenApiSchemaGenerationConfigBuilder.builder()
// Server URL
.baseUrl( "http://www.example.com" )
// The resource path which shall be added
.resourcePath( "/testPath/{parameter}" )
// Determines whether semantic versioning should be used for the API
// i.e., true = v1.2.3, false = v1
.useSemanticVersion( false )
// A String containing all the information for dynamic properties mentioned in the resource path.
// The string must be syntactically valid YAML.
.properties( readYaml( """
parameter:
name: parameter
in: path
description: "A parameter."
required: true
schema:
type: string
""" ) )
// Should the query API be added to the generated specification?
.includeQueryApi( true )
// The paging Option to be chosen. In case paging is possible:
// The default for a time related collection is time-based paging.
// Otherwise the default is offset-based paging.
.pagingOption( PagingOption.OFFSET_BASED_PAGING )
.build();
// Generate pretty-printed YAML
final AspectModelOpenApiGenerator generator = new AspectModelOpenApiGenerator();
final String yaml = generator.apply( aspect, config ).getContentAsYaml();
}
@Test
public void generateJson() throws IOException {
final File modelFile = new File( "aspect-models/org.eclipse.esmf.examples.movement/1.0.0/Movement.ttl" );
// Aspect as created by the AspectModelLoader
final Aspect aspect = // ...
// exclude the actual loading from the example to reduce noise
AspectModelResolver.loadAndResolveModel( modelFile ).flatMap( AspectModelLoader::getSingleAspect ).get();
final ObjectMapper objectMapper = new ObjectMapper();
final OpenApiSchemaGenerationConfig config = OpenApiSchemaGenerationConfigBuilder.builder()
// Server URL
.baseUrl( "http://www.example.com" )
// The resource path which shall be added
.resourcePath( "/testPath/{parameter}" )
// Determines whether semantic versioning should be used for the API
// i.e., true = v1.2.3, false = v1
.useSemanticVersion( false )
// A JsonNode containing all the information for variable properties mentioned
// in the resource path .
.properties( readYaml( """
{
"test-Id": {
"name": "test-Id",
"in": "path",
"description": "The ID of the unit.",
"required": true,
"schema": {
"type": "string"
}
}
}
""" ) )
// Should the query API be added to the generated specification?
.includeQueryApi( true )
// The paging Option to be chosen. In case paging is possible:
// The default for a time related collection is time-based paging.
// Otherwise the default is offset-based paging.
.pagingOption( PagingOption.OFFSET_BASED_PAGING )
.build();
// Generate the JSON
final AspectModelOpenApiGenerator generator = new AspectModelOpenApiGenerator();
final JsonNode json = generator.apply( aspect, config ).getContent();
// If needed, print or pretty print it into a string
final ByteArrayOutputStream out = new ByteArrayOutputStream();
objectMapper.writerWithDefaultPrettyPrinter().writeValue( out, json );
final String result = out.toString();
}
private static ObjectNode readYaml( String content ) throws JsonProcessingException {
return (ObjectNode) new YAMLMapper().readTree( content );
}
}
// Aspect as created by the AspectModelLoader
final Aspect aspect = // ...
//language=yaml
final OpenApiSchemaGenerationConfig config = OpenApiSchemaGenerationConfigBuilder.builder()
// Server URL
.baseUrl( "http://www.example.com" )
// The resource path which shall be added
.resourcePath( "/testPath/{parameter}" )
// Determines whether semantic versioning should be used for the API
// i.e., true = v1.2.3, false = v1
.useSemanticVersion( false )
// A String containing all the information for dynamic properties mentioned in the resource path.
// The string must be syntactically valid YAML.
.properties( readYaml( """
parameter:
name: parameter
in: path
description: "A parameter."
required: true
schema:
type: string
""" ) )
// Should the query API be added to the generated specification?
.includeQueryApi( true )
// The paging Option to be chosen. In case paging is possible:
// The default for a time related collection is time-based paging.
// Otherwise the default is offset-based paging.
.pagingOption( PagingOption.OFFSET_BASED_PAGING )
.build();
// Generate pretty-printed YAML
final AspectModelOpenApiGenerator generator = new AspectModelOpenApiGenerator();
final String yaml = generator.apply( aspect, config ).getContentAsYaml();
// Aspect as created by the AspectModelLoader
final Aspect aspect = // ...
final ObjectMapper objectMapper = new ObjectMapper();
final OpenApiSchemaGenerationConfig config = OpenApiSchemaGenerationConfigBuilder.builder()
// Server URL
.baseUrl( "http://www.example.com" )
// The resource path which shall be added
.resourcePath( "/testPath/{parameter}" )
// Determines whether semantic versioning should be used for the API
// i.e., true = v1.2.3, false = v1
.useSemanticVersion( false )
// A JsonNode containing all the information for variable properties mentioned
// in the resource path .
.properties( readYaml( """
{
"test-Id": {
"name": "test-Id",
"in": "path",
"description": "The ID of the unit.",
"required": true,
"schema": {
"type": "string"
}
}
}
""" ) )
// Should the query API be added to the generated specification?
.includeQueryApi( true )
// The paging Option to be chosen. In case paging is possible:
// The default for a time related collection is time-based paging.
// Otherwise the default is offset-based paging.
.pagingOption( PagingOption.OFFSET_BASED_PAGING )
.build();
// Generate the JSON
final AspectModelOpenApiGenerator generator = new AspectModelOpenApiGenerator();
final JsonNode json = generator.apply( aspect, config ).getContent();
// If needed, print or pretty print it into a string
final ByteArrayOutputStream out = new ByteArrayOutputStream();
objectMapper.writerWithDefaultPrettyPrinter().writeValue( out, json );
final String result = out.toString();
For Enumerations with complex data types, the values are modelled as instances of the Entity defined as the Enumeration’s data type (see Declaring Enumerations for more information). In case the Entity instances contain Properties with a sorted collection as their data type, the order of the values of said collection in the Entity instances is not preserved in the generated OpenAPI specification. Preserving this order in OpenAPI is not possible at this point. |
Generating AsyncAPI Specification
The AsyncAPI specification generator creates either a JSON Schema or a Yaml Spec that specifies an Aspect regarding to the AsyncApi specification. The currently used versions correspond to Draft 4 of the JSON Schema specification, and 3.0.0 of the AsyncAPI specification.
Show used imports
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import org.eclipse.esmf.aspectmodel.generator.asyncapi.AspectModelAsyncApiGenerator;
import org.eclipse.esmf.aspectmodel.generator.asyncapi.AsyncApiSchemaGenerationConfig;
import org.eclipse.esmf.aspectmodel.generator.asyncapi.AsyncApiSchemaGenerationConfigBuilder;
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.metamodel.Aspect;
import org.eclipse.esmf.metamodel.loader.AspectModelLoader;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator;
import com.fasterxml.jackson.dataformat.yaml.YAMLMapper;
// Aspect as created by the AspectModelLoader
final Aspect aspect = // ...
final ObjectMapper yamlMapper = new YAMLMapper().enable( YAMLGenerator.Feature.MINIMIZE_QUOTES );
final AsyncApiSchemaGenerationConfig config = AsyncApiSchemaGenerationConfigBuilder.builder()
// i.e., true = v1.2.3, false = v1
.useSemanticVersion( false )
// Set Application id
.applicationId( "test:serve" )
.channelAddress( "/123-456/789-012/movement/0.0.1/Movement" )
.build();
// Generate pretty-printed YAML
final AspectModelAsyncApiGenerator generator = new AspectModelAsyncApiGenerator();
final JsonNode json = generator.apply( aspect, config ).getContent();
final String yaml = yamlMapper.writeValueAsString( json );
final ByteArrayOutputStream out = new ByteArrayOutputStream();
out.write( yaml.getBytes( StandardCharsets.UTF_8 ) );
final String result = out.toString();
// Aspect as created by the AspectModelLoader
final Aspect aspect = // ...
final ObjectMapper objectMapper = new ObjectMapper();
final AsyncApiSchemaGenerationConfig config = AsyncApiSchemaGenerationConfigBuilder.builder()
// i.e., true = v1.2.3, false = v1
.useSemanticVersion( false )
// Set Application id
.applicationId( "test:serve" )
.channelAddress( "/123-456/789-012/movement/0.0.1/Movement" )
.build();
// Generate the JSON
final AspectModelAsyncApiGenerator generator = new AspectModelAsyncApiGenerator();
final JsonNode json = generator.apply( aspect, config ).getContent();
// If needed, print or pretty print it into a string
final ByteArrayOutputStream out = new ByteArrayOutputStream();
objectMapper.writerWithDefaultPrettyPrinter().writeValue( out, json );
final String result = out.toString();
Generating Java Code for Aspect Models
Java code can be generated from an Aspect model in two ways:
-
The generated code represents the Aspect payload. Aspects and Entities become Java classes; their Properties become fields in the classes. Characteristics are not first-class elements, but are implicitly represented by the usage of corresponding data types (e.g. using
java.util.Set
as the type for theSet
Characteristic of a Property) orjavax.validation
annotations. The generated classes can be used in a straightforward fashion, but they do not contain information about the underlying Aspect model such as its version number. Parts of the Aspect model that have no representation in its corresponding JSON payload are not part of those classes either, in particular descriptions and preferred names. These classes are called POJOs (Plain Old Java Objects), as they do not contain logic but serve mainly as data containers. -
The generated code represents the Aspect model itself: It is a type-safe variant of the model and includes every information that is also present in the model such as Characteristics, descriptions including language tags and original XSD data types. It is however not intended to store payload corresponding to an Aspect. Theses classes are called static meta classes, because they are created at compile time (static) and talk about the structure of the information, not the information itself (meta).
Depending on the use case, you would either use one or both of the types simultaneously.
To include the Java generator, use the following dependencies:
- Maven
-
<dependency> <groupId>org.eclipse.esmf</groupId> <artifactId>esmf-aspect-model-java-generator</artifactId> <version>2.7.0</version> </dependency>
- Gradle Groovy DSL
-
implementation 'org.eclipse.esmf:esmf-aspect-model-java-generator:2.7.0'
- Gradle Kotlin DSL
-
implementation("org.eclipse.esmf:esmf-aspect-model-java-generator:2.7.0")
Type Mapping
In the Java code generated from an Aspect model, the scalar Aspect model data types are mapped to native Java types. The following table lists the correspondences.
Aspect model type | Java native type |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Generating POJOs
POJO generation is straightforward; there are two minor differences to the generation of documentation artifacts.
Firstly, when instantiating the generator, you pass a flag indicating whether
Jackson annotations should be generated in the class. Secondly, the name
mapping function passed to the generation method takes a QualifiedName
instead of a String, so that you can decide how
to handle the package structure of the class.
Show used imports
import org.eclipse.esmf.aspectmodel.java.JavaCodeGenerationConfig;
import org.eclipse.esmf.aspectmodel.java.JavaCodeGenerationConfigBuilder;
import org.eclipse.esmf.aspectmodel.java.pojo.AspectModelJavaGenerator;
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.metamodel.Aspect;
import org.eclipse.esmf.metamodel.loader.AspectModelLoader;
import java.io.File;
import org.apache.commons.io.output.NullOutputStream;
// Aspect as created by the AspectModelLoader
final Aspect aspect = // ...
final JavaCodeGenerationConfig config = JavaCodeGenerationConfigBuilder.builder()
.enableJacksonAnnotations( true )
.packageName( "com.example.mycompany" ) // if left out, package is taken from Aspect's namespace
.build();
final AspectModelJavaGenerator generator = new AspectModelJavaGenerator( aspect, config );
generator.generate( qualifiedName -> {
// Create an output stream for the given qualified Java class name
} );
Generating Static Meta Classes
For the generation of static meta classes, consider the following example:
Show used imports
import org.eclipse.esmf.aspectmodel.java.JavaCodeGenerationConfig;
import org.eclipse.esmf.aspectmodel.java.JavaCodeGenerationConfigBuilder;
import org.eclipse.esmf.aspectmodel.java.metamodel.StaticMetaModelJavaGenerator;
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.metamodel.Aspect;
import org.eclipse.esmf.metamodel.loader.AspectModelLoader;
import java.io.File;
import org.apache.commons.io.output.NullOutputStream;
// Aspect as created by the AspectModelLoader
final Aspect aspect = // ...
final JavaCodeGenerationConfig config = JavaCodeGenerationConfigBuilder.builder()
.enableJacksonAnnotations( true )
.packageName( "com.example.mycompany" ) // if left out, package is taken from Aspect's namespace
.build();
final StaticMetaModelJavaGenerator generator = new StaticMetaModelJavaGenerator( aspect, config );
generator.generate( qualifiedName -> {
// Create an output stream for the given qualified Java class name
} );
To use the generated static meta classes, you need the following additional dependency:
- Maven
-
<dependency> <groupId>org.eclipse.esmf</groupId> <artifactId>esmf-aspect-static-meta-model-java</artifactId> <version>2.7.0</version> </dependency>
- Gradle Groovy DSL
-
implementation 'org.eclipse.esmf:esmf-aspect-static-meta-model-java:2.7.0'
- Gradle Kotlin DSL
-
implementation("org.eclipse.esmf:esmf-aspect-static-meta-model-java:2.7.0")
Providing Custom Macros for Code Generation
It is possible to change predefined sections of the generated classes by providing custom Velocity templates; see the Velocity User Guide for more information. The custom macros must be defined in a single template file. The path to the template file as well as its name may be passed as arguments to the code generation, e.g. using the SAMM-CLI.
Custom macros may be provided for the following sections:
Section | Macro Name | Default Macro Provided |
---|---|---|
Copyright Licence Header |
fileHeader |
When using custom macros, macros for all sections above must be provided. |
Example:
#macro( fileHeader )
/*
* Copyright (c) $currentYear.getValue() Test Inc. All rights reserved.
*/
#end
Programmatically migrate the Meta Model Version
The meta model version migrator provides functionality to automatically migrate an Aspect model that
uses an older version of the Aspect Meta Model to an Aspect model that uses a newer (usually the
latest) version of the meta model. Calling this functionality is only required if you intent to
work with the raw RDF model (i.e., the VersionedModel
). If you use the AspectModelLoader
to load
a model into ModelElement
s or Aspect
s, calling the MigratorService
is not
necessary, as it will be done automatically if necessary.
The following sample shows usage of the migration:
Show used imports
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.aspectmodel.resolver.FileSystemStrategy;
import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy;
import org.eclipse.esmf.aspectmodel.resolver.services.VersionedModel;
import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;
import org.eclipse.esmf.aspectmodel.versionupdate.MigratorService;
import io.vavr.control.Try;
import java.nio.file.Paths;
// Try<VersionedModel> as returned by the AspectModelResolver
final Try<VersionedModel> tryModel = // ...
final Try<VersionedModel> tryMigratedModel = tryModel.flatMap( versionedModel ->
new MigratorService().updateMetaModelVersion( versionedModel ) );
final VersionedModel migratedModel = tryMigratedModel.getOrElseThrow( () ->
new RuntimeException( tryMigratedModel.getCause() ) );
To use the meta model version migrator, you need the following dependency:
- Maven
-
<dependency> <groupId>org.eclipse.esmf</groupId> <artifactId>esmf-aspect-meta-model-version-migrator</artifactId> <version>2.7.0</version> </dependency>
- Gradle Groovy DSL
-
implementation 'org.eclipse.esmf:esmf-aspect-meta-model-version-migrator:2.7.0'
- Gradle Kotlin DSL
-
implementation("org.eclipse.esmf:esmf-aspect-meta-model-version-migrator:2.7.0")
Accessing the SAMM programmatically
In order to access the source RDF files that describe the SAMM vocabulary, shared Characteristics and Entities as well
as Units, you can add a dependency to the esmf-aspect-meta-model
artifact. Note that this artifact does not contain any
Java classes.
- Maven
-
<dependency> <groupId>org.eclipse.esmf</groupId> <artifactId>esmf-semantic-aspect-meta-model</artifactId> <version>2.1.0</version> </dependency>
- Gradle Groovy DSL
-
implementation 'org.eclipse.esmf:esmf-semantic-aspect-meta-model:2.1.0'
- Gradle Kotlin DSL
-
implementation("org.eclipse.esmf:esmf-semantic-aspect-meta-model:2.1.0")
In order to access the files via java.lang.Class#getResourceAsStream
, you can refer to the following directory
structure that is present in the artifact:
.
├── characteristic
│ └── 2.1.0
│ ├── characteristic-definitions.ttl
│ ├── characteristic-instances.ttl
│ ├── characteristic-shapes.ttl
│ └── characteristic-validations.js
├── entity
│ └── 2.1.0
│ ├── FileResource.ttl
│ ├── ThreeDimensionalPosition.ttl
│ └── TimeSeriesEntity.ttl
├── meta-model
│ └── 2.1.0
│ ├── aspect-meta-model-definitions.ttl
│ ├── aspect-meta-model-shapes.ttl
│ ├── prefix-declarations.ttl
│ └── type-conversions.ttl
└── unit
└── 2.1.0
└── units.ttl
If you use the artifact aspect-model-resolver instead (see section Loading and Saving Aspect Models for infos on the
dependency) you can directly refer to the RDF files using a resource URL:
|
Show used imports
import static org.eclipse.esmf.aspectmodel.resolver.services.TurtleLoader.loadTurtle;
import static org.eclipse.esmf.aspectmodel.resolver.services.TurtleLoader.openUrl;
import java.io.InputStream;
import java.net.URL;
import java.util.Optional;
import org.apache.jena.vocabulary.RDF;
import org.eclipse.esmf.samm.KnownVersion;
import org.eclipse.esmf.aspectmodel.resolver.services.MetaModelUrls;
import org.eclipse.esmf.aspectmodel.vocabulary.SAMM;
final KnownVersion metaModelVersion = KnownVersion.getLatest();
final Optional<URL> characteristicDefinitionsUrl =
MetaModelUrls.url( "characteristic", metaModelVersion, "characteristic-instances.ttl" );
characteristicDefinitionsUrl.ifPresent( url -> {
final InputStream inputStream = openUrl( url );
loadTurtle( inputStream ).forEach( model -> {
// Do something with the org.apache.jena.org.rdf.model.Model
final SAMM samm = new SAMM( metaModelVersion );
final int numberOfCharacteristicInstances =
model.listStatements( null, RDF.type, samm.Characteristic() ).toList().size();
final String result = String.format( "Meta Model Version " + metaModelVersion.toVersionString()
+ " defines " + numberOfCharacteristicInstances + " Characteristic instances" );
} );
} );
Mapping Aspect Models to Asset Administration Shell (AAS) Submodel Templates
The Asset Administration Shell (AAS) and its information model is a widely recognized standard developed by the Industrial Digital Twin Association (IDTA) to express and handle Digital Twins. Central element of the AAS is the concept of Submodels, which describe certain aspects of a Digital Twin.
The SAMM Aspect Meta Model allows to specify aspects of a digital twin and its semantics. The AAS Generator module provides mapping implementations to derive AAS Submodels from SAMM Aspect Models. On the one hand, this allows to integrate SAMM models in AAS environments and on the other hand it allows AAS submodels to be described with rich semantics, as it is possible with SAMM.
Details of the Mapping Concept
In the following section, the mapping rules applied by the generator are explained. The rules apply to SAMM v2.0.0 and AAS Specification Part 1 V3.0RC01.
SAMM | AAS | Comment |
---|---|---|
samm:Aspect |
aas:Submodel with kind=Template |
Empty Asset and AssetAdministrationShell entries are added to the output file |
samm:name |
aas:Submodel.idShort |
|
samm:preferredName |
aas:Submodel.displayName |
|
samm:description |
aas:Submodel.description |
|
samm:property |
see samm:Property |
|
samm:operation |
see samm:Operation |
|
samm:Aspect.urn |
aas:Submodel.semanticId |
|
samm:Property |
aas:Property, aas:SubmodelElementCollection |
The AAS type is derived from the type of the SAMM Characteristic specifying the SAMM property. Depending on the type it is decided what the resulting AAS element will be. In case of an Entity it will result in a SubmodelElementCollection. It will also be a SubmodelElementCollection if the SAMM Characteristic is of a Collection type (see the Characteristics taxonomy). In all other cases an aas:Property will be generated |
samm:Property.name |
aas:Property.idShort |
|
samm:Property.urn |
aas:ConceptDescription.identification.id, aas:Property.semanticId |
|
samm:Property.preferredName |
aas:Property.displayName |
|
samm:Property.description |
aas:Property.description |
Note: Also mapped to aas:DataSpecificationIEC61360.definition of the aas:ConceptDescription of this property |
samm:Property.exampleValue |
aas:Property.value |
|
samm:Characteristic.dataType |
aas:Property.valueType |
|
samm:Operation |
Operation |
in/out parameters are not used in SAMM so the mapping only generates input variables and output variables in AAS |
aas:SubmodelElement, aas:ConceptDescription |
Characteristics in SAMM define the semantics of a property, which includes there types as well as links to further definitions (standards, dictionaries, etc), a natural language description and name in different languages. Type and description are separated in AAS, which is why there is not a one-to-one mapping of a Characteristic to one element in AAS but rather Characteristics are used in the mapping of Properties, first, to guide the generation process and, second, to capture semantics in ConceptDescriptions of properties with data specification "DataSpecificationIEC61360" of the AAS. |
|
aas:SubmodelElementList, aas:ConceptDescription |
The general remarks to Characteristics apply also to Collection type Characteristics. However, properties referencing Collections are mapped to SubmodelElementLists. Specific properties of collections are mapped. samm:Set is unique, samm:SortedSet is unique and sorted, samm:List is sorted. |
|
aas:SubmodelElement, aas:ConceptDescription |
The general remarks to Characteristics apply also to Quantifiable type Characteristics. Quantifiables (also Duration and Measurement) reference a unit, which is added to the ConceptDescription corresponding the mapped Characteristic. |
|
aas:SubmodelElement, aas:ConceptDescription |
The general remarks to Characteristics apply also to Either. However, the Either characteristic has two distinct entries of which one is to be selected. This concept is not present in AAS. Thus, both entries will be written to a Submodel template, where one has to be ignored. |
|
aas:SubmodelElement, aas:ConceptDescription |
The general remarks to Characteristics apply also to Trait. However, the constraint of a trait will be ignored and only the base type will be evaluated, which will act as the characteristic of a property. |
|
aas:SubmodelElement, aas:ConceptDescription |
Similar to plain Characteristic. |
|
aas:SubmodelElement, aas:ConceptDescription |
The general remarks to Characteristics apply also to StructuredValue. However, AAS has no concpet like deconstruction rule. Thus, the deconstruction rule and the sub properties of the deconstruction entity will be ignored and only the Characteristic is mapped. |
|
aas:SubmodelElement, aas:ConceptDescription |
The general remarks to Characteristics apply also to Enumerations. Additionally, the values of an Enumeration are mapped to a valueList of a DataSpecificationIEC61360. |
|
aas:SubmodelElement, aas:ConceptDescription |
Same as Enumeration. |
|
aas:MultiLanguageProperty |
if a MultiLanguageText is used in SAMM it is mapped to the MultiLanguageProperty in AAS. |
Known Limitations
The AAS Generator implements a base set of features, which are mapped from SAMM to AAS. However, there are still limitations:
-
Predefined entity mapping (FileResource would be mapped to aas:File)
-
samm-c:Either is mapped to aas:SubmodelElementCollection with two entries for left and right side
-
Recursive optional properties of SAMM model are not included in output but dropped straight away
Translate Aspect Model to AAS
The following code demonstrates how the API to translate an Aspect Model into one of the valid AAS formats (JSON, XML, AASX) is used:
Show used imports
import java.io.File;
import java.io.IOException;
import org.eclipse.esmf.aspectmodel.aas.AspectModelAasGenerator;
import org.eclipse.esmf.aspectmodel.aas.AasFileFormat;
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.metamodel.Aspect;
import org.eclipse.esmf.metamodel.loader.AspectModelLoader;
// Aspect as created by the AspectModelLoader
final Aspect aspect = // ...
final AspectModelAasGenerator generator = new AspectModelAasGenerator();
// Generate AAS .aasx for input Aspect
generator.generate( AasFileFormat.AASX, aspect, this::outputStreamForName );
// Generate AAS .xml for input Aspect
generator.generate( AasFileFormat.XML, aspect, this::outputStreamForName );
// Generate AAS .json for input Aspect
generator.generate( AasFileFormat.JSON, aspect, this::outputStreamForName );
To include the translator artifact, use the following dependencies:
- Maven
-
<dependency> <groupId>org.eclipse.esmf</groupId> <artifactId>esmf-aspect-aas-generator</artifactId> <version>2.7.0</version> </dependency>
- Gradle Groovy DSL
-
implementation 'org.eclipse.esmf:esmf-aspect-model-aas-generator:2.7.0'
- Gradle Kotlin DSL
-
implementation("org.eclipse.esmf:esmf-aspect-model-aas-generator:2.7.0")
Translate AAS to Aspect Model
It is also possible to translate AAS Submodel Templates to Aspect Models using a best-effort. The following example shows how the API to translate an AAS environment containing one or more Submodel Templates to Aspect Models:
Show used imports
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.eclipse.esmf.aspectmodel.aas.AasFileFormat;
import org.eclipse.esmf.aspectmodel.aas.AasToAspectModelGenerator;
import org.eclipse.esmf.aspectmodel.aas.AspectModelAasGenerator;
import org.eclipse.esmf.aspectmodel.resolver.AspectModelResolver;
import org.eclipse.esmf.metamodel.Aspect;
import org.eclipse.esmf.metamodel.loader.AspectModelLoader;
import org.apache.commons.io.FileUtils;
import org.junit.jupiter.api.Test;
public class GenerateAspectFromAas extends AbstractGenerator {
private OutputStream outputStream( final Path directory, final String aspectName ) {
try {
return new FileOutputStream( directory.resolve( aspectName + ".aasx" ).toFile() );
} catch ( final FileNotFoundException exception ) {
throw new RuntimeException( exception );
}
}
@Test
public void generate() throws IOException {
final Path outputDirectory = Files.createTempDirectory( "junit" );
new AspectModelAasGenerator().generate( AasFileFormat.AASX,
AspectModelResolver.loadAndResolveModel( new File( "aspect-models/org.eclipse.esmf.examples.movement/1.0.0/Movement.ttl" ) )
.flatMap( AspectModelLoader::getSingleAspect ).get(),
name -> outputStream( outputDirectory, name ) );
final File file = // an AAS file that ends in .aasx, .xml or .json
outputDirectory.resolve( "Movement.aasx" ).toFile();
// Multiple "from" methods are available: fromFile (which checks the file extension),
// fromAasJson, fromAasXml, fromAasx as well as fromEnvironment (for an AAS4J AAS environment)
final AasToAspectModelGenerator generator = AasToAspectModelGenerator.fromFile( file );
final List<Aspect> aspects = generator.generateAspects();
aspects.forEach( aspect -> {
// do something with the generated aspect
} );
FileUtils.deleteDirectory( outputDirectory.toFile() );
}
}
final File file = // an AAS file that ends in .aasx, .xml or .json
// Multiple "from" methods are available: fromFile (which checks the file extension),
// fromAasJson, fromAasXml, fromAasx as well as fromEnvironment (for an AAS4J AAS environment)
final AasToAspectModelGenerator generator = AasToAspectModelGenerator.fromFile( file );
final List<Aspect> aspects = generator.generateAspects();
aspects.forEach( aspect -> {
// do something with the generated aspect
} );
To include the translator artifact, use the same same dependencies as shown above in section Translate AAS to Aspect Model.