Program Models
Each model has a root element. This element is referred to as the Program
.
A Program has a ElementClass
and a ProgramType
.
- ElementClass: ElementClass, with role program.
- ProgramType: description of what kind of program we wish to expand from the model.
To make this more concrete, we can look at the elements metamodel and the JEE Application stack.
- Application is a program ElementClass, which is described in the elements ontology.
- There is a
JeeApplication
ProgramType. This describes how to load the Application model, where to expand and which layers are used. It also provides the logic for importing Components.
One program ElementClass can have multiple ProgramTypes. This corresponds with the multiple ways how that model can be expanded into code. E.g. an Application model can be used to generate a JEE application. But, the same model can also be used to generate a Spring application or any other technology.
Program Metamodel
To model a new class of Programs, define an ElementClass with role program
.
<elementClass name="Executable">
<isGloballyUnique>false</isGloballyUnique>
<isAbstract>false</isAbstract>
<isSealed>false</isSealed>
<group name="custom" ontology="demo"/>
<role name="program"/>
<attributes>
<attribute name="name">
<isIdentifier>true</isIdentifier>
<dataType name="Name" ontology="demo"/>
</attribute>
</attributes>
</elementClass>
Once you've expanded this, you'll need to provide some implementations:
- Create a ProgramFactory implementation.
- Define a ProgramType in programTypes.xml.
- Optionally, add a customization to ProgramEnricher to provide technologies to the Program.
- Customize SpecFactory to add a modelSpec() method for Expanders tests.
ProgramFactory
The ProgramFactory is a class that provides implementations to import the models.
ProgramFactory example
public class ExecutableFactory
implements ProgramFactory<ExecutableDataRef, ExecutableTree> {
/**
* ProgramExpansionConverter translates the reference in the expansionSettings to a DataRef
* which can be used by the ProgramReader class
*/
@Override
public ProgramExpansionToDataRefConverter<ExecutableDataRef> getProgramExpansionConverter(
ModelLoadingContext modelLoadingContext, ProgramTypeComposite programTypeComposite) {
return programExpansion -> ExecutableDataRef.withFunctionalKey(programExpansion.getTarget());
}
/**
* The ModuleRetriever is used to decide which modules to load.
* See documentation of Modules for more information.
*/
@Override
public ProgramModuleRetriever<ExecutableTree, ModuleTypeComposite> getModuleRetriever(
ModelLoadingContext modelLoadingContext, ProgramTypeComposite programTypeComposite) {
return Collections.emptyList();
}
/*
Several Classes are expanded for Program ElementClasses.
In most cases, it is enough to use these classes directly.
*/
@Override
public ProgramReader<ExecutableDataRef, ExecutableTree> getProgramReader(
ModelLoadingContext modelLoadingContext, ProgramTypeComposite programTypeComposite) {
return new ExecutableProgramReader(modelLoadingContext, programTypeComposite);
}
@Override
public ProgramLoader<ExecutableTree> getProgramLoader(
ModelLoadingContext modelLoadingContext, ProgramTypeComposite programTypeComposite) {
return new ExecutableProgramLoader(modelLoadingContext, programTypeComposite);
}
@Override
public ProgramEnricher getProgramEnricher(
ModelLoadingContext modelLoadingContext, ProgramTypeComposite programTypeComposite) {
return new ExecutableProgramEnricher(modelLoadingContext, programTypeComposite);
}
@Override
public ProgramWriter<ExecutableTree> getProgramWriter(
ModelLoadingContext modelLoadingContext, ProgramTypeComposite programType) {
return new ExecutableProgramWriter(modelLoadingContext, programType);
}
}
ProgramTypes.xml
The metamodel project contains a programTypes.xml
file with a custom anchor to add ProgramTypes.
- name and component are combined to make a unique key.
- rootExpandableElement refers to the ElementClass in the metamodel.
- programFactory is the fully qualified name of the ProgramFactory implementation.
- modelDirectory is a template for the location of the model files.
- Similarly, sourceDirectory describes the location for harvest and ext files.
- expansionDirectory is used for the target directory for Expanders.
- layerTypes lists the layerTypes of the Program.
ProgramType example
<dataResource type="expansionControl::ProgramType">
<!-- anchor:custom-program-types:start -->
<programType name="Java Cli">
<component>demo</component>
<rootExpandableElement component="demo" name="Executable"/>
<programFactory>org.normalizedsystems.demo.ExecutableFactory</programFactory>
<modelDirectory>$source.rootDirectory$/executables/$executable.name$/model</modelDirectory>
<sourceDirectory>$source.rootDirectory$/executables/$executable.name$</sourceDirectory>
<expansionDirectory>$expansion.rootDirectory$/$executable.name$</expansionDirectory>
<layerTypes>
<layerType name="EXEC_ROOT"/>
<layerType name="EXEC_CLI"/>
</layerTypes>
</programType>
<!-- anchor:custom-program-types:end -->
</dataResource>
Expanders always target a specific LayerType. Note that Expanders will only run if that LayerType is included in the ProgramType. (Except for Elements in a Module, which use the ModuleType Layers.)
This gives the Expander developer control over which Expanders apply to which ProgramTypes.
Map Technologies
When expanding, Expanders are filtered based on the Technologies that are present in the Program. To add these technologies, a customization is needed in the ProgramEnricher class.
Example how to add Technologies
// anchor:custom-after-transform:start
List<TechnologyComposite> technologies = new ArrayList<>(program.getTechnologies());
// Fetch and add PICOCLI Technology
modelLoadingContext.getDataRegistry()
.getComposite("PICOCLI", TechnologyComposite.class)
.ifPresent(technologies::add);
// Maybe add some technologies referenced in the Model
technologies.add(composite.getDataBaseTechnology());
technologies.add(composite.getViewTechnology());
program.setTechnologies(technologies);
// anchor:custom-after-transform:end
The COMMON technology is always present on the Program. Hence Expanders targeting COMMON will always be applicable. (Provided all other conditions are met.)
Usage in tests
To be able to define test models using a program ElementClass, you will need to add an implementation
of the modelSpec(Spec)
method.
modelSpec() example
// anchor:custom-methods:start
@Override
public ModelSpec<ExecutableComposite, ExecutableComposite> modelSpec() {
return model(spec);
}
// anchor:custom-methods:end