Java Code Generation
The tooling is capable of generating Java classes that can be used to store instances of the models. The code is annotated to allow JAXB and JPA to operate, which mean that it is easy to read and write model instances to XML and standard relational databases. It should be noted that the generated code uses java 1.8 constructs.
The generated Java code depends on the VO-DML java runtime library, which the plugin will automatically add to the dependencies along with the necessary JAXB and JPA libraries.
Generating The Java code
The command
gradle vodmlJavaGenerate
./build/generated/sources/vodml/java
which is on the build classpath and so
gradle build
gradle build
will
automatically run gradle vodmlJavaGenerate
if the code is out of date because the model has been updated.
Characteristics of the Generated code
In general the code creates POJOs or data classes - i.e. the classes have very little functionality apart from being stores of the data model. The functionality that they do have is described below.
Instance Creation
To be JPA and JAXB compliant, the classes are Java beans with a no argument constructor, so they can be default constructed and then getX/setX can be used on the properties.
A a = new A();
a.setX(x);
a.setY(y);
In addition, there are several other ways of creating objects
Full constructor
A constructor with all the possible properties included is
A a = new A(x,y);
Fluent
Each property has a withX(X x) function
A a = new A().withX(x).withY(y);
Fluent functional builder
A static builder that takes a functional argument.
A a = A.createA( b -> {
b.x = x;
b.y = y;
}
);
Serializing Models
As well as all the individual Enum, ObjectType, and DataType classes, there is an overall ${modelname}Model class generated, that is intended to act as a 'container' for the individual elements. This is especially useful in the case where the model has references, as then there are some convenience methods for dealing with the automatic setting of reference IDs if necessary.
Functions for adding content
For each of the concrete objectTypes in the model there is
an overloaded addContent()
method, which will add the content to the
overall instance and find any references.
Once all the content has been added, then there is a processReferences()
method
which will go through the whole model and automatically assign IDs for any
references that do not already have them.
At this point the overall model object is suitable to be serialized.
On reading in a model instance, it is possible to extract any top level ObjectTypes
with the public <T> List<T> getContent(Class<T> c)
method.
contained references
There is some provisional support for "contained references" when cloning an object - the API for this
is subject to change, but an example is used in copyTest using the createContext()
and updateClonedReferences()
methods either side of an object clone with a copy constructor.
Finally, there is a static public static boolean hasReferences()
method which
can be used to check if the model has any references - if it does not then much of the
machinery above (apart from the JAXBContext) is necessary, and individual ObjectTypes may be
written.
The unit tests for this project show most of the various code features being used
XML Serialization
A static function to create a suitable JAXBContext is present
JAXBContext jc = MyModel.contextFactory();
JSON Serialisation
The JSON serialization is implemented with the Jackson library
A suitable ObjectMapper is obtained with
ObjectMapper mapper = MyModel.jsonMapper();
Reading and Writing from RDBs
The generated code has JPA annotations to allow storing in RDBs with compliant systems.
Operations are not in general cascaded into references - so that the references need to be explicitly managed. In most cases this will be the "natural" way to do things
for the model - however at creation time it might be inconvenient to do this so that there is a method
persistRefs(jakarta.persistence.EntityManager _em)
on the model that will do a deep persist of any references in the child objects, which in turn then will allow an error free persist of the content. Note that in general it is only possible to run the persistRefs once all of the content has been added to the model, and only for the first time a reference is created - for subsequent updates of the model it will be necessary to manage the references manually.
In general collections are marked for lazy loading, and as a convenience there is a forceLoad()
method generated that will do a deep walk of all the collections in a particular type, which will force the loading of the whole instance tree if that is desired.
This extra JPA functionality is described by the JPAManipulations interface.
Composition Helpers
Some convenience methods are created on the object that is the parent of compositions to make dealing with JPA updates easier.
class X {
public void replaceInY(final Y _p) {...}
}
Internally this makes use of another convenience function
class Y {
public void updateUsing ( final Y other){...}
}
Embeddable dataTypes
The most natural way to represent dataTypes in JPA is as embeddable, this means that they do not have a separate "identity" and are simply represented as columns within the parent entity table. The problem with this is that JPA does not specifically allow inheritance of embeddables (though nor does it disallow the use). As a consequence the support for inherited embeddables is not uniform in JPA providers.
Hibernate seems to support the concept of embeddable hierarchies reasonably well by
naturally using the @MappedSuperclass
annotation - although there is an irritation in that
the full flexibility of having optional attributes that are dataTypes is not supported as all columns are
made non-nullable - a bug has been submitted https://hibernate.atlassian.net/browse/HHH-14818
There are also eclipselink bugs that mean that the suggested way of doing inherited embeddables does not seem to work.
Testing models
The java runtime has a number of base classes that aid the testing of model instances - there is an example for the mock coords model. Although it is not obvious from the source code presented because most of the behaviour is inherited from the base test class, this test will actually
- round trip the model instance to JSON
- round trip the model instance to XML
- validate the model instance
simply by running
gradle test
will generate the actual model code (if not already done) and run the tests as long as
tasks.test {
useJUnitPlatform()
}
build.gradle.kts
file.
General interfaces
Much of the functionality described above is defined in two interfaces
ModelManagement an
instance of which can be obtained with the management()
method on the model class and
ModelDescription an
instance of which can be obtained with the description()
method on the model class.
These interfaces allow generic model handling code to be written.