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::\" and do not need to be registered. If the field type is any, primitive values will be interpreted as the corresponding Dyn type.

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::\". If the element type is any, primitive values will be interpreted as the corresponding Dyn type.

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.