Data Objects

This document is referring to a past Scout release. Please click here for the recent version.

Data objects are Scout beans, which are used as data transfer objects for synchronous REST and asynchronous MOM interfaces. Furthermore, they can be used as domain objects within business logic.

Data Object Definition

A data object extends the DoEntity base class and declares each attribute as a single accessor method. Attributes of two kinds are available:

  • Value attribute of type T

  • List attribute of type List<T>

The name of the accessor method defines the attribute name. The return value of the accessor method defines the attribute type.

Listing 1. Example: ExampleEntityDo
@TypeName("lorem.ExampleEntity")
@TypeVersion(Lorem_1_2_0.class)
public class ExampleEntityDo extends DoEntity {

  public DoValue<String> name() { (1)
    return doValue("name");
  }

  public DoList<Integer> values() { (2)
    return doList("values");
  }

  /* **************************************************************************
   * GENERATED CONVENIENCE METHODS
   * *************************************************************************/

  @Generated("DoConvenienceMethodsGenerator")
  public ExampleEntityDo withName(String name) {
    name().set(name);
    return this;
  }

  @Generated("DoConvenienceMethodsGenerator")
  public String getName() {
    return name().get();
  }

  @Generated("DoConvenienceMethodsGenerator")
  public ExampleEntityDo withValues(Collection<? extends Integer> values) {
    values().updateAll(values);
    return this;
  }

  @Generated("DoConvenienceMethodsGenerator")
  public ExampleEntityDo withValues(Integer... values) {
    values().updateAll(values);
    return this;
  }

  @Generated("DoConvenienceMethodsGenerator")
  public List<Integer> getValues() {
    return values().get();
  }
}
1 Example attribute of type String
2 Example attribute of type List<Integer>

For convenience reasons when working with the data objects it is recommended to add a getter and a with (e.g. setter) method. Using the convenience with methods, new data objects can be created with fluent-style API:

Listing 2. Example: Create ExampleEntityDo instance
    ExampleEntityDo entity = BEANS.get(ExampleEntityDo.class)
        .withName("Example")
        .withValues(1, 2, 3, 4, 5);

Marshalling

Using the IDataObjectMapper interface a data object can be converted from and to its string representation. The marshalling strategy is generic and replaceable. The Scout platform defines the IDataObjectMapper interface, at runtime a Scout bean implementing the interface must be available.

The Scout module org.eclipse.scout.rt.jackson provides a default implementation serializing data objects from and to JSON using the Jackson library.

Listing 3. Example: Serialize ExampleEntityDo
    String string = BEANS.get(IDataObjectMapper.class).writeValue(entity);

The data object ExampleEntityDo serialized to JSON:

Listing 4. Example: ExampleEntityDo as JSON
{
  "_type" : "lorem.ExampleEntity",
  "_typeVersion": "lorem-1.2.0",
  "name" : "example",
  "values" : [1,2,3,4,5]
}
Listing 5. Example: Deserialize ExampleEntityDo
    ExampleEntityDo marhalled = BEANS.get(IDataObjectMapper.class)
        .readValue(string, ExampleEntityDo.class);

Type Name

A data object is annotated with a logical type name using the @TypeName annotation.

Declaring a logical type name using the @TypeName annotation for each data object is mandatory.

The annotation value is added to the serialized JSON object as top-level _type property. Using the type property the data object marshaller is able to find and instantiate the matching data object class, without having to rely on a fully classified class name. It avoids a 1:1 dependency between the serialized JSON String and the fully classified class name. A stable type name is required in order to be able to change the data object structure without breaking the API.

Type Version

A data object may be annotated with a type version using the @TypeVersion annotation. The type version represents the version of the structure of the data object and not the version of the data within the data object. The type version value should be incremented, each time, the data object class is modified (add/remove/rename attributes). If a version is required for versioning the values of a data object, consider add a version attribute, incrementing its value, every time a value of the data object is modified.

The annotation value is added to the serialized JSON object as top-level _typeVersion property. The serialized _typeVersion value is not deserialized into an attribute, since the deserializer creates a concrete data object class at runtime, having the @TypeVersion annotation providing the type version value.

Declaring a logical type version using the `@TypeVersion`annotation is highly recommended if a data object is persisted as JSON document to a file or database.

Namespace and ITypeVersion

A namespace (implementation of INamespace) represents a container for data objects. Each data object must have a unique type name within a namespace. Scout has its own namespace (with ID scout), your project should use an own one.

Listing 6. Example: LoremNamespace
public final class LoremNamespace implements INamespace {

  public static final String ID = "lorem";
  public static final double ORDER = 9000;

  @Override
  public String getId() {
    return ID;
  }

  @Override
  public double getOrder() {
    return ORDER;
  }
}

A class implementing ITypeVersion is used within the @TypeVersion annotation. Several type versions for one namespace may be bundled in a container class.

There are a few different constructors provided by AbstractTypeVersion that simplify the definition of such a type version. The default constructor extracts the namespace and version based on the class name.

Listing 7. Example: LoremTypeVersions
public final class LoremTypeVersions {

  private LoremTypeVersions() {
  }

  public static final class Lorem_1_0_0 extends AbstractTypeVersion {
  }

  public static final class Lorem_1_2_0 extends AbstractTypeVersion {
  }
}

Signature Test

AbstractDataObjectSignatureTest provides an abstract implementation of a test that creates a signature of all data object annotated with a type version including additional signatures (e.g. referenced IEnum with their values). A signature test enables to detect changes in data object that might need a migration. Each module containing data objects with type version annotation should implement a data object signature test.

Listing 8. Example: DocsSnippetsDataObjectSignatureTest
public class DocsSnippetsDataObjectSignatureTest extends AbstractDataObjectSignatureTest {

  @Override
  protected String getFilenamePrefix() {
    return "docs-snippets";
  }

  @Override
  protected String getPackageNamePrefix() {
    return "org.eclipse.scout.docs.snippets";
  }
}

Data Object Naming Convention

Scout objects use the following naming conventions:

  • A data object class should use the `Do' suffix.

  • The value of the @TypeName annotation corresponds to the simple class name without Do suffix

    • A namespace prefix (separated by a dot) is recommended in order to avoid duplicated type names across different modules (e.g. scout.Bookmark, helloworld.MyDataObject)

Attribute Name

The default attribute name within the serialized string corresponds to the name of the attribute accessor method defined in the data object. To use a custom attribute name within the serialized string, the attribute accessor method can be annotated by @AttributeName providing the custom attribute name.

Listing 9. Example: Custom Attribute Name
  @AttributeName("myCustomName")
  public DoValue<String> name() {
    return doValue("myCustomName"); (1)
  }
1 Important: The annotation value must be equals to the string constant used for the doValue() or doList() attribute declaration.
Listing 10. Example: Custom Attribute Name as JSON
{
  "_type" : "CustomAttributeNameEntity",
  "myCustomName" : "example"
}

Attribute Format

Using the ValueFormat annotation a data type dependent format string may be provided, which is used for the marshalling.

Listing 11. Example: Custom Attribute Format
  @ValueFormat(pattern = IValueFormatConstants.DATE_PATTERN)
  public DoValue<Date> date() {
    return doValue("date");
  }

The IValueFormatConstants interface declares a set of default format pattern constants.

Attributes with type java.util.Date accept the format pattern specified by SimpleDateFormat class (see https://docs.oracle.com/javase/8/docs/api/java/text/SimpleDateFormat.html)

Ignoring an Attribute

The @JsonIgnore annotation included in the Jackson library is currently not supported for data objects. To ignore an attribute when serializing a data object, the attribute must be removed from the data object by either not setting a value for the desired attribute or by explicitly removing the attribute before a data object is serialized:

Listing 12. Example: Remove an attribute from a data object
    ExampleEntityDo entity = BEANS.get(ExampleEntityDo.class)
        .withName("Example")
        .withValues(1, 2, 3, 4, 5);

    // remove by attribute accessor method reference
    entity.remove(entity::name);

    // remove by attribute node
    entity.remove(entity.name());

    // remove by attribute name
    entity.remove(entity.name().getAttributeName());

    // remove by attribute name raw
    entity.remove("name");

Handling of DoEntity Attributes

Instead of data objects, a REST or MOM interface could be built using simple plain old Java objects (POJOs). Compared to POJOs a Scout data object offers additional support and convenience when working with attributes.

A JSON attribute may have three different states:

  • Attribute available with a value

  • Attribute available with value null

  • Attribute not available

These three states cannot be represented with a POJO object which is based on a single variable with a pair of getter/setter. In order to differ between value not available and value is null, a wrapper type is required, which beside the value stores the information, if the attribute is available. Scout data objects solve this issue: Data objects internally use a Map<String, DoNode<?>> where the abstract DoNode at runtime is represented by a DoValue<T> or a DoList<T> object instance wrapping the value.

Access Data Object Attributes

  • Value: DoNode.get() returns the (wrapped) value of the attribute

Listing 13. Example: ExampleEntityDo Access the Attribute Value
    ExampleEntityDo entity = BEANS.get(ExampleEntityDo.class)
        .withName("Example")
        .withValues(1, 2, 3, 4, 5);

    // access using attribute accessor
    String name1 = entity.name().get();
    List<Integer> values1 = entity.values().get();

    // access using generated attribute getter
    String name2 = entity.getName();
    List<Integer> values2 = entity.getValues();
  • Existence: Using the DoNode.exists() method, each attribute may be checked for existence

Listing 14. Example: ExampleEntityDo Attribute Existence
    // check existence of attribute
    boolean hasName = entity.name().exists();

Abstract Data Objects & Polymorphism

A simple data objects is implemented by subclassing the DoEntity class.

For a complex hierarchy of data objects the base class may be abstract and extend the DoEntity class, further subclasses extend the abstract base class. The abstract base data object class does not need to specify a @TypeName annotation since there are no instances of the abstract class which are serialized or deserialized directly. Each non-abstract subclass must specify a unique @TypeName annotation value.

Listing 15. Example: Abstract class AbstractExampleEntityDo with one attribute
public abstract class AbstractExampleEntityDo extends DoEntity {
  public DoValue<String> name() {
    return doValue("name");
  }
Listing 16. Example: Subclass 1 with an additional attribute and a unique type name
@TypeName("ExampleEntity1")
public class ExampleEntity1Do extends AbstractExampleEntityDo {
  public DoValue<String> name1Ex() {
    return doValue("name1Ex");
  }

  /* **************************************************************************
   * GENERATED CONVENIENCE METHODS
   * *************************************************************************/

  @Generated("DoConvenienceMethodsGenerator")
  public ExampleEntity1Do withName1Ex(String name1Ex) {
    name1Ex().set(name1Ex);
    return this;
  }

  @Generated("DoConvenienceMethodsGenerator")
  public String getName1Ex() {
    return name1Ex().get();
  }

  @Override
  @Generated("DoConvenienceMethodsGenerator")
  public ExampleEntity1Do withName(String name) {
    name().set(name);
    return this;
  }
}
Listing 17. Example: Subclass 2 with an additional attribute and a unique type name
@TypeName("ExampleEntity2")
public class ExampleEntity2Do extends AbstractExampleEntityDo {
  public DoValue<String> name2Ex() {
    return doValue("name2Ex");
  }

  /* **************************************************************************
   * GENERATED CONVENIENCE METHODS
   * *************************************************************************/

  @Generated("DoConvenienceMethodsGenerator")
  public ExampleEntity2Do withName2Ex(String name2Ex) {
    name2Ex().set(name2Ex);
    return this;
  }

  @Generated("DoConvenienceMethodsGenerator")
  public String getName2Ex() {
    return name2Ex().get();
  }

  @Override
  @Generated("DoConvenienceMethodsGenerator")
  public ExampleEntity2Do withName(String name) {
    name().set(name);
    return this;
  }
}
Listing 18. Example: Class with attributes of type AbstractExampleEntityDo
public class ExampleDoEntityListDo extends DoEntity {
  public DoList<AbstractExampleEntityDo> listAttribute() {
    return doList("listAttribute");
  }

  public DoValue<AbstractExampleEntityDo> singleAttribute() {
    return doValue("singleAttribute");
  }

  /* **************************************************************************
   * GENERATED CONVENIENCE METHODS
   * *************************************************************************/

  @Generated("DoConvenienceMethodsGenerator")
  public ExampleDoEntityListDo withListAttribute(Collection<? extends AbstractExampleEntityDo> listAttribute) {
    listAttribute().updateAll(listAttribute);
    return this;
  }

  @Generated("DoConvenienceMethodsGenerator")
  public ExampleDoEntityListDo withListAttribute(AbstractExampleEntityDo... listAttribute) {
    listAttribute().updateAll(listAttribute);
    return this;
  }

  @Generated("DoConvenienceMethodsGenerator")
  public List<AbstractExampleEntityDo> getListAttribute() {
    return listAttribute().get();
  }

  @Generated("DoConvenienceMethodsGenerator")
  public ExampleDoEntityListDo withSingleAttribute(AbstractExampleEntityDo singleAttribute) {
    singleAttribute().set(singleAttribute);
    return this;
  }

  @Generated("DoConvenienceMethodsGenerator")
  public AbstractExampleEntityDo getSingleAttribute() {
    return singleAttribute().get();
  }
}
Listing 19. Example: Using the ExampleDoEntityListDo class with different kinds of AbstractExampleEntityDo sub classes
    ExampleDoEntityListDo entity = BEANS.get(ExampleDoEntityListDo.class);
    entity.withListAttribute(
        BEANS.get(ExampleEntity1Do.class)
            .withName1Ex("one-ex")
            .withName("one"),
        BEANS.get(ExampleEntity2Do.class)
            .withName2Ex("two-ex")
            .withName("two"));

    entity.withSingleAttribute(
        BEANS.get(ExampleEntity1Do.class)
            .withName1Ex("single-one-ex")
            .withName("single-one"));

If an instance of ExampleDoEntityListDo is serialized, each attribute is serialized using its runtime data type, adding an appropriate _type attribute to each serialized object. Therefore, the deserializer knows which concrete class to instantiate while deserializing the JSON document. This mechanism is used for simple value properties and list value properties. To each object which is part of a list value property the _type property is added to support polymorphism within single elements of a list.

Listing 20. Example: ExampleDoEntityListDo as JSON
{
  "_type" : "ExampleDoEntityListDo",
  "listAttribute" : [ {
    "_type" : "ExampleEntity1",
    "name" : "one",
    "name1Ex" : "one-ex"
  }, {
    "_type" : "ExampleEntity2",
    "name" : "two",
    "name2Ex" : "two-ex"
  } ],
  "singleAttribute" : {
    "_type" : "ExampleEntity1",
    "name" : "single-one",
    "name1Ex" : "single-one-ex"
  }
}

Rename an attribute of a data object in a subclass

To rename a data object attribute in a subclass, override the attribute accessor method and annotate it with @AttributeName using the new attribute name as value. Additionally the overridden method must call the doValue() method providing the new attribute name as argument.

Listing 21. Example: Rename attribute in a data object subclass
@TypeName("ExampleEntityEx")
public class ExampleEntityExDo extends ExampleEntityDo {

  @Override
  @AttributeName("nameEx")
  public DoValue<String> name() { (1)
    return doValue("nameEx");
  }

  /* **************************************************************************
   * GENERATED CONVENIENCE METHODS
   * *************************************************************************/

  @Override
  @Generated("DoConvenienceMethodsGenerator")
  public ExampleEntityExDo withName(String name) {
    name().set(name);
    return this;
  }

  @Override
  @Generated("DoConvenienceMethodsGenerator")
  public ExampleEntityExDo withValues(Collection<? extends Integer> values) {
    values().updateAll(values);
    return this;
  }

  @Override
  @Generated("DoConvenienceMethodsGenerator")
  public ExampleEntityExDo withValues(Integer... values) {
    values().updateAll(values);
    return this;
  }
}
1 Rename name attribute of superclass to nameEx

Interfaces to Data Objects

Use the basic data object interface IDoEntity to model a data object hierarchy with own base interfaces and a set of implementing classes.

Interfaces extending IDataObject do not need a @TypeName annotation, since they are never directly serialized or deserialized.

The interfaces may be used as types for attributes within a data object. At runtime the concrete classes implementing the interfaces are serialized and their @TypeName annotation value is used.

Equals and Hashcode

The Data Object base class DoEntity defines a generic equals() and hashCode() implementation considering all attributes of a data object for equality. A data object is equals to another data object, if the Java class of both data objects is identical and the attribute maps (including their nested values) of both data objects are equals.

For futher details see:

  • org.eclipse.scout.rt.dataobject.DoEntity.equals(Object)

  • org.eclipse.scout.rt.dataobject.DoNode.equals(Object)

Generic DoEntity

An instance of the DoEntity class can represent any kind of JSON document. If the JSON document contains no type attributes or no matching data object class exists at runtime, the JSON document is deserialized into a raw DoEntity instance holding all attributes. To access the attributes of the data object a set of generic getter methods may be used by specifying the attribute name. A generic JSON document is deserialized into a generic tree-like structure of nested DoEntity instances. If the serialized JSON document contains a _type and/or _typeVersion attribute, the attribute and its value is added as attribute to the generic raw DoEntity instance.

Listing 22. Example: ExampleEntityDo accessing attribute "by name"
    ExampleEntityDo entity = BEANS.get(ExampleEntityDo.class)
        .withName("Example")
        .withValues(1, 2, 3, 4, 5);

    // access name attribute by its attribute name
    Object name1 = entity.get("name"); (1)
    String name2 = entity.get("name", String.class); (2)
    String name3 = entity.getString("name"); (3)

    // access values attribute by its attribute name
    List<Object> values1 = entity.getList("values"); (4)
    List<String> values2 = entity.getList("values", String.class); (5)
    List<String> values3 = entity.getStringList("values"); (6)

    // optional list attribute access by its attribute name
    Optional<List<Object>> values4 = entity.optList("values"); (7)
    Optional<List<String>> values5 = entity.optList("values", String.class); (8)
1 Accessing value attribute, default type is Object
2 Accessing value attribute, specify the type as class object if known
3 Accessing value attribute, convenience method for a set of common types
4 Accessing list attribute, default type is Object
5 Accessing list attribute, specify the type as class object if known
6 Accessing list attribute, convenience method for a set of common types
7 Accessing optional list attribute, default type is Object
8 Accessing optional list attribute, specify the type as class object if known
If a list attribute is not available, using one of the getList(…​) getters adds an empty list as attribute value into the entity and returns the list. Use optList(…​) in order to get an optionally available list without adding a new empty list as attribute.

Apart of the convenience methods available directly within the DoEntity class, the DataObjectHelper class contains a set of further convenience methods to access raw values of a data object.

Accessing number values

If a generic JSON document is deserialized to a DoEntity class without using a subclass specifying the attribute types, all attributes of type JSON number are deserialized into the smallest possible Java type. For instance the number value 42 is deserialized into an Integer value, a large number may be deserialized into a BigInteger or BigDecimal if it is a floating point value. Using the convenience method DoEntity.getDecimal(…​) each number attribute is converted automatically into a BigDecimal instance on access.

If a generic JSON document is deserialized, only a set of basic Java types like String, Number, Double are supported. Every JSON object is deserialized into a (nested) DoEntity structure, which internally is represented by a nested structure of Map<String, Object>.

Map of objects

To build map-like a data object (corresponds to Map<String, T>), the DoMapEntity<T> base class may be used.

Listing 23. Example: Map<String, ExampleEntityDo> as ExampleMapEntityDo data object
@TypeName("ExampleMapEntity")
public class ExampleMapEntityDo extends DoMapEntity<ExampleEntityDo> {
}

The example JSON document of ExampleMapEntityDo instance with two elements:

Listing 24. Example: ExampleMapEntityDo with two elements:
{
  "_type" : "ExampleMapEntity",
  "mapAttribute1" : {
    "_type" : "ExampleEntity",
    "name" : "example-1",
    "values" : [1,2,3,4,5]
  },
    "mapAttribute2" : {
    "_type" : "ExampleEntity",
    "name" : "example-2",
    "values" : [6,7,8,9]
  }
}
Listing 25. Example: Accessing attributes
    ExampleMapEntityDo mapEntity = BEANS.get(ExampleMapEntityDo.class);
    mapEntity.put("mapAttribute1",
        BEANS.get(ExampleEntityDo.class)
            .withName("Example")
            .withValues(1, 2, 3, 4, 5));
    mapEntity.put("mapAttribute2",
        BEANS.get(ExampleEntityDo.class)
            .withName("Example")
            .withValues(6, 7, 8, 9));

    ExampleEntityDo attr1 = mapEntity.get("mapAttribute1"); (1)
    Map<String, ExampleEntityDo> allAttributes = mapEntity.all(); (2)
1 Accessing attribute using get method returns the attribute of declared type T
2 Accessing all attributes using all method returns a map with all attributes of type T
A DoMapEntity<T> subclass may declare custom attributes of another type than T (e.g. an integer size attribute). If attributes of other types are used, using the all method results in a ClassCastException since not all attributes are of the same type any longer.

IDataObject Interface - Data Objects with unknown structure

According to the JSON specification a JSON document at top level may contain a object or an array. If a JSON string of unknown structure is deserialized, the common super interface IDataObject may be used as target type for the call to the deserializer:

Listing 26. Example: Deserialize a JSON document with unknown structure
    String json = "<any JSON content>";
    IDataObjectMapper mapper = BEANS.get(IDataObjectMapper.class);
    IDataObject dataObject = mapper.readValue(json, IDataObject.class);
    if (dataObject instanceof IDoEntity) {
      // handle object content
    }
    else if (dataObject instanceof DoList) {
      // handle array content
    }

Ad-Hoc Data Objects

The DoEntityBuilder may be used to build ad-hoc data objects without a concrete Java class defining its attributes.

Listing 27. Example: DoEntityBuilder
    IDoEntity entity = BEANS.get(DoEntityBuilder.class)
        .put("attr1", "foo")
        .put("attr2", "bar")
        .putList("listAttr", 1, 2, 3)
        .build(); (1)

    String entityString = BEANS.get(DoEntityBuilder.class)
        .put("attr1", "foo")
        .put("attr2", "bar")
        .putList("listAttr", 1, 2, 3)
        .buildString(); (2)
1 Builder for a DoEntity object
2 Builder for the string representation of a DoEntity objects

Maven Dependencies

The Scout data object implementation does not reference any specific Java serialization library or framework. The basic building blocs of data objects are part of the Scout platform and to not reference any thirdparty libraries. At runtime an implementation of the IDataObjectMapper interface must be provided. The Scout default implementation based on the JSON library Jackson is provided by adding a maven dependency to the module org.eclipse.scout.rt.jackson. The dependency to this module must be added in the top-level .dev/.app module. A dependency within the program code is not necessaray as long as no specific Jackson features should be used within the application code.

Data Object Inventory

The class org.eclipse.scout.rt.dataobject.DataObjectInventory provides access to all available data objects at runtime. For each data object all available attributes and their properties (name, type, accessor method and format pattern) are available:

Listing 28. Example: Accessing data object inventory
    Map<String, DataObjectAttributeDescriptor> attributes =
        BEANS.get(DataObjectInventory.class).getAttributesDescription(ExampleEntityDo.class);
    attributes.forEach(
        (key, value) -> System.out.println("Attribute " + key + " type " + value.getType()));

Apart from attribute descriptions, the inventory provides access to type name and type version of each data object class.

Extending with custom serializer and deserializer

The application scoped beans DataObjectSerializers resp. DataObjectDeserializers define the available serializer and deserializer classes used to marshal the data objects. Own custom serializer and deserializer implementations can be added by replacing the corresponding base class and register its own custom serializer or deserializer.

Enumerations within Data Objects

Implementations of org.eclipse.scout.rt.dataobject.enumeration.IEnum add a stringValue() method to each enumeration value, guaranteeing a constant, fixed string value for each enumeration value. An arbitrary Java enum may be used within a data object, but does not guarantee a stable serialized value, if an enumeration value is changed in future.

Additionally implementations of IEnum can be annotated with @EnumName to support being referenced in a data object signature test.

All instances of IEnum may be used within data objects and are automatically serialized to their JSON string value representation and deserialized back to the correct Java class instance.

The default resolver mechanism for IEnum (see org.eclipse.scout.rt.dataobject.enumeration.EnumResolver) matches the given string with the available string values in the current enumeration implementation to look up the matching enumeration value. An optional static resolve() method handles the resolve of a given string value into the correct enumeration value allowing to support even string values, whose enumeration values where changed or deleted.

Listing 29. Example IEnum implementation
  @EnumName("scout.ExampleEnum")
  public enum ExampleEnum implements IEnum {

    ONE("one"),
    TWO("two"),
    THREE("three");

    private final String m_stringValue;

    ExampleEnum(String stringValue) {
      m_stringValue = stringValue;
    }

    @Override
    public String stringValue() {
      return m_stringValue;
    }

    public static ExampleEnum resolve(String value) { (1)
      // custom null handling
      if (value == null) {
        return null;
      }
      switch (value) {
        // custom handling of old values (assuming 'old' was used in earlier revisions)
        case "one":
          return ONE;
        case "two":
          return TWO;
        case "three":
          return THREE;
        case "four":
          return THREE;
        default:
          // custom handling of unknown values
          throw new AssertionException("unsupported status value '{}'", value);
      }
    }

  }
1 Optional resolve method

Typed IDs within Data Objects

Implementations of org.eclipse.scout.rt.dataobject.id.IId<WRAPPED_TYPE> interface wrap an arbitrary value adding a concrete Java type to a scalar value. E.g. the key of an example entity which technically is a UUID becomes an instance of the ExampleId class.

All instances of IId may be used within data objects and are automatically serialized to their JSON string representation of the wrapped value and deserialized back to the correct Java class instance.

An exampleId instance may then be used as type-safe parameter for further referencing a given example entity record, for instance as attribute value within a data object.

Listing 30. Example ID implementation wrapping a UUID
  @IdTypeName("scout.ExampleId")
  public static final class ExampleId extends AbstractUuId {
    private static final long serialVersionUID = 1L;

    public static ExampleId create() {
      return new ExampleId(UUID.randomUUID());
    }

    public static ExampleId of(UUID id) {
      if (id == null) {
        return null;
      }
      return new ExampleId(id);
    }

    public static ExampleId of(String id) {
      if (id == null) {
        return null;
      }
      return new ExampleId(UUID.fromString(id));
    }

    private ExampleId(UUID id) {
      super(id);
    }
  }

Unit Testing

A set of utility methods for unit tests with data objects are provided within the DataObjectTestHelper class. Commonly used are a set of assert methods (e.g. assertEquals(Object expected, Object actual)) for testing data objects for (deep) equality.