Customizing the Prog Model
This chapter explains the main APIs to extend or alter the programming conventions that Apache Causeway understands to build up its metamodel.
Custom validator
Apache Causeway' programming model includes a validator component that detects and prevents (by failing fast) a number of situations where the domain model is logically inconsistent.
For example, the validator will detect any orphaned supporting methods (eg hideXxx()
) if the corresponding property or action has been renamed or deleted but the supporting method was not also updated.
Another example is that a class cannot have a title specified both using title()
method and also using @Title
annotation.
You can also impose your own application-specific rules by installing your own metamodel validator. To give just one example, you could impose naming standards such as ensuring that a domain-specific abbreviation such as "ISBN" is always consistently capitalized wherever it appears in a class member.
API and Implementation
There are several ways to go about implementing a validator.
MetaModelValidator
Any custom validator must implement Apache Causeway' internal MetaModelValidator
interface, so the simplest option is just to implement MetaModelValidator
directly:
package org.apache.causeway.core.metamodel.specloader.validator;
public interface MetaModelValidator {
default void onFailure(
@NonNull FacetHolder facetHolder, (1)
@NonNull Identifier deficiencyOrigin, (2)
@NonNull String deficiencyMessageFormat,
Object ...args) {
val deficiencyMessage =
String.format(deficiencyMessageFormat, args);
DeficiencyFacet.appendTo( (3)
facetHolder, deficiencyOrigin, deficiencyMessage);
}
}
1 | represents an element of the metamodel, either an ObjectSpecification (domain class or mixin), or an ObjectMember (property, collection or action), or an ObjectActionParameter . |
2 | identifier of the element |
3 | appends the message into the DeficiencyFacet associated with the element. |
If the onFailure
is called, then a message explaining the deficiency is stored.
The framework also provides a MetaModelValidatorAbstract
that implements this interface.
However, it is the responsibility of the validator itself to figure out how to iterate over the entire model.
Since this is a common use case, the framework provides a more convenient and fine-grained "Visitor" API, discussed next.
Visitor
More often than not, you’ll want to visit every element in the metamodel, and so for this you can instead subclass from MetaModelValidatorVisiting.Visitor
:
package org.apache.causeway.core.metamodel.specloader.validator;
public final class MetaModelValidatorVisiting
extends MetaModelValidatorAbstract {
public interface Visitor {
public boolean visit( (1)
ObjectSpecification objectSpec, (2)
ValidationFailures validationFailures); (3)
}
// ...
}
1 | return true continue visiting specs. |
2 | ObjectSpecification is the internal API representing a class |
3 | add any metamodel violations to the ValidationFailures parameter |
If you have more than one rule then each can live in its own visitor.
SummarizingVisitor
As a slight refinement, you can also subclass from MetaModelValidatorVisiting.SummarizingVisitor
:
package org.apache.causeway.core.metamodel.specloader.validator;
public final class MetaModelValidatorVisiting
extends MetaModelValidatorAbstract {
public interface SummarizingVisitor extends Visitor {
public void summarize(ValidationFailures validationFailures);
}
// ...
}
A SummarizingVisitor
will be called once after every element in the metamodel has been visited.
This is great for performing checks on the metamodel as a whole.
Configuration
Once you have implemented your validator, you must register it with the framework.
This is most easily done by implementing service that implements MetaModelRefiner
service.
For example, some folk advocate that pattern names such as "Repository" or "Factory" should not appear in class names because they are not part of the ubiquitous language. Such a rule could be verified using this implementation:
@Service
public static class NoRepositorySuffixRefiner implements MetaModelRefiner {
@Override
public void refineProgrammingModel(ProgrammingModel programmingModel) {
programmingModel.addValidator(new MetaModelValidatorVisiting.Visitor() {
@Override
public boolean visit(ObjectSpecification objectSpec, MetaModelValidator validator) {
if(objectSpec.getSingularName().endsWith("Repository")) {
validator.onFailure(objectSpec, objectSpec.getIdentifier(), "Domain services may not have the suffix 'Repository'");
}
return true;
}
});
}
}
Finetuning
The core metamodel defines APIs and implementations for building the Apache Causeway metamodel: a description of the set of entities, domain services and values that make up the domain model.
The description of each domain class consists of a number of elements:
- ObjectSpecification
-
Analogous to
java.lang.Class
; holds information about the class itself and holds collections of each of the three types of class' members (below); - OneToOneAssociation
-
Represents a class member that is a single-valued property of the class. The property’s type is either a reference to another entity, or is a value type.
- OneToManyAssociation
-
Represents a class member that is a collection of references to other entities. Note that Apache Causeway does not currently support collections of values.
- ObjectAction
-
Represents a class member that is an operation that can be performed on the action. Returns either a single value, a collection of entities, or is
void
.
The metamodel is built up through the ProgrammingModel, which defines an API for registering a set of FacetFactorys.
Two special FacetFactory
implementations - PropertyAccessorFacetFactory and CollectionAccessorFacetFactory - are used to identify the class members.
Pretty much all the other FacetFactory
s are responsible for installing Facets onto the metamodel elements.
There are many many such Facet
s, and are used to do such things get values from properties or collections, modify properties or collections, invoke action, hide or disable class members, provide UI hints for class members, and so on.
In short, the FacetFactory
s registered defines the Apache Causeway programming conventions.
Modifying the Prog. Model
Creating the ProgrammingModel
is the responsibility of the ProgrammingModelService.
The default implementation, ProgrammingModelServiceDefault, creates as the ProgrammingModel
the concrete implementation ProgrammingModelFacetsJava8, which registers a large number of FacetFactory
s.
This programming model can then be added to because the service call every known implementation of MetaModelRefiner.
The programming model can also be filtered using the ProgrammingModelInitFilter interface. The default implementation of this interface, ProgrammingModelInitFilterDefault accepts all facet factories though allows deprecated facet factories to be excluded through a configuration property.
The diagram below shows how these classes relate:
To summarise:
-
To add to the programming model (new facet factories, validators or post processors), create a
@Service
implementing theMetaModelRefiner
interface -
to remove from the programming model, create a
@Service
implementing aProgrammingModelInitFilter
(with an earlier precedence than the default implementation).