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.Setas the type for theSetCharacteristic of a Property) orjavax.validationannotations. 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.12.0</version> </dependency> - Gradle Groovy DSL
-
implementation 'org.eclipse.esmf:esmf-aspect-model-java-generator:2.12.0' - Gradle Kotlin DSL
-
implementation("org.eclipse.esmf:esmf-aspect-model-java-generator:2.12.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.
By default, POJO classes are generated without setters. If setters are desired, use enableSetters( true ) on the
generator config. Three setter styles are supported:
| Setter style | Example output |
|---|---|
|
|
|
|
|
|
The FLUENT_* styles allow for method chaining, so that POJO instances can be written in a more compact way.
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.loader.AspectModelLoader;
import org.eclipse.esmf.metamodel.AspectModel;
// AspectModel as returned by the AspectModelLoader
final AspectModel aspectModel = // ...
final JavaCodeGenerationConfig config = JavaCodeGenerationConfigBuilder.builder()
.enableJacksonAnnotations( true )
.packageName( "com.example.mycompany" ) // if left out, package is taken from Aspect's namespace
.enableSetters( true ) // if left out, setters are not generated
.setterStyle( JavaCodeGenerationConfig.SetterStyle.FLUENT ) // if left out, "STANDARD" setter style is used
.build();
final AspectModelJavaGenerator generator = new AspectModelJavaGenerator( aspectModel.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.loader.AspectModelLoader;
import org.eclipse.esmf.metamodel.AspectModel;
// AspectModel as returned by the AspectModelLoader
final AspectModel aspectModel = // ...
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( aspectModel.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.12.0</version> </dependency> - Gradle Groovy DSL
-
implementation 'org.eclipse.esmf:esmf-aspect-static-meta-model-java:2.12.0' - Gradle Kotlin DSL
-
implementation("org.eclipse.esmf:esmf-aspect-static-meta-model-java:2.12.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
Generating SQL for Aspect Models
Using the Aspect Model SQL generator, an SQL script can be generated that sets up a table for data corresponding to the Aspect. The current implementation provides support for the Databricks SQL dialect and a mapping strategy that uses a denormalized table, i.e., the table contains one column for each Property used in the Aspect Model (or any of its transitively referenced Entities).
Show used imports
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import org.eclipse.esmf.aspectmodel.generator.sql.AspectModelSqlGenerator;
import org.eclipse.esmf.aspectmodel.generator.sql.SqlGenerationConfig;
import org.eclipse.esmf.aspectmodel.generator.sql.SqlGenerationConfigBuilder;
import org.eclipse.esmf.aspectmodel.generator.sql.databricks.DatabricksColumnDefinitionBuilder;
import org.eclipse.esmf.aspectmodel.generator.sql.databricks.DatabricksSqlGenerationConfig;
import org.eclipse.esmf.aspectmodel.generator.sql.databricks.DatabricksSqlGenerationConfigBuilder;
import org.eclipse.esmf.aspectmodel.generator.sql.databricks.DatabricksType;
import org.eclipse.esmf.aspectmodel.loader.AspectModelLoader;
import org.eclipse.esmf.metamodel.AspectModel;
// AspectModel as returned by the AspectModelLoader
final AspectModel aspectModel = // ...
final DatabricksSqlGenerationConfig databricksSqlGenerationConfig =
DatabricksSqlGenerationConfigBuilder.builder()
.commentLanguage( Locale.ENGLISH ) // optional
.includeTableComment( true ) // optional
.includeColumnComments( true ) // optional
.decimalPrecision( 10 ) // optional
.decimalScale( 0 ) // optional
.customColumns( List.of( // optional
DatabricksColumnDefinitionBuilder.builder()
.name( "custom_column" )
.type( new DatabricksType.DatabricksArray( DatabricksType.STRING ) )
.nullable( false )
.comment( Optional.of( "Custom column" ) )
.build() ) )
.build();
final SqlGenerationConfig sqlGenerationConfig =
SqlGenerationConfigBuilder.builder()
.dialect( SqlGenerationConfig.Dialect.DATABRICKS )
.mappingStrategy( SqlGenerationConfig.MappingStrategy.DENORMALIZED )
.dialectSpecificConfig( databricksSqlGenerationConfig )
.build();
final String result = new AspectModelSqlGenerator( aspectModel.aspect(), sqlGenerationConfig ).getContent();
Databricks Type Mapping
Data types in the Aspect Model are mapped to Databricks types using the following correspondences:
| Aspect model type | Databricks SQL type | Note |
|---|---|---|
|
|
|
|
|
|
|
|
While xsd:decimal is by definition unbounded, DECIMAL’s default precision is 10 digits and can be up to 38. if we assume values larger than that can appear in the data, the Aspect Models using xsd:decimal should also use a samm-c:FixedPointConstraint accordingly. |
|
|
As opposed to xsd:int, xsd:integer has arbitrary precision, i.e. DECIMAL is needed. |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|