MetaModels - fieldenms/tg GitHub Wiki
This document provides a few notes on configuring a TG-based application to use annotation processing to generate meta-models in Eclipse.
These examples will use TG Fleet - adjust as required for other projects.
In order to simplify the management of application projects, which use meta-models in Eclipse IDE, it is highly recommended to embrace the m2e
plugin instead of Maven to clean and generate Eclipse project files.
Effectively, the use of m2e
provides a way to retain some of the important configurations, which are required for generation of meta-models. This documentation shows the use of m2e
.
Entities declare properties which are reified using String
values for purposes such as definining a fetch model.
For example, entity Task
is declared with properties name
, desc
, detailingTask
, and the respective fetch provider could be declared like:
static final IFetchProvider<Task> FETCH_PROVIDER = EntityUtils.fetch(Task.class).with("name", "desc", "detailingTask", ...);
Any changes to the model, such as renaming of properties, would require the developer to manually adjust all String
-typed property references, which tends to be highly error-prone.
With the use of meta-models the job of finding references that need adjusting is delegated to the compiler, making this a simple process of following the compilation errors.
Meta-models themselves are represented by Java classes which are generated at compile time. Property references can be constructed by chaining the respective method calls on meta-model objects.
So instead of referring to property detailingTask
with string literal "detailingTask"
, you would use the Task
meta-model: Task_.detailingTask()
, which can be further converted to a String
if needed: Task_.detailingTask().toPath()
.
The .toPath()
method converts a meta-model (IConvertableToPath
) to String
, and is only required where IConvertableToPath
is not accepted.
A meta-model's class name is the same as its underlying entity, but with a MetaModel
suffix. All meta-models are listed in the generated container class MetaModels
which serves as an entry point to access them. Each meta-model can be accessed in 2 ways from MetaModels
:
- Through a static field that's named after the underlying entity with a
_
suffix (e.g.,MetaModels.Task_
represents theTaskMetaModel
). - Through a static method that accepts an alias (e.g.,
MetaModels.Task_("t")
). These are called aliased meta-models, and the paths they convert to are prefixed with the respective alias. For example,MetaModels.Task_("t").desc()
converts to"t.desc"
.
Additionally, a bare meta-model reference, such as Task_
, converts to "this"
. A bare aliased meta-model reference, such as Task_("t")
, converts to its alias ("t"
).
Using meta-models to reference properties enables auto-completion of property names and potentially compile-time checks on property validity (by leveraging the type system). It also supports dot notation, and provides ready access to comprehensive javadoc describing the properties.
Apart from a standard Java compiler, Eclipse provides a "batch compiler" that is capable of processing source files in batches.
This batch mode is triggered whenever the number of input source files exceeds a certain limit (by default set to 2000).
It is required to disable it by removing the limit, since there are known issues with annotation processing in batch mode.
To do so, navigate to your local Eclipse installation and modify the eclipse.ini
file by adding the following:
-DmaxCompiledUnitsAtOnce=0
Make sure that this line comes after -vmargs
.
It is necessary to install the annotation processor jar file, and to perform an initial configuration of the project. It is envisaged that most of the necessary configuration changes would be committed to GitHub, and thus available to anyone who clones or pulls the project.
-
It is generally recommended to show working sets at the top level - click the button in the Package Explorer toolbar with three vertical dots and select
Top Level Elements
-Working Sets
. -
Ensure that there are no uncommitted changes in the target project (e.g. TG Fleet) - to ensure a clean start, the project will be freshly cloned from GitHub to avoid any side-effects that might linger from previous Eclipse operations.
-
Open Eclipse and delete the TG Fleet modules, and then the respective working set (do this as two separate actions, as deleting the working set first effectively hides the project modules, preventing you from re-importing them).
You can elect to delete the files from disk now (tick the
Delete project contents on disk (cannot be undone)
checkbox), or do not, and archive/remove them manually (in case something goes wrong). -
Quit Eclipse.
-
Checkout or update
tg
. At the time of writing, switch to branchmigration-to-java-11-with-EQL3
(suitable for modern Java only). -
Perform
mvn clean install
ontg
- this should install the annotation processor jar in your.m2
directory.Note: At the time of writing, this has the same version number as the platform -
1.4.6-SNAPSHOT
. -
Clone a fresh copy of TG Fleet into your Eclipse workspace e.g.
git clone [email protected]:fieldenms/tgfleet.git
.Alternatively if you have chosen not to delete your existing TG Fleet project, remove all Eclipse-related artifacts (
mvn clean eclipse:clean
might be a good place to start). -
Create file
fleet-pojo-bl/.factorypath
(if it does not already exist) with the following content:<factorypath> <factorypathentry kind="VARJAR" id="M2_REPO/fielden/platform-annotation-processors/1.4.6-SNAPSHOT/platform-annotation-processors-1.4.6-SNAPSHOT.jar" enabled="true" runInBatchMode="false"/> </factorypath>
Note that this is pointing to the jar file installed in an earlier step.
-
In
fleet-pojo-bl/pom.xml
add the following text to the<build>...</build>
section (if it is not already there):<plugins> <plugin> <artifactId>maven-compiler-plugin</artifactId> <configuration> <encoding>UTF-8</encoding> <!-- to enable output of annotation processors --> <showWarnings>true</showWarnings> <generatedSourcesDirectory>${project.build.directory}/generated-sources/</generatedSourcesDirectory> <annotationProcessorPaths> <annotationProcessorPath> <groupId>fielden</groupId> <artifactId>platform-annotation-processors</artifactId> <version>${platform.version}</version> </annotationProcessorPath> </annotationProcessorPaths> <annotationProcessors> <annotationProcessor> ua.com.fielden.platform.processors.verify.VerifyingProcessor </annotationProcessor> <annotationProcessor> ua.com.fielden.platform.processors.metamodel.MetaModelProcessor </annotationProcessor> </annotationProcessors> </configuration> </plugin> </plugins>
-
Start Eclipse.
-
From the menu select
File
-Import...
. In the import dialog expandMaven
and selectExisting Maven Projects
. ClickNext >
. -
In the
Import Maven Projects
dialog browse to thetgfleet/fleet
directory inside your Eclipse workspace (where the top-levelpom.xml
resides).The dialog should show the top level
pom.xml
, and thepom.xml
for each of the modules under it, includingfleet-pojo-bl
(like unto searching for nested projects when importing a Maven-generated Eclipse project file).Note: If the
pom.xml
appears in the list but is greyed out and nothing can be selected, it is likely that the TG Fleet modules still exist in Eclipse. Cancel the import, view all projects, remove those related to TG Fleet, then try again.Note: The default working set name is the same as the directory name i.e.
fleet
. You can change it to match the GitHub repository nametgfleet
if you like. -
Click
Finish
.Note: Importing could take several minutes, and will perform a number of steps, including
Importing Maven projects
,Discover lifecycle mappings
andDownload sources and javadoc
.Note: During import while preparing these notes, a "warning" about discovering m2e connectors was displayed with no actual error message. Clicking
Finish
produced a warning that "projects will have build errors", and "see help for more information".OK
was clicked and the import finished successfully with no errors. -
This should result in a top level working set
tgfleet
(or justfleet
if you did not change the default), with the separate modules below, including the top-levelfleet
. -
Select the
fleet-pojo-bl
module in the package Explorer, right-click and selectProperties
.-
Expand
Java Compiler
and selectAnnotation Processing
. -
Enable project specific settings (click the first checkbox).
-
Specify
Generated source directory
astarget/generated-sources
. -
Specify
Generated test source directory
astarget/generated-test-sources
. -
The following processor options are supported:
-
cacheStats
- settingtrue
enables recording of cache statistics and reporting of additional messages. This might be useful for troubleshooting or measuring performance.
When specifying options do not use prefix
-A
before the option name, as hinted by Eclipse. -
-
Click
Apply and Close
. -
A dialog
Annotation Processing Settings Changed
should appear - clickYes
to rebuild the project.
-
-
If this all worked, you should be able to open type
MetaModels
- press your "open type" keyboard shortcut (⌘⇧T on macOS), and filter byMetaModels
- you should see a class infleet-pojo-bl/target/generated-sources
- select it and clickOpen
.
When working on a meta-model-enabled project, the following guidelines should be followed for project maintenance.
-
Update the source, typically with a
git pull
. -
Open Eclipse.
-
Select the
tgfleet
working set (or justfleet
if you did not change the default name when importing the project), pressF5
to refresh, then right-click and selectMaven
-Update Project...
.Note: A keyboard shortcut may be easier - on macOS the appropriate shortcut is
⌥F5
, which follows on nicely from theF5
to refresh. -
In the
Update Maven Project
window ensure that all modules are selected and the three checkboxes to the lower left are checked, as illustrated below, then clickOK
.This will go through the steps of updating the Maven project, downloading source (if required), and then building.
Anywhere that a string literal is used to represent an entity's property, it should be replaced with the equivalent meta-model reference.
For example TaskCo.java
could be adjusted as follows:
package fielden.work;
import static metamodels.MetaModels.Task_;
public interface TaskCo extends IEntityDao<Task>, ICanAttach<Task, TaskAttachment> {
/* Old version:
static final IFetchProvider<Task> FETCH_PROVIDER = EntityUtils.fetch(Task.class).with(
"name", "desc", "active", "estimatedManhours", "totalItems",
"mainTask", "inspectionTask", "detailingTask", "numberOfAttachments");
*/
static final IFetchProvider<Task> FETCH_PROVIDER = EntityUtils.fetch(Task.class).with(
Task_.name(), Task_.desc(), Task_.active(), Task_.estimatedManhours(), Task_.totalItems(),
Task_.mainTask(), Task_.inspectionTask(), Task_.detailingTask(), Task_.numberOfAttachments());
}
Note the static import near the top of the file.
The old version of the fetch provider, with string literals, is retained for comparison purposes. It can be seen that the meta-model version is longer, but only slightly so considering the advantages this approach offers. You might consider putting each property in the fetch model onto a separate line - it has been shown to improve readability a little, but may not be appropriate if there are a large number of properties.
Suppose that the detailingTask
property was to be renamed to detailersTask
.
Pre-meta-model, you would edit Task.java
and rename the property, then search through all source files to find references to detailingTask
and change them to detailersTask
, hoping that you had not missed any.
Some would be detected at runtime (e.g. selection criteria and EGI properties in a webui), but others might not (e.g. an EQL query that is only executed under specific circumstances).
Post-meta-model, as soon as Task.detailingTask
is refactored/renamed to Task.detailersTask
, the meta-models are re-generated, and any existing references to Task_.detailingTask()
are immediately flagged as compile-time errors.
Note that MetaModels
class fields that expose meta-models are named after the underlying entities they represent with an underscore at the end. So class Task
would be represented by a meta-model in the field Task_
.
This prevents simple name conflicts in case where the underlying entity class is also imported. For example, TaskWebUiConfig
imports both Task
and its meta-model (Task_
through a static import).
import fielden.work.Task;
import static metamodels.MetaModels.Task_;
final EntityCentreConfig<Task> ecc = EntityCentreBuilder.centreFor(Task.class)
...
.addCrit(Task_).asMulti().autocompleter(Task.class).also()
.addCrit(Task_.active()).asMulti().bool().setDefaultValue(multi().bool().setIsValue(true).setIsNotValue(false).value()).also()
.addCrit(Task_.desc()).asMulti().text().also()
.addCrit(Task_.estimatedManhours()).asRange().decimal().also()
.addCrit(Task_.totalItems()).asRange().integer().also()
.addCrit(Task_.numberOfAttachments()).asRange().integer().also()
.addCrit(Task_.mainTask()).asMulti().bool().also()
.addCrit(Task_.inspectionTask()).asMulti().bool().also()
.addCrit(Task_.detailingTask()).asMulti().bool()
...
.addProp(Task_).order(1).asc().minWidth(100)
.withSummary("total_count_", "COUNT(SELF)", format("Count:The total number of matching %ss.", Task.ENTITY_TITLE))
.withAction(editTaskAction).also()
.addProp(Task_.desc()).minWidth(200).also()
.addProp(Task_.estimatedManhours()).minWidth(100).also()
.addProp(Task_.totalItems()).minWidth(100).withAction(editTaskAction).also()
.addProp(Task_.numberOfAttachments()).minWidth(100).withAction(editTaskAction).also()
.addProp(Task_.mainTask()).minWidth(64).also()
.addProp(Task_.inspectionTask()).minWidth(64).also()
.addProp(Task_.detailingTask()).minWidth(64).also()
.addProp(Task_.active()).minWidth(100)
.addPrimaryAction(editTaskAction)
.build();
Meta-models allow dot-notation, as well as providing javadoc describing the properties that are traversed. For example, in WorkOrderWebUiConfig
the following image illustrates replacing "station.zone.sector.division"
with the meta-model equivalent:
At the point when the screen capture was taken, the user had typed WorkOrder_.station().zone().sector().d
and pressed Ctrl-space, and Eclipse displayed the available properties. This included division
, along with javadoc describing all of the essential attributes from the property definition.
Now any change to any of the entities or properties used here would immediately result in a compilation error, highlighting the affected areas of the reference that would need to be adjusted.
Due to the scope of work, it is not envisaged that all string-based property references will be changed at once. Instead it is proposed that anyone working in a particular file where string-based property names are used would take a few moments to update that file to use meta-models. Over time, the entire application will gradually be transformed.
Meta-models can also be used in annotations, but in limited form. In Java, values of annotation members must be compile-time constants, but the usual meta-model property references are runtime values, and therefore cannot be used.
The only way to use meta-models in such restrictive context is through static final fields declared by each meta-model type.
Each such field is typed with String
, and denotes the simple name of a property of a corresponding entity type.
For example, entity type OperationCheck
has property checkStartDate
that has dependent property checkFinishDate
, which could be declared using a meta-model as follows.
import static fielden.fleet.meta.OperationCheckMetaModel.checkFinishDate_;
@IsProperty
@Dependent(checkFinishDate_) // compile-time constant value
private Date checkStartDate;
This is considered the only valid use of primitive meta-model property references.
In more liberal contexts, the standard use of meta-models should be preferred.
E.g., OperationCheck_.checkFinishDate()
(followed by .toPath()
if a string is needed).
Meta-models are generated for all domain entity types at the application level. However, they are not generated for some kinds of entity types, including synthetic and union entities.
To enable meta-model generation for such entity types, annotate them with @DomainEntity
or @WithMetaModel
.
@DomainEntity
is intended for synthetic and union entity types, which can be considered to be a part of the domain.
Conversely, @WithMetaModel
should be used for non-domain entity types, such as locators or functional entity types (aka actions).
Note that typing the annotation and saving the file should trigger automatic re-generation of meta-models, now including the one just annotated.
For entity types defined at the platform level, such as User
and Attachment
, the use of @DomainEntity
is required irrespective of whether an entity type is persistent or not.
This was a design decision due to existence of a large number of various "test" entity types at the platform level that would otherwise pollute meta-models at the application level.
Meta-models for composite types are currently not supported, but it might at times be required to reference their sub-properties. If the need or a benefit is identified for having meta-models for composite types, the meta-model generator can always be enhanced to generate one.
It is possible to view the progress of annotation processing in Eclipse, although it generally takes less than a second to complete.
From the Eclipse main menu, select Window - Show View - Error Log.
- For insights into the process of meta-model generation with an incremental compiler see this page.
- High-level description of the meta-model processing algorithm.
- Nothing much.