Closed 01es closed 2 years ago
Some entities, such as User
, may contain properties that are of the same type as the owning entity. For example, here is one of the properties of an entity User
:
@IsProperty
@Title(value = "Base User", desc = "...")
@MapTo
@BeforeChange(@Handler(UserBaseOnUserValidator.class))
@AfterChange(UserBasedOnUserDefiner.class)
private User basedOnUser;
The generated meta-model reflects this property as such:
public final UserMetaModel basedOnUser;
and initializes it in the constructor like so:
this.basedOnUser = new UserMetaModel(joinPath(basedOnUser_));
The problem is that the UserMetaModel
object can't be instantiated, since the initial constructor call will never terminate, causing a StackOverflowError
:
java.lang.StackOverflowError
at ua.com.fielden.platform.security.user.meta.UserMetaModel.<init>(UserMetaModel.java:108)
at ua.com.fielden.platform.security.user.meta.UserMetaModel.<init>(UserMetaModel.java:114)
at ua.com.fielden.platform.security.user.meta.UserMetaModel.<init>(UserMetaModel.java:114)
at ua.com.fielden.platform.security.user.meta.UserMetaModel.<init>(UserMetaModel.java:114)
....
at ua.com.fielden.platform.security.user.meta.UserMetaModel.<init>(UserMetaModel.java:114)
The solution is to utilize lazy initialization of such properties in the generated meta-model class through the use of java.util.function.Supplier
. Consequently, instead of providing direct access to properties of the generated meta-model class, getter-like methods should be provided with equivalent names.
class UserMetaModel {
private static final String basedOnUser_ = "basedOnUser";
private Supplier<UserMetaModel> basedOnUser;
public UserMetaModel(String path) {
super(path);
this.basedOnUser = () -> {
UserMetaModel value = new UserMetaModel(joinPath(basedOnUser_));
basedOnUser = () -> value;
return value;
};
}
public UserMetaModel basedOnUser() {
return basedOnUser.get();
}
}
Cyclic reference during initiazliation of a meta-model class.
Suppose entity Person
has a property vehicle: Vehicle
and entity Vehicle
has a property owner: Person
. Then the following constructors would be generated:
class PersonMetaModel {
...
public PersonMetaModel(String path) {
super(path);
this.vehicle = new VehicleMetaModel(joinPath(vehicle_));
}
}
class VehicleMetaModel {
...
public VehicleMetaModel(String path) {
super(path);
this.owner = new PersonMetaModel(joinPath(owner_));
}
}
This leads to an infinite loop.
I see 2 possible solutions. In the process of meta-model generation we have to check if a property causes a cyclic reference, that is, whether a property is entity type and that entity has a property of current (generation target) entity type.
Example for Vehicle
and Person
:
Person.vehicle
inside the constructor, create a setter method setVehicle()
and call it in MetaModels
class:class MetaModels {
...
public static final PersonMetaModel Person = new PersonMetaModel().setVehicle();
}
User.basedOnUser
.
a. Combine this with above mentioned check for cyclic properties.
b. Do this for every property that is entity type.@homedirectory approach 2 with lazy initialisation should work well as the current situation is not really different from the original one. Consider employing lazy initialisation in all cases for entity-typed properties, regardless of whether they are recursively defined.
Issues caused by entity inheritance and the incremental compilation process of Eclipse.
Eclipse uses its own JDT core as the Java compiler providing incremental compilation. This leads to an issue when recompiling entity classes that have child entities classes, since the child classes are not automatically recompiled together with their parent. Therefore the annotation processor's environment consists of a sole parent class.
In order to resolve this issue whe have to recreate the same inheritance tree for the generated sources, i.e. ChildMetaModel extends ParentMetaModel
.
@homedirectory I think this is due to binary compatibility (https://docs.oracle.com/javase/specs/jls/se17/html/jls-13.html), which makes it possible for child classes (compiled) to remain unchanged if their parent changes (e.g., some parent class from a 3rd party library) -- something that completely escaped my mind when we were considering this aspect.
Please let me know if you would like to discuss the proposed by your approach to remedy this situation.
Description
Instead of relying on very unreliable and error prone (especially after renaming) string-based referencing of entity properties (including dot-notations for graph traversal) as part of various DSL calls such as in EQL, fetch and Web UI configurations, it is required to develop a support for an ad-hoc generation of a programming model that would provide a way for reliably reference entity properties. Compile time errors should be reported in cases where non-existing properties are referenced.
The implementation should analyse the domain entities and generate a set of classes with static members resembling the graph structure those entities. The developers should be equipped then to use those static member to travers the entity graph in the code editor. For example, typing something like
$model.WorkActivity.locationSubsystem.
should result in the autocompletion (code inside) of all properties of entityLocationSubsystem
, which is the type of propertylocationSubsystem
in entityWorkActivity
.There are two approaches to triggering of the property-model generation.
$model
..prop(...)
,.allOfProps(..)
,.anyOfProps(...)
and.props(...)
should accept values ofIConvertableToPath
. This contract has methodtoPath()
and is implemented byPropertyMetaModel
andEntityMetaModel
.FetchProvider
,fetch
and various Web UI APIs need to be enhanced to supportIConvertableToPath
as arguments.Expected outcome
This feature will further increase evolvability of TG-based applications whereby developers will be informed by the compiler of invalid references to entity properties.