Types
The ACE type system
All values in ACE have a type. There are three main classes of types in ACE: objects, arrays and primitives. ACE does not support sub-typing, so as far as the type system is concerned, the only relationships that exist between types is that a type may contain values of other types.
Static vs runtime types
All values have a runtime type. A field or a list may also have a static type. In the normal case, the static and runtime type are the same. The exception is when the static type is 'any', which means the type can only be known at runtime.
The 'any' type
The any type is used when the concrete type is not statically known. A value can never have the any type, it is only used in type declarations.
Primitives
The primitive types are fixed and not user definable. Some types are only available in a statically typed context. When the type is not statically known, the Dyn type for the value's JSON type will be used. All types support being undefined when used as a field, but only some types may be null- these are indicated in the Null column.
| Type | JSON type | Dyn | Null | Description |
|---|---|---|---|---|
| string | string | X | * | A JSON string |
| contentVersionId | string | * | A content version ID, as a JSON string on the format "contentid/c:XXX:YYY" | |
| alias | string | * | A content alias, as a JSON string on the format "namespace/alias" | |
| integer | number | A JSON number interpreted as a 32 bit signed integer | ||
| long | number | A JSON number interpreted as 64 bit signed integer | ||
| float | number | A JSON number interpreted as a IEEE-754 single precision floating point number | ||
| double | number | X | A JSON number interpreted as a IEEE-754 double precision floating point number | |
| boolean | boolean | X | A JSON boolean | |
| datetime | string | * | An instant in time, as a JSON string using the full Javascript Date Time String Format in UTC (e.g. "2007-03-12T14:33:00.123Z") |
Objects
Object types correspond to JSON objects. An object is made up of fields, each of which has a static type. There are two kinds of object types: closed, and open. A closed object type has a fixed set of fields, each of which has its own static type. An open object type by contrast can have arbitrary fields, but the fields all have the same static type.
Closed object types must be registered under an explicit name, while
open object types are always named "MAP::\
An object may optionally be connected with a Java class, which is used by composers that request a Java bean type.
Arrays
Array types correspond to JSON arrays. An array has a static element
type specified in the type name, which is "LIST::\
Defining Types in ACE
Types are defined by content with the type aceTypeDefinitionContent, using the aspect type aceTypeDefinition, and an alias in the _type namespace with the type's name as the alias. A node type definition may have the following fields:
| Field | Type | Description |
|---|---|---|
| name | string | The name of the type |
| fields | MAP::any | Maps field names to field type names |
| openFieldType | string | Marks the type as open for extension with more fields of some type |
| isContentType | boolean | Marks the type as a content type. |
openFieldType is primarily for use with content types and map types - if it has a value, additional fields can be added to instances of the type as long as they match the openFieldType (which can be any to permit fields with any type of value).
Example
A type definition with three fields, a string, a type object, and an object where the values are content IDs might look like this:
{
"name": "my_Type",
"fields": {
"aString" : "string",
"anObject" : "my_OtherType",
"contents" : "MAP::contentid"
}
}
Defining content types
To be used as a content type, a type must fulfill some special requirements:
- it must be marked with isContentType
- it must have openFieldType set to any (to permit adding optional aspects)
- its fields must be named the same as their type (this being the content's mandatory aspects)
To simplify defining content types, the import supports a special DSL for content types: a YAML file with a name, and a list of mandatory aspects. The file must have an extension of .contentType to be processed as a content type definition.
name: my_Content
mandatoryAspects: [my_Aspect, my_OtherAspect]
Defining Aspects in ACE
In order to be able to use a type as a content aspect, the type must be registered as an aspect. This is done by a content with type atex_AspectDefinition, and an aspect of type aceAspectDefinition, and an alias in the _aspect namespace with the aspect's type as the alias. An aspect definition has only one field, id, which holds the name of the aspect type.
Example
Registering the type from the previous example as an aspect.
{
"system": {
"contentType": "aceAspectDefinitionContent"
},
"aspects": {
"aceAspectDefinition": {
"_type": "aceAspectDefinition",
"id": "my_Type"
}
}
}
Type Service
The type service exposes an API to inspect various types in the system.
Example 1, examine types
To inspect a specific type provide the name of the type:
$ curl -H "X-Auth-Token: $TOKEN" http://ace-content-service:8081/type/timestatetestcontent
{
"main" : {
"openFieldType" : "any",
"metaType" : "NODE",
"name" : "timestatetestcontent",
"fields" : {
"timestatetesttype" : "any"
},
"isContentType" : true
}
}
It is possible to also include referenced types in the response by providing the recursive=true query parameter.
Example 2, list types
You can also list types. By default a list of type names will be returned. You can optionally pass the query param verbose=true to get a list of types that contains all the data related to each type that you would receive from the single get endpoint above.
You can also optionally pass the query param isContentType=true to only return types that are content types, isContentType=false will only return types that are NOT content types, by default both types that are and aren't content types will be returned.
$ curl -H "X-Auth-Token: $TOKEN" http://ace-content-service:8081/type
[
"timestatetestcontent",
"someOtherType"
]
Types from Java classes
For convenience, types, aspects and content types can be automatically generated from Java classes using annotations. There are three annotations used for this purpose:
@TypeDefinition- used to generate an ACE type that should not be an aspect.@AspectDefinition- used to generate an ACE aspect. Also automatically generates a regular ACE type (see above).@ContentTypeDefinition- used to generate an ACE content type.
All of these annotations reside in the com.atex.ace.annotations package.
Setup
To do this you must add a dependency to com.atex.ace:ace-annotations and use the
ace-annotations maven plugin. Use the maven-jar-plugin to include the generated files in the ace-content JAR:
<project>
...
<dependencies>
...
<dependency>
<artifactId>ace-annotations</artifactId>
<groupId>com.atex.ace</groupId>
<version>1.3.2</version>
</dependency>
...
</dependencies>
...
<build>
<plugins>
<plugin>
<artifactId>ace-annotations-maven-plugin</artifactId>
<groupId>com.atex.ace</groupId>
<version>1.3.2</version>
<configuration>
<ace>true</ace>
</configuration>
<executions>
<execution>
<id>process-ace-annotations</id>
<goals>
<goal>process-ace-annotations</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>jar</goal>
</goals>
<configuration>
<classesDirectory>target/generated-content</classesDirectory>
<classifier>ace-content</classifier>
<includes>
<include>**/*</include>
</includes>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
Usage examples
Example 1 – Aspect generation
The following Java bean example (where the value of the annotation is the name of the aspect to generate)...
@AspectDefinition(FilesAspectBean.ASPECT_NAME)
public class FilesAspectBean
{
public static final String ASPECT_NAME = "myFiles";
private Map<String, ContentFileInfo> files = null;
public FilesAspectBean() {
}
public FilesAspectBean(final FilesAspectBean orig) {
this.files = new HashMap<>(orig.getFiles());
}
public FilesAspectBean(final Map<String, ContentFileInfo> files) {
this.files = Collections.unmodifiableMap(new HashMap<>(files));
}
[...]
}
...will generate type_myFiles.json...
{
"system": {
"contentType": "aceTypeDefinitionContent"
},
"aspects": {
"aceTypeDefinition": {
"_type": "aceTypeDefinition",
"name": "myFiles",
"fields": {
"files": "MAP::myFileInfo"
}
}
}
}
...and aspect_myFiles.json:
{
"system": {
"contentType": "aceAspectDefinitionContent"
},
"aspects": {
"aceAspectDefinition": {
"_type": "aceAspectDefinition",
"id": "myFiles"
}
}
}
Example 2 – Type generation
The following Java bean example (where the value of the annotation is the name of the type to generate)...
@TypeDefinition(ContentFileInfo.TYPE_NAME)
public class ContentFileInfo {
public static final String TYPE_NAME = "myFileInfo";
private String filePath;
private String fileUri;
public ContentFileInfo() {
}
public ContentFileInfo(final ContentFileInfo orig) {
filePath = orig.getFilePath();
fileUri = orig.getFileUri();
}
public ContentFileInfo(final String filePath, final String fileUri) {
this.filePath = filePath;
this.fileUri = fileUri;
}
[...]
}
...will generate the file type_myFileInfo.json:
{
"system": {
"contentType": "aceTypeDefinitionContent"
},
"aspects": {
"aceTypeDefinition": {
"_type": "aceTypeDefinition",
"name": "myFileInfo",
"fields": {
"filePath": "string",
"fileUri": "string"
}
}
}
}
Example 3 – Content type generation without mandatory aspects
The following Java bean example...
@ContentTypeDefinition(ArticleContentType.TYPE_NAME)
public class ArticleContentType {
public static final String TYPE_NAME = "article";
private FilesAspect aceFiles;
public ArticleContentType() {
}
[...]
}
...will generate the file article.contentType:
name: article
mandatoryAspects: []
Example 4 – Content type generation with one mandatory aspect
This java bean...
@ContentTypeDefinition(ArticleContentType.TYPE_NAME)
public class ArticleContentType {
public static final String TYPE_NAME = "articleWithFiles";
@MandatoryAspect
private FilesAspect aceFiles;
public ArticleContentType() {
}
[...]
}
...will generate the file articleWithFiles.contentType:
name: articleWithFiles
mandatoryAspects: [aceFiles]
Example 5 – Content type generate with several mandatory aspects
This Java bean...
@ContentTypeDefinition(
value = ArticleContentType.TYPE_NAME,
mandatoryAspects = { "aceFiles", "aceCategorization" })
public class ArticleContentType {
public static final String TYPE_NAME = "articleWithFilesAndCategorization";
private FilesAspect aceFiles;
private CategorizationAspect aceCategorization;
public ArticleContentType() {
}
[...]
}
...will generate the file articleWithFilesAndCategorization.contentType:
name: articleWithFilesAndCategorization
mandatoryAspects: [aceFiles,aceCategorization]
Custom storage types
In some cases, you will want to use a Java type in your aspect / type source bean and have it stored as one of the ACE type system types (see Primitives). A very common example of this would be wanting to use one of the built-in Java types (e.g. java.time.Duration) which doesn't have any native representation in the ACE type system.
Example:
@AspectDefinition(MyDataBean.ASPECT_NAME)
public class MyDataBean
{
public static final String ASPECT_NAME = "myData";
private Duration duration;
public MyDataBean() {
}
[...]
}
In this case, you might want to have the duration field stored in ACE as type string. You can achieve this by
creating an AceTypeAdapter, which is a type adapter interface you can implement and make available
on the classpath when generating your types.
Maven dependency
Similar to how you use the ACE type annotations, you first need to declare a dependency to the com.atex:ace-java-serialization
Maven artifact:
<project>
...
<dependencies>
...
<dependency>
<artifactId>ace-java-serialization</artifactId>
<groupId>com.atex</groupId>
<version>1.3.1</version>
</dependency>
...
</dependencies>
...
</project>
Implementation
You can then implement the type adapter interface:
package com.example;
public class DurationAceTypeAdapter
implements AceTypeAdapter<Duration, String>
{
@Override
public Duration deserialize(final JsonElement element)
{
return Duration.parse(element.getAsString());
}
}
The first generic argument for the AceTypeAdapter interface is the Java type you are declaring the type adapter
for. The second argument is the type used to store the object in ACE. See Primitives for possible
values.
Java Service Loader declaration
ACE type adapters are loaded using Java service loader functionality. In order for this to work, you need to declare
your type adapter implementations in the service loader declaration file
META-INF/services/com.atex.ace.client.serialization.AceTypeAdapter of the module containing the type adapter classes.
Example declaration of the Duration type adapter shown above:
com.example.DurationAceTypeAdapter
Please see the ServiceLoader javadoc for more information on service loading.
Usage in type generation
The last step is to make your adapter implementation available to the ACE Annotations Maven plugin:
<project>
...
<build>
<plugins>
<plugin>
<groupId>com.atex.ace</groupId>
<artifactId>ace-annotations-maven-plugin</artifactId>
<version>1.3.2</version>
<dependencies>
<dependency>
<groupId>...</groupId>
<artifactId>...</artifactId>
<version>...</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>process-ace-annotations</id>
<goals>
<goal>process-ace-annotations</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
...
</project>
The dependency coordinates in the above example should be pointing to the Maven artifact where you declared your ACE type adapters. Note that this is required even for the case when your type adapters are located in the artifact where you are using the ACE annotations plugin.
The generated type would then look as follows:
{
"system": {
"contentType": "aceTypeDefinitionContent"
},
"aspects": {
"aceTypeDefinition": {
"name": "myData",
"fields": {
"duration": "string"
}
}
}
}
The same type adapter implementation is then used in combination with the ACE Java Client when reading such aspects
using the Java beans as long as you have the type adapters on the classpath of the client.