Getting Started with Java APIs for 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 esmf-aspect-model-starter artifact that aggregates all necessary dependencies:

Maven
<dependency>
    <groupId>org.eclipse.esmf</groupId>
    <artifactId>esmf-aspect-model-starter</artifactId>
    <version>2.12.0</version>
</dependency>
Gradle Groovy DSL
implementation 'org.eclipse.esmf:esmf-aspect-model-starter:2.12.0'
Gradle Kotlin DSL
implementation("org.eclipse.esmf:esmf-aspect-model-starter:2.12.0")

Alternatively, you can use the esmf-sdk-parent artifact as a Maven BOM (Bill of Materials) to enable declaring dependencies to specific modules without an explicit version. Add the following section to the Maven pom.xml in your project:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.eclipse.esmf</groupId>
      <artifactId>esmf-sdk-parent</artifactId>
      <version>2.12.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

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 21 and require a JDK >= 21 such as Adoptium Temurin.

The esmf-sdk can also be used with a Java 21-compatible GraalVM JDK.

When using esmf-sdk with a GraalVM JDK and you target native-image compilation, add a dependency to the esmf-native-support module. This will add the necessary configuration for esmf-sdk resources, reflection and initialization that is required by the compiler:

Maven
<dependency>
    <groupId>org.eclipse.esmf</groupId>
    <artifactId>esmf-native-support</artifactId>
    <version>2.12.0</version>
</dependency>
Gradle Groovy DSL
implementation 'org.eclipse.esmf:esmf-native-support:2.12.0'
Gradle Kotlin DSL
implementation("org.eclipse.esmf:esmf-native-support:2.12.0")

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.getNamespaceMainPart(); // 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.12.0</version>
</dependency>
Gradle Groovy DSL
implementation 'org.eclipse.esmf:esmf-aspect-model-urn:2.12.0'
Gradle Kotlin DSL
implementation("org.eclipse.esmf:esmf-aspect-model-urn:2.12.0")

Loading and Saving Aspect Models

Aspect models are, like the Semantic Aspect Meta Model, described using the Resource Description Format (RDF, [rdf11]) and the Terse RDF Triple Language syntax (TTL, [turtle]). When an Aspect Model is loaded, there are two ways of working with it: Either on the abstraction level of the underlying RDF/Turtle serialization, or on the native Java Aspect Model level, where the model is represented as a Java object graph. 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, or build code interfacing with other RDF vocabularies, this is better done using the RDF API.

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

To load an AspectModel, you use the org.eclipse.esmf.aspectmodel.loader.AspectModelLoader class. An instance of AspectModelLoader provides load() methods to load an Aspect Model from an InputStream, one or multiple Files, or a number of AspectModelUrn (where you can delegate finding out where a model element is defined to the AspectModelLoader - details on that are explained in the next section).

The AspectModelLoader loads Aspect Models based on the most recent version of the Semantic Aspect Meta Model and previous versions: Models based on older meta model versions are automatically translated to models corresponding to the latest meta model version.

The resulting AspectModel object contains information about the loaded model elements, their namespaces and the files they were defined in. Details about the structure of these objects can be found in the Decision Record 0007. The AspectModel provides the methods elements(), files() and namespaces() as well as the convenience methods aspects() (which returns all Aspect elements in the model) and aspect(), which returns the single Aspect element if one exists.

Show used imports
import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader;
import org.eclipse.esmf.metamodel.AspectModel;
import org.eclipse.esmf.metamodel.ModelElement;
final AspectModel aspectModel = new AspectModelLoader().load(
      // a File, InputStream or AspectModelUrn
);

// Do something with the elements
for ( final ModelElement element : aspectModel.elements() ) {
   System.out.printf( "Model element: %s has URN %s%n", element.getName(), element.urn() );
}

// Directly work with the Aspect, if the AspectModel happens to contain
// exactly one Aspect. If there are 0 or >1 Aspects, this will throw an exception.
System.out.println( "Aspect URN: " + aspectModel.aspect().urn() );

To include the Java Aspect Model artifact, use the following dependency:

Maven
<dependency>
    <groupId>org.eclipse.esmf</groupId>
    <artifactId>esmf-aspect-meta-model-java</artifactId>
    <version>2.12.0</version>
</dependency>
Gradle Groovy DSL
implementation 'org.eclipse.esmf:esmf-aspect-meta-model-java:2.12.0'
Gradle Kotlin DSL
implementation("org.eclipse.esmf:esmf-aspect-meta-model-java:2.12.0")

Understanding Model Resolution

The example in the last section showed how a self-contained Aspect Model file can be loaded (i.e., a file that does not refer to model element definitions in other files or namespaces). Whenever models are loaded that could contain such references, or you want to load a model element by URN, the AspectModelLoader relies on so-called resolution strategies. A resolution strategy is a function that takes a model element identifier (a model element URN) as an input and returns the content of the file that contains the corresponding model element definition. Note that this is not necessarily a file in the local filesystem, but it could also be a remote file or even exist only virtually as a collection of statements in an RDF triple store. Several ResolutionStrategy​s are provided that can be instantiated and passed to the AspectModelLoader constructor to enable it to find model element definitions:

  • The FileSystemStrategy resolves elements from files in the file system which are either structured in the models directory structure or exist as flat list of files in one directory (by using FileSystemStrategy with a FlatModelsRoot).

  • The ClassPathStrategy resolves model elements from resources in the Java class path.

  • The FromLoadedFileStrategy resolves model elements from an AspectModelFile that already resides in memory.

  • The EitherStrategy can be used to chain two or more different ResolutionStrategy​s.

  • The ExternalResolverStrategy delegates resolution to an external command such as a script; it is used in the implementation of the --custom-resolver option of the samm-cli.

  • The GitHubStrategy resolves model elements from repositories hosted on GitHub.

In addition, custom resolution strategies can be provided by implementing the ResolutionStrategy interface.

The following example demonstrates how to pass a custom instance of a ResolutionStrategy and resolve a model element by URN:

Show used imports
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader;
import org.eclipse.esmf.aspectmodel.resolver.FileSystemStrategy;
import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy;
import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;
import org.eclipse.esmf.metamodel.AspectModel;
import org.eclipse.esmf.metamodel.vocabulary.SammNs;

import org.apache.jena.vocabulary.RDF;
// 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 AspectModelUrn urn = AspectModelUrn.fromUrn( "urn:samm:org.eclipse.esmf.examples.movement:1.0.0#Movement" );
final AspectModel result = new AspectModelLoader( fileSystemStrategy ).load( urn );

Loading an Aspect Model to work on the RDF Level

The following example shows how to use the AspectModelLoad to load an Aspect Model.

Show used imports
import java.io.File;
import java.nio.file.Path;
import java.nio.file.Paths;

import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader;
import org.eclipse.esmf.aspectmodel.resolver.FileSystemStrategy;
import org.eclipse.esmf.aspectmodel.resolver.ResolutionStrategy;
import org.eclipse.esmf.aspectmodel.urn.AspectModelUrn;
import org.eclipse.esmf.metamodel.AspectModel;
import org.eclipse.esmf.metamodel.vocabulary.SammNs;

import org.apache.jena.vocabulary.RDF;
final AspectModel aspectModel = new AspectModelLoader().load(
      // a File, InputStream or AspectModelUrn
);

// Do something with the Aspect Model on the RDF level.
// Example: List the URNs of all samm:Entitys
aspectModel.mergedModel().listStatements( null, RDF.type, SammNs.SAMM.Entity() )
      .forEachRemaining( statement -> System.out.println( statement.getSubject().getURI() ) );

After loading an AspectModel using the AspectModelLoader, you can access the mergedModel() on the AspectModel, which represents the merged RDF graph of all source files that were loaded in the model and therefore contains all transitively referenced model element definitions. Alternatively, you can also access the sourceModel() of each Aspect Model file given by files(), which will return only the RDF graph of this file.

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 provide Java classes that represent the meta model.

Maven
<dependency>
    <groupId>org.eclipse.esmf</groupId>
    <artifactId>esmf-semantic-aspect-meta-model</artifactId>
    <version>2.2.0</version>
</dependency>
Gradle Groovy DSL
implementation 'org.eclipse.esmf:esmf-semantic-aspect-meta-model:2.2.0'
Gradle Kotlin DSL
implementation("org.eclipse.esmf:esmf-semantic-aspect-meta-model:2.2.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.2.0
│       ├── characteristic-definitions.ttl
│       ├── characteristic-instances.ttl
│       ├── characteristic-shapes.ttl
│       └── characteristic-validations.js
├── entity
│   └── 2.2.0
│       ├── FileResource.ttl
│       ├── ThreeDimensionalPosition.ttl
│       └── TimeSeriesEntity.ttl
├── meta-model
│   └── 2.2.0
│       ├── aspect-meta-model-definitions.ttl
│       ├── aspect-meta-model-shapes.ttl
│       ├── prefix-declarations.ttl
│       └── type-conversions.ttl
└── unit
    └── 2.2.0
        └── units.ttl
You can use the MetaModelFile enumeration provided by the esmf-aspect-meta-model-java module to access object representations of the meta model files, for example:
Show used imports
import org.apache.jena.rdf.model.Model;
import org.apache.jena.vocabulary.RDF;

import org.eclipse.esmf.aspectmodel.resolver.modelfile.MetaModelFile;
import org.eclipse.esmf.metamodel.vocabulary.SammNs;
import org.eclipse.esmf.samm.KnownVersion;
final KnownVersion metaModelVersion = KnownVersion.getLatest();
final Model characteristicDefinitions = MetaModelFile.CHARACTERISTIC_DEFINITIONS.sourceModel();

// Do something with the org.apache.jena.org.rdf.model.Model
final int numberOfCharacteristicInstances =
      characteristicDefinitions.listStatements( null, RDF.type, SammNs.SAMM.Characteristic() ).toList().size();
final String result = String.format( "Meta Model Version " + metaModelVersion.toVersionString()
      + " defines " + numberOfCharacteristicInstances + " Characteristic instances" );

Next Steps

For more advanced topics, see the dedicated pages: