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>io.openmanufacturing</groupId>
    <artifactId>sds-aspect-model-starter</artifactId>
    <version>2.1.0</version>
</dependency>
Gradle Groovy DSL
implementation 'io.openmanufacturing:sds-aspect-model-starter:2.1.0'
Gradle Kotlin DSL
implementation("io.openmanufacturing:sds-aspect-model-starter:2.1.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. Compared with throwing exceptions, using a Try<T> as a return type instead has the advantage of enabling composable and better readable code. Please see the Vavr User Guide for more information.

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>OpenManufacturingPlatform SDS-SDK</name>
	 <url>https://maven.pkg.github.com/OpenManufacturingPlatform/sds-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 sds-sdk components are built with Java 17 and require a JDK >= 17 such as Adoptium Temurin.

The sds-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 sds-sdk in the GraalVM itself, install the JS component using the GraalVM Updater: gu install js

  • For using the sds-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.

import io.openmanufacturing.sds.aspectmodel.urn.AspectModelUrn;

final AspectModelUrn urn = AspectModelUrn.fromUrn( "urn:bamm:com.example:1.0.0#Example" );

System.out.println( urn.getNamespace() ); // com.example
System.out.println( urn.getName() );      // Example
System.out.println( urn.getVersion() );   // 1.0.0
System.out.println( urn.getUrnPrefix() ); // urn:bamm:com.example:1.0.0#

To include the artifact, use the following dependencies:

Maven
<dependency>
    <groupId>io.openmanufacturing</groupId>
    <artifactId>sds-aspect-model-urn</artifactId>
    <version>2.1.0</version>
</dependency>
Gradle Groovy DSL
implementation 'io.openmanufacturing:sds-aspect-model-urn:2.1.0'
Gradle Kotlin DSL
implementation("io.openmanufacturing:sds-aspect-model-urn:2.1.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 BAMM 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
  • Low level, focus on power and flexibility

  • Flexibly navigate and filter the model on the RDF statements level

  • Work with models that are valid RDF, but incomplete Aspect models, e.g. in Aspect model editors

  • Use SPARQL [sparql] to execute complex queries on Aspect models

  • High level, focus on convenience and type-safety

  • Use Aspect-model specific interfaces for type-safe code

  • Use Java-native data types (e.g. java.math.BigInteger for xsd:integer)

  • Easily traverse the model on the abstraction level of Aspect model elements

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 bamm:), and can refer to namespaces of shared elements (bamm-c, bamm-e and unit)

  • 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 a java.nio.file.Path for a file relative to the models directory

  • The UrnFileSystemStrategy resolves a AspectModelUrn to a file in the models directory

  • The InMemoryStrategy resolves a model against an already loaded Apache Jena RDF model

  • The SequenceStrategy can be used to chain two different strategies of the same type

  • 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

Consider the following example:

import java.nio.file.Path;
import java.nio.file.Paths;

import org.apache.jena.rdf.model.Model;
import org.apache.jena.rdf.model.Property;
import org.apache.jena.rdf.model.RDFList;
import org.apache.jena.rdf.model.RDFNode;
import org.apache.jena.rdf.model.Resource;
import org.apache.jena.rdf.model.ResourceFactory;
import org.apache.jena.vocabulary.RDF;

import io.openmanufacturing.sds.aspectmodel.resolver.AspectModelResolver;
import io.openmanufacturing.sds.aspectmodel.resolver.FileSystemStrategy;
import io.openmanufacturing.sds.aspectmodel.resolver.ResolutionStrategy;
import io.openmanufacturing.sds.aspectmetamodel.KnownVersion;
import io.openmanufacturing.sds.aspectmodel.urn.AspectModelUrn;

import io.vavr.control.Either;

final Path modelsRoot = Paths.get( "/path/to/models-root" );
final ResolutionStrategy fileSystemStrategy = new FileSystemStrategy( modelsRoot ); (1)

new AspectModelResolver()
      .resolveAspectModel( fileSystemStrategy,
            AspectModelUrn.fromUrn( "urn:bamm:io.openmanufacturing.test:1.0.0#Example" ) )
      .forEach( versionedModel -> {
         final KnownVersion metaModelVersion = versionedModel.getVersion();
         final Model model = versionedModel.getModel();

         // Do something with the RDF model:
         // List the names of the Properties of the Aspect in the model
         final String bammPrefix = String.format( "urn:bamm:io.openmanufacturing:meta-model:%s#",
               metaModelVersion.toVersionString() );
         final Resource aspectClassResource = ResourceFactory.createResource( bammPrefix + "Aspect" );
         final Property propertiesResource = ResourceFactory.createProperty( bammPrefix + "properties" );
         final Resource aspectResource = model (2)
               .listStatements( null, RDF.type, aspectClassResource )
               .nextStatement()
               .getSubject();
         model.listStatements( aspectResource, propertiesResource, (RDFNode) null ) (3)
              .nextStatement()
              .getObject()
              .as( RDFList.class )
              .asJavaList()
              .stream()
              .map( RDFNode::asResource )
              .map( Resource::getLocalName )
              .forEach( System.out::println );
      } );
1 The example uses the FileSystemStrategy to refer to an Aspect model file in a local file system, following the models directory structure.
2 The RDF resource for the Aspect is retrieved from the RDF model, and
3 used to print the Aspect’s Properties.

To include the model resolver artifact, use the following dependencies:

Maven
<dependency>
    <groupId>io.openmanufacturing</groupId>
    <artifactId>sds-aspect-model-resolver</artifactId>
    <version>2.1.0</version>
</dependency>
Gradle Groovy DSL
implementation 'io.openmanufacturing:sds-aspect-model-resolver:2.1.0'
Gradle Kotlin DSL
implementation("io.openmanufacturing:sds-aspect-model-resolver:2.1.0")

Loading an Aspect Model to work on the Java Aspect Model Level

Consider the following example:

import java.nio.file.Path;
import java.nio.file.Paths;

import io.openmanufacturing.sds.aspectmodel.resolver.AspectModelResolver;
import io.openmanufacturing.sds.aspectmodel.resolver.FileSystemStrategy;
import io.openmanufacturing.sds.aspectmodel.resolver.ResolutionStrategy;
import io.openmanufacturing.sds.aspectmodel.urn.AspectModelUrn;
import io.openmanufacturing.sds.metamodel.IsDescribed;
import io.openmanufacturing.sds.metamodel.loader.AspectModelLoader;

final Path modelsRoot = Paths.get( "/path/to/models-root" );
final ResolutionStrategy fileSystemStrategy = new FileSystemStrategy( modelsRoot ); (1)

new AspectModelResolver()
      .resolveAspectModel( fileSystemStrategy,
            AspectModelUrn.fromUrn( "urn:bamm:io.openmanufacturing.test:1.0.0#Example" ) )
      .flatMap( AspectModelLoader::fromVersionedModel ) (2)
      .forEach( aspect -> (3)
            aspect.getProperties().stream()
                  .map( IsDescribed::getName )
                  .forEach( System.out::println ) );
1 The example uses the FileSystemStrategy to refer to an Aspect model file in a local file system, following the models directory structure.
2 The Aspect Loader turns the RDF model into the Java Aspect model representation.
3 The Java model is used to print the Aspect’s Properties.

To include the Java Aspect model artifact, use the following dependencies:

Maven
<dependency>
    <groupId>io.openmanufacturing</groupId>
    <artifactId>sds-aspect-meta-model-java</artifactId>
    <version>2.1.0</version>
</dependency>
Gradle Groovy DSL
implementation 'io.openmanufacturing:sds-aspect-meta-model-java:2.1.0'
Gradle Kotlin DSL
implementation("io.openmanufacturing:sds-aspect-meta-model-java:2.1.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 BAMM specification.

import java.io.PrintWriter;

import org.apache.jena.rdf.model.Model;

import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;
import io.openmanufacturing.sds.aspectmodel.serializer.PrettyPrinter;
import io.openmanufacturing.sds.aspectmodel.serializer.RdfModelCreatorVisitor;
import io.openmanufacturing.sds.aspectmodel.urn.AspectModelUrn;
import io.openmanufacturing.sds.metamodel.Aspect;
import io.openmanufacturing.sds.metamodel.vocabulary.Namespace;

// Aspect as created by the AspectModelLoader
final Aspect aspect = ...

// First step: Turn Java Aspect model into a Jena RDF model
final Namespace aspectNamespace = () -> aspect.getAspectModelUrn().toString();
final RdfModelCreatorVisitor rdfCreator = new RdfModelCreatorVisitor(
      aspect.getMetaModelVersion(), aspectNamespace );
final Model model = rdfCreator.visit( aspect );

// At this point, you can manipulate the RDF model, if required

// Second step: Serialize RDF model into nicely formatted Turtle
final PrintWriter printWriter = new PrintWriter( System.out );
final PrettyPrinter prettyPrinter = new PrettyPrinter( new VersionedModel( model, aspect.getMetaModelVersion() ),
      aspectNamespace, printWriter );
prettyPrinter.print();

To include the serializer artifact, use the following dependencies:

Maven
<dependency>
    <groupId>io.openmanufacturing</groupId>
    <artifactId>sds-aspect-model-serializer</artifactId>
    <version>2.1.0</version>
</dependency>
Gradle Groovy DSL
implementation 'io.openmanufacturing:sds-aspect-model-serializer:2.1.0'
Gradle Kotlin DSL
implementation("io.openmanufacturing:sds-aspect-model-serializer:2.1.0")

Validating Aspect Models

Consider the following example:

import java.util.stream.Collectors;

import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;
import io.openmanufacturing.sds.aspectmodel.validation.report.ValidationError;
import io.openmanufacturing.sds.aspectmodel.validation.report.ValidationReport;
import io.openmanufacturing.sds.aspectmodel.validation.services.AspectModelValidator;

import io.vavr.control.Try;

// Versioned model as returned by the AspectModelResolver
final Try<VersionedModel> versionedModel = ...;

final AspectModelValidator validator = new AspectModelValidator(); (1)
final ValidationReport validationReport = validator.validate( versionedModel );

if ( validationReport.conforms() ) { (2)
   System.out.println( "The model is valid" );
} else {
   // The Validation Report indicates that the validation has failed.

   // Variant 1: Call .toString() on the validationReport
   System.out.println( validationReport.toString() ); (3)

   // Variant 2: Custom processing of different types of validation errors
   final String customErrorMessage = validationReport.getValidationErrors().stream().<String> map(
         validationError -> validationError.accept( new ValidationError.Visitor<>() { (4)
            @Override
            public String visit( final ValidationError.Syntactic error ) {
               // Separately access line and column numbers of the syntax error
               return "...";
            }

            @Override
            public String visit( final ValidationError.Semantic error ) {
               // Separately access focus node, result path, validator message and value
               // of the error
               return "...";
            }

            @Override
            public String visit( final ValidationError.Processing error ) {
               // Access the processing error message and stack trace of the error
               return "...";
            }
         } ) ).collect( Collectors.joining( ", " ) );

   System.out.println( customErrorMessage );
}
1 An Aspect model validator is created and its validation is called on the Aspect model.
2 The .conforms() method can be used to checked if the model is valid or not.
3 To retrieve a preformatted error message in case the Aspect model is invalid, the validation report can be turned into a String.
4 If more flexibility is required, e.g. for creating error messages in different languages or formats such as JSON, the validation error can also accept an implementation of a ValidationError.Visitor, that distinguishes between the different types of validation failures: syntax errors, semantic errors (i.e., the input is valid RDF but violates the Aspect model contract) and processing errors (e.g., a file could not be accessed).

To include the model validator, use the following dependencies:

Maven
<dependency>
    <groupId>io.openmanufacturing</groupId>
    <artifactId>sds-aspect-model-validator</artifactId>
    <version>2.1.0</version>
</dependency>
Gradle Groovy DSL
implementation 'io.openmanufacturing:sds-aspect-model-validator:2.1.0'
Gradle Kotlin DSL
implementation("io.openmanufacturing:sds-aspect-model-validator:2.1.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>io.openmanufacturing</groupId>
    <artifactId>sds-aspect-model-document-generators</artifactId>
    <version>2.1.0</version>
</dependency>
Gradle Groovy DSL
implementation 'io.openmanufacturing:sds-aspect-model-document-generators:2.1.0'
Gradle Kotlin DSL
implementation("io.openmanufacturing:sds-aspect-model-document-generators:2.1.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.

import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Locale;
import java.util.Set;

import io.openmanufacturing.sds.aspectmodel.diagramgenerator.AspectModelDiagramGenerator;
import io.openmanufacturing.sds.aspectmodel.diagramgenerator.AspectModelDiagramGenerator.Format;
import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;

// Versioned model as returned by the AspectModelResolver
final VersionedModel versionedModel = ...

final AspectModelDiagramGenerator generator = new AspectModelDiagramGenerator( versionedModel ); (1)

// Variant 1: Generate a diagram in SVG format using @en descriptions and preferredNames from the model
final OutputStream output = new FileOutputStream( "..." );
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.

import java.io.OutputStream;

import io.openmanufacturing.sds.aspectmodel.asciidoc.AspectModelDocumentationGenerator;
import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;

// Versioned model as returned by the AspectModelResolver
final VersionedModel versionedModel = ...

final AspectModelDocumentationGenerator generator = new AspectModelDocumentationGenerator( versionedModel );
generator.generateHtml( this::outputStreamForName );

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 bamm:exampleValue​s of Properties if present, otherwise random values corresponding to the respective data types are generated.

import io.openmanufacturing.sds.aspectmodel.generator.json.AspectModelJsonPayloadGenerator;

// Versioned model as returned by the AspectModelResolver
final VersionedModel versionedModel = ...

final AspectModelJsonPayloadGenerator generator = new AspectModelJsonPayloadGenerator( versionedModel );

// 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. Currently, the generated schema corresponds to

import java.io.ByteArrayOutputStream;
import io.openmanufacturing.sds.aspectmodel.generator.jsonschema.AspectModelJsonSchemaGenerator;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

// Aspect as created by the AspectModelLoader
final Aspect aspect = ...

// Generate the JSON Schema
final AspectModelJsonSchemaGenerator generator = new AspectModelJsonSchemaGenerator();
final JsonNode jsonSchema = generator.apply( aspect, Locale.ENGLISH );

// 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.

import java.util.Optional;
import java.io.ByteArrayOutputStream;
import io.openmanufacturing.sds.aspectmodel.generator.openapi.AspectModelOpenApiGenerator;
import io.openmanufacturing.sds.aspectmodel.generator.openapi.PagingOption;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

// Aspect as created by the AspectModelLoader
final Aspect aspect = ...

//Server url i.e. http://www.example.com
final String baseUrl = ...

// The resource path which shall be added i.e. /testPath/{parameter}
final String resourcePath = ...

// A String containing all the information for in the resource path mentioned dynamic properties. The string must be syntactially valid YAML.
// A definition for a dynamic property is mandatory as you won't be able to define which sort of data have to be used.
// I.e for the above used path:
//parameter:
//  name: parameter
//  in: path
//  description: A parameter.
//  required: true
//  schema:
//    type: string
final Optional<String> yamlProperties = ...

//If the query api shall be added to the generated specification.
final boolean includeQueryApi = ...

// The paging Option to be chosen. In case paging is possible: default for a time related collection is time-based paging.
// Otherwise the default is offset-based paging.
final Optional<PagingOption> pagingOption = ...

// Generate pretty-printed YAML
final AspectModelOpenApiGenerator generator = new AspectModelOpenApiGenerator();
final String yaml = generator.applyForYaml( aspect, useSemanticVersion, baseUrl, resourcePath, yamlProperties, includeQueryApi, pagingOption );


//A JsonNode containing all the information for in the resource path mentioned variable properties.
final final Optional<JsonNode> jsonProperties = ...

// Generate the JSON
final AspectModelOpenApiGenerator generator = new AspectModelOpenApiGenerator();
final JsonNode json = generator.apply( aspect, useSemanticVersion, baseUrl, resourcePath, jsonProperties, includeQueryApi, pagingOption );

// If needed, print or pretty print it into a string
final ByteArrayOutputStream out = new ByteArrayOutputStream();
final ObjectMapper objectMapper = new ObjectMapper();

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 Java Code for Aspect Models

Java code can be generated from an Aspect model in two ways:

  1. 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 the Set Characteristic of a Property) or javax.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.

  2. 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>io.openmanufacturing</groupId>
    <artifactId>sds-aspect-model-java-generator</artifactId>
    <version>2.1.0</version>
</dependency>
Gradle Groovy DSL
implementation 'io.openmanufacturing:sds-aspect-model-java-generator:2.1.0'
Gradle Kotlin DSL
implementation("io.openmanufacturing:sds-aspect-model-java-generator:2.1.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

xsd:string

java.lang.String

xsd:boolean

java.lang.Boolean

xsd:decimal

java.math.BigDecimal

xsd:integer

java.math.BigDecimal

xsd:double

java.lang.Double

xsd:float

java.lang.Float

xsd:date

javax.xml.datatype.XMLGregorianCalendar

xsd:time

javax.xml.datatype.XMLGregorianCalendar

xsd:dateTime

javax.xml.datatype.XMLGregorianCalendar

xsd:dateTimeStamp

javax.xml.datatype.XMLGregorianCalendar

xsd:gYear

javax.xml.datatype.XMLGregorianCalendar

xsd:gMonth

javax.xml.datatype.XMLGregorianCalendar

xsd:gDay

javax.xml.datatype.XMLGregorianCalendar

xsd:gYearMonth

javax.xml.datatype.XMLGregorianCalendar

xsd:gMonthDay

javax.xml.datatype.XMLGregorianCalendar

xsd:duration

javax.xml.datatype.Duration

xsd:yearMonthDuration

javax.xml.datatype.Duration

xsd:dayTimeDuration

javax.xml.datatype.Duration

xsd:byte

java.lang.Byte

xsd:short

java.lang.Short

xsd:int

java.lang.Integer

xsd:long

java.lang.Long

xsd:unsignedByte

java.lang.Short

xsd:unsignedShort

java.lang.Integer

xsd:unsignedInt

java.lang.Long

xsd:unsignedLong

java.math.BigInteger

xsd:positiveInteger

java.math.BigInteger

xsd:nonNegativeInteger

java.math.BigInteger

xsd:negativeInteger

java.math.BigInteger

xsd:nonPositiveInteger

java.math.BigInteger

xsd:hexBinary

byte[]

xsd:base64Binary

byte[]

xsd:anyURI

java.net.URI

bamm:curie

io.openmanufacturing.metamodel.datatypes.Curie

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.

import java.io.OutputStream;
import io.openmanufacturing.sds.aspectmodel.java.QualifiedName;
import io.openmanufacturing.sds.aspectmodel.java.pojo.AspectModelJavaGenerator;
import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;

OutputStream outputStreamForName( final QualifiedName qualifiedName ) {
   // Return an OutputStream for the qualified class name
}

// Versioned model as returned by the AspectModelResolver
final VersionedModel versionedModel = ...

// The boolean flag determines whether the generator should generate Jackson annotations
final AspectModelJavaGenerator generator = new AspectModelJavaGenerator( versionedModel, true );
generator.generate( this::outputStreamForName );

Generating Static Meta Classes

For the generation of static meta classes, consider the following example:

import java.io.OutputStream;
import io.openmanufacturing.sds.aspectmodel.java.QualifiedName;
import io.openmanufacturing.sds.aspectmodel.java.metamodel.StaticMetaModelJavaGenerator;
import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;

OutputStream outputStreamForName( final QualifiedName qualifiedName ) {
   // Return an OutputStream for the qualified class name
}

// Versioned model as returned by the AspectModelResolver
final VersionedModel versionedModel = ...

final StaticMetaModelJavaGenerator generator = new StaticMetaModelJavaGenerator( versionedModel );
generator.generate( this::outputStreamForName );

To use the generated static meta classes, you need the following additional dependency:

Maven
<dependency>
    <groupId>io.openmanufacturing</groupId>
    <artifactId>sds-aspect-static-meta-model-java</artifactId>
    <version>2.1.0</version>
</dependency>
Gradle Groovy DSL
implementation 'io.openmanufacturing:sds-aspect-static-meta-model-java:2.1.0'
Gradle Kotlin DSL
implementation("io.openmanufacturing:sds-aspect-static-meta-model-java:2.1.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 BAMM-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() OMP 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.

This is shown in the following sample:

import java.util.function.Consumer;

import io.openmanufacturing.sds.aspectmodel.resolver.AspectModelResolver;
import io.openmanufacturing.sds.aspectmodel.resolver.services.VersionedModel;
import io.openmanufacturing.sds.aspectmetamodel.KnownVersion;
import io.openmanufacturing.sds.aspectmodel.versionupdate.Migrator;
import io.openmanufacturing.sds.aspectmodel.versionupdate.Migrators;

import io.vavr.control.Try;

// Variant 1: Load an Aspect model and migrate it to the latest meta model version
final Try<VersionedModel> updatedModel =
   new AspectModelResolver().resolveAspectModel( /* see Loading an Aspect model */ ) (1)
      .flatMap( versionedModel -> Migrators.updateMetaModelVersion( versionedModel, KnownVersion.getLatest() ) ); (2)

// Variant 2: Same as Variant 1, except also log a description of each migration step
final Consumer<Migrator> observeMigrationStep = migrator -> { (5)
   LOG.info( "Performed migration step from meta model version {} to {}: {}",
         migrator.getInputPort().toVersionString(), migrator.getOutputPort().toVersionString(),
         migrator.getDescription() );
};

final Try<VersionedModel> updatedModel2 =
   new AspectModelResolver().resolveAspectModel( /* see Loading an Aspect model */ ) (3)
      .flatMap( versionedModel ->
            Migrators.updateMetaModelVersion( versionedModel, KnownVersion.getLatest(), observeMigrationStep ) ); (4)
1 An Aspect model is loaded, and
2 Migrators#updateMetaModelVersion is used to automatically migrate the model to the latest meta model version.
3 In the second variant, an Aspect model is loaded, and
4 the update function is called. This time, an additional argument is passed, namely a
5 Consumer<Migrator> that is used to receive a callback for each migration step.

To use the meta model version migrator, you need the following dependency:

Maven
<dependency>
    <groupId>io.openmanufacturing</groupId>
    <artifactId>sds-aspect-meta-model-version-migrator</artifactId>
    <version>2.1.0</version>
</dependency>
Gradle Groovy DSL
implementation 'io.openmanufacturing:sds-aspect-meta-model-version-migrator:2.1.0'
Gradle Kotlin DSL
implementation("io.openmanufacturing:sds-aspect-meta-model-version-migrator:2.1.0")

Accessing the BAMM programmatically

In order to access the source RDF files that describe the BAMM vocabulary, shared Characteristics and Entities as well as Units, you can add a dependency to the sds-aspect-meta-model artifact. Note that this artifact does not contain any Java classes.

Maven
<dependency>
    <groupId>io.openmanufacturing</groupId>
    <artifactId>sds-aspect-meta-model</artifactId>
    <version>2.0.0</version>
</dependency>
Gradle Groovy DSL
implementation 'io.openmanufacturing:sds-aspect-meta-model:2.0.0'
Gradle Kotlin DSL
implementation("io.openmanufacturing:sds-aspect-meta-model:2.0.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.0.0
│       ├── characteristic-definitions.ttl
│       ├── characteristic-instances.ttl
│       ├── characteristic-shapes.ttl
│       └── characteristic-validations.js
├── entity
│   └── 2.0.0
│       ├── FileResource.ttl
│       ├── ThreeDimensionalPosition.ttl
│       └── TimeSeriesEntity.ttl
├── meta-model
│   └── 2.0.0
│       ├── aspect-meta-model-definitions.ttl
│       ├── aspect-meta-model-shapes.ttl
│       ├── prefix-declarations.ttl
│       └── type-conversions.ttl
└── unit
    └── 2.0.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:
import java.io.InputStream;
import java.net.URL;
import java.util.Optional;
import io.openmanufacturing.sds.aspectmodel.resolver.services.AspectMetaModelResourceResolver;
import io.openmanufacturing.sds.aspectmodel.resolver.services.MetaModelUrls;
import io.openmanufacturing.sds.aspectmetamodel.KnownVersion;
import io.openmanufacturing.sds.aspectmodel.vocabulary.BAMM;

final KnownVersion metaModelVersion = KnownVersion.BAMM_1_0_0;
final AspectMetaModelResourceResolver resolver = new AspectMetaModelResourceResolver();
final Optional<URL> characteristicDefinitionsUrl =
      MetaModelUrls.url( "characteristic", metaModelVersion, "characteristic-instances.ttl" );
characteristicDefinitionsUrl.ifPresent( url -> {
   final InputStream inputStream = resolver.openUrl( url );
   resolver.loadTurtle( inputStream ).forEach( model -> {
      // Do something with the org.apache.jena.org.rdf.model.Model
      final BAMM bamm = new BAMM( metaModelVersion );
      final int numberOfCharacteristicInstances =
            model.listStatements( null, RDF.type, bamm.Characteristic() ).toList().size();
      System.out.println( "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 BAMM 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 BAMM Aspect Models. On the one hand, this allows to integrate BAMM models in AAS environments and on the other hand it allows AAS submodels to be described with rich semantics, as it is possible with BAMM.

Details of the Mapping Concept

In the following section, the mapping rules applied by the generator are explained. The rules apply to BAMM v1.0.0 and AAS Specification Part 1 V3.0RC01.

BAMM AAS Comment

bamm:Aspect

aas:Submodel with kind=Template

Empty Asset and AssetAdministrationShell entries are added to the output file

bamm:name

aas:Submodel.idShort

bamm:preferredName

aas:Submodel.displayName

bamm:description

aas:Submodel.description

bamm:property

see bamm:Property

bamm:operation

see bamm:Operation

bamm:Aspect.urn

aas:Submodel.semanticId

bamm:Property

aas:Property, aas:SubmodelElementCollection

The AAS type is derived from the type of the BAMM Characteristic specifying the BAMM 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 BAMM Characteristic is of a Collection type (see the Characteristics taxonomy). In all other cases an aas:Property will be generated

bamm:Property.name

aas:Property.idShort

bamm:Property.urn

aas:ConceptDescription.identification.id, aas:Property.semanticId

bamm:Property.preferredName

aas:Property.displayName

bamm:Property.description

aas:Property.description

Note: Also mapped to aas:DataSpecificationIEC61360.definition of the aas:ConceptDescription of this property

bamm:Property.exampleValue

aas:Property.value

bamm:Characteristic.dataType

aas:Property.valueType

bamm:Operation

Operation

in/out parameters are not used in BAMM so the mapping only generates input variables and output variables in AAS

bamm-c:Characteristic

aas:SubmodelElement, aas:ConceptDescription

Characteristics in BAMM 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.

bamm-c:Collection

aas:SubmodelElementCollection, aas:ConceptDescription

The general remarks to Characteristics apply also to Collection type Characteristics. However, properties referencing Collections are mapped to SubmodelElementCollections. Specific properties of collections are mapped. bamm:Set is unique, bamm:SortedSet is unique and sorted, bamm:List is sorted.

bamm-c:Quantifiable

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.

bamm-c:Either

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.

bamm-c:Trait

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.

bamm-c:Code

aas:SubmodelElement, aas:ConceptDescription

Similar to plain Characteristic.

bamm-c:StructuredValue

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.

bamm-c:Enumeration

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.

bamm-c:State

aas:SubmodelElement, aas:ConceptDescription

Same as Enumeration.

bamm-c:MultiLanguageText

aas:MultiLanguageProperty

if a MultiLanguageText is used in BAMM it is mapped to the MultiLanguageProperty in AAS.

Known Limitations

The AAS Generator implements a base set of features, which are mapped from BAMM to AAS. However, there are still limitations:

  • Predefined entity mapping (FileResource would be mapped to aas:File)

  • bamm-c:Either is mapped to aas:SubmodelElementCollection with two entries for left and right side

  • Recursive optional properties of BAMM model are not included in output but dropped straight away