Structural multi inheritance - fieldenms/tg GitHub Wiki
This document describes the feature of structural multi-inheritance in TG.
The object model of the Java language is limited to single inheritance. A class may have at most one superclass.
In the context of TG, this means that an entity type may extend at most one other entity type, in the sense of Java's extends
.
Single inheritance in Java presents a limitation, which in practice makes modelling with Synthetic Entities laborious in cases where more than one entity type is used for model construction.
Synthetic entities are underpinned by an EQL model. As with any kind of entities, properties of synthetic entities define their structure, and the EQL model yields data into this structure.
In the most general case, it is required to explicitly define all the necessary properties for a synthetic entity and to make sure that the underlying EQL model yields values for all those properties. Properties can be omitted in yields, but those that are yielded must be present.
If a synthetic entity type extends a persistent one (e.g. ReWorkActivity
extends WorkActivity
), the structure of the synthetic type inherits all properties from the persistent type.
And so, there is no need to declare those properties explicitly, and EQL provides a convenient operation yieldAll()
, which tells the EQL engine to yield all inherited properties.
This is a simple example of automatic structure definition, where properties are inherited, and thus are present at the level of the Java class that defines the Synthetic Entity.
To overcome the limitation of single inheritance in Java, TG provides an annotation processor that supports structural multi-inheritance. This kind of inheritance enables the combination of structures from multiple entity types into one. The resulting entity type, however, is not a subtype of those entity types -- only the structure is inherited.
The following running example is used:
class Building extends AbstractEntity<DynamicEntityKey> {
@IsProperty
@CompositeKeyMember(1)
@Title("Building Number")
@MapTo
String number;
@IsProperty
@MapTo
@Title(value = "Status", desc = "Status of the Building.")
String status;
@IsProperty
@Title("Address")
@MapTo
String address;
@IsProperty
@MapTo
Date constructionDate;
}
class Equipment extends AbstractEntity<DynamicEntityKey> {
@IsProperty
@CompositeKeyMember(1)
@Title("Equipment Number")
@MapTo
String number;
@IsProperty
@MapTo
@Title(value = "Status", desc = "Status of the Equipment.")
EquipmentStatus status;
}
@EntityTitle(value = ReAsset_Spec.ENTITY_TITLE, desc = ReAsset_Spec.ENTITY_DESC)
@KeyTitle(value = ReAsset_Spec.ENTITY_TITLE, desc = ReAsset_Spec.ENTITY_DESC)
@KeyType(DynamicEntityType.class)
@Extends(name = "ReAsset",
parentToken = AssetModuleToken.class,
value = {
@Entity(value = Equipment.class, exclude = {"status"}),
@Entity(value = Building.class, exclude = {"status", "constructionDate"})
})
abstract class ReAsset_Spec extends ActivatableAbstractEntity<DynamicEntityKey> {
public static final String ENTITY_TITLE = "Asset";
public static final String ENTITY_DESC = "Assets belongs to one of the categories: Equipment or Building.";
@AutoYield
@IsProperty
@Title("Asset Number")
@CompositeKeyMember(1)
String number;
@IsProperty
@MapTo
@Title(value = "Status", desc = "Status of the Asset.")
String status;
private static <E extends AbstractEntity<?>> ISubsequentCompletedAndYielded<E>
modelFor(final Class<? extends AbstractEntity<?>> type, final IFromAlias<E> source)
{
final ISubsequentCompletedAndYielded<E> part0;
if (type == Building.class) {
part0 = source.yield().prop(Building_.status()).as(ReAsset_.status());
}
else if (type == Equipment.class) {
part0 = source.yield().prop(Equipment_.status().key()).as(ReAsset_.status());
}
else {
throw new InvalidStateException();
}
return part0;
}
}
@SpecifiedBy(ReAsset_Spec.class)
@CompanionIsGenerated
class ReAsset extends ReAsset_Spec {
private static List<EntityResultQueryModel> models_;
@IsProperty
@Title("Address")
String address;
}
ReAsset_Spec
is a multi-inheritance specification entity type, or spec-entity for short.
@Extends
specifies the entity types whose structure should be inherited.
The result of processing this declaration is a generated synthetic entity type ReAsset
, whose name is determined by @Extends.name
.
If the resultant synthetic entity is expected to have activatable nature, it is important for the spec entity to extend ActivatableAbstractEntity
.
If not all extended entities have activatable nature, it is required to declare property active
explicitly and implement modelFor
for yielding active
for all extended entities (active
would need to be excluded from inheritance for activatable entities).
As per the established practice, it also recommended to declare ENTITY_TITLE
and ENTITY_DESC
, and use them in annotations @KeyTitle
and @EntityTile
.
As a prerequisite, it is important to understand the concept of property presence.
Property p
is present in entity type T
iff it is declared by T
or is present in the superclass of T
.
For each entity type specified in @Extends
, the following properties are inherited:
-
All persistent properties but for the exceptions described below.
-
These properties are never inherited:
key
,version
,AbstractPersistentEntity.created*
properties,AbstractPersistentEntity.lastUpdated*
properties,refCount
.
-
desc
is inherited iff the spec-entity type hasdesc
(i.e.,@DescTitle
is present ordesc
is declared explicitly). -
Properties that are excluded with
@Extends.Entity.exclude
are not inherited.
All inherited properties are declared in the generated entity type, except for:
-
Properties present in the spec-entity type that are annotated with
@AutoYield
.This avoids declaring an equivalent property in the generated entity type, when it can be inherited from the spec-entity type via Java
extends
. -
Property
desc
, which is conditionally inherited from the spec-entity type.Be aware of modelling scenarios where the spec-entity type has
desc
, but none of the extended types do (ordesc
is excluded for all of them). The resulting generated type will containdesc
, but the generated EQL model will contain no yields fordesc
. In such a case, the developer should adjust the definition.
Entity types generated by the processor are synthetic and thus require an EQL model.
As can be seen from the definition of ReAsset
, its EQL model is not statically assigned.
Instead, it is generated at runtime, using the information from ReAsset_Spec
.
The resulting EQL model is a union of sub-models for entity types in @Extends
.
Therefore, the resulting EQL model has a rectangular shape.
If a property is inherited, it will be yielded in the generated EQL model.
E.g., ReAsset.address
is inherited from Building.address
, but not from Equipment.address
.
The generated EQL model will contain the following:
select(Building.class)
...
yield().prop("address").as().prop("address")
...
select(Equipment.class)
...
yield().val(null).as().prop("address")
...
Since address
is absent in Equipment
, it has to be filled with a default value, which is null
for all property types except for boolean
, for which false
is used.
To provide control over the resulting EQL model, a spec-entity type may define the following static method:
static <E extends AbstractEntity<?>> ISubsequentCompletedAndYielded<E> modelFor(final Class<E> type, final IFromAlias<E> source)
This method will then be called during EQL model generation for each entity type in @Extends
.
E.g., for ReAsset_Spec
this method will be called as modelFor(Building.class, select(Building.class))
and similary for Equipment
.
A spec-entity type may declare its own properties, which should then be yielded in the modelFor
method.
E.g., ReAsset_Spec
declares status : String
to reconcile Building.status
and Equipment.status
that have different types, with corresponding yields in the modelFor
method.
After modelFor
has returned, the generation logic will proceed to yield inherited properties.
Properties present in the spec-entity type and annotated with @AutoYield
will be automatically yielded during EQL model generation.
E.g., ReAsset_Spec.number
hides Building.number
and Equipment.number
, and the generated EQL model will contain the following:
select(Building.class)
...
yield().prop("number").as().prop("number")
...
select(Equipment.class)
...
yield().prop("number").as().prop("number")
...
If there was another extended entity, such as Vehicle
, and it did not have number
, then null
would be yielded for its sub-model:
select(Vehicle.class)
...
yield().val(null).as().prop("number")
...
It is an error if a property annotated with @AutoYield
cannot be yielded from any of the types in @Extends
(either due to exclusion or non-existence).
Companion object types for generated entity types, such as ReAsset
, are generated at runtime, which is indicated by @CompanionIsGenerated
.
Therefore, ReAssetCo
need not exist in source code.
Security tokens are also generated at runtime.
Specifically, tokens READ
and READ_MODEL
are generated for generated entity types, such as ReAsset
.
The parent token type of generated tokens can be specified with @Extends.parentToken
.
A generated entity type will always declare an additional property annotated with @EntityTypeCarrier
.
The name of this property can be controlled with @Extends.entityTypeCarrierProperty
.
The purpose of this property is to support navigation between Compound Masters of different entity types.
During EQL model generation, a yield for this property will be inserted, which will use the type of a corresponding type in @Extends
for the yielded value.
-
Specification entity types should be declared abstract, since their sole purpose is to provide input to the generator -- they will not be considered "domain types", hence will not have corresponding companion objects.
This will also have the desired effect of excluding specification entity types from being registered in the
ApplicationDomain
. If a specification entity type cannot be declared abstract, it should be annotated with@SkipEntityRegistration
. -
Specification entity types may have an arbitrary key type, depending on the nature and structure of their supertypes.
The recommended practice is to declare an optional composite key member for each supertype, and specify corresponding yields for EQL sub-models. This will improve the user experience of using search criteria -- each key member could be used for autocompletion.
However, if there is a natural candidate for a key that is shared by all extended entities, it is best to use that key for the synthetic entity. For example, if
Building
andEquipment
are uniquely identified by their propertynumber: String
, then simply declarenumber
as a composite key member in the spec with@AutoYield
and useDynamicEntityKey
as the key type parameter.In situations where the key has no meaning, the key type should be declared as
NoKey
.If there is a clear set of common properties in the extended entity types that could be used as a key, a composite key should be declared with the corresponding members defined explicitly, with the EQL submodels for each extended entity type yielding their values. For example, entity
Equipment
might have single composite key membername: String
, while entityVehicle
has key membernumber: String
. The spec-entity type could then declare a single composite key membernumber: String
, and yieldname
asnumber
in the EQL submodel forEquipment
andnumber
asnumber
in the EQL submodel forVehicle
.
The processor is packaged alongside other processors in platform-annotation-processors
.
To enable the processor, it must be specified in the pom.xml
of the *-pojo-bl
project:
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.11.0</version>
<configuration>
<showWarnings>true</showWarnings>
<!-- source output directory -->
<generatedSourcesDirectory>${project.build.directory}/generated-sources/</generatedSourcesDirectory>
<annotationProcessorPaths>
<path>
<groupId>fielden</groupId>
<artifactId>platform-annotation-processors</artifactId>
<version>${platform.version}</version>
<classifier>uber</classifier>
</path>
</annotationProcessorPaths>
<annotationProcessors>
<annotationProcessor>ua.com.fielden.platform.processors.minheritance.MultiInheritanceProcessor</annotationProcessor>
<!-- Other processors follow -->
</annotationProcessors>
</configuration>
</plugin>