Skip to main content

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 ApplicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution. stack.

  • ApplicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution. is a program ElementClass, which is described in the elements ontology.
  • There is a JeeApplication ProgramType. This describes how to load the ApplicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution. model, where to expand and which layersLayerLogical separation of functionality within a single system/application, focused on code organization. are used. It also provides the logic for importing Components.
ProgramTypes and technologies

One program ElementClass can have multiple ProgramTypes. This corresponds with the multiple ways how that model can be expanded into code. E.g. an ApplicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution. model can be used to generate a JEE applicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution.. But, the same model can also be used to generate a Spring applicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution. or any other technology.

Program Metamodel​

To model a new class of Programs, define an ElementClass with role program.

model/elementClasses/Executable.xml
<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:

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 anchorAnchorA marker within generated source code reserved for adding custom logic during re-expansion. 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 ExpandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact..
  • 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>
Program Layers

ExpandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. always target a specific LayerType. Note that ExpandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. will only run if that LayerType is included in the ProgramType. (Except for Elements in a Module, which use the ModuleType LayersLayerLogical separation of functionality within a single system/application, focused on code organization..)

This gives the ExpanderExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. developer control over which ExpandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. apply to which ProgramTypes.

Map Technologies​

When expanding, ExpandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. 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
COMMON

The COMMON technology is always present on the Program. Hence ExpandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. 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

ExecutableSpecFactory.java
  // anchor:custom-methods:start
@Override
public ModelSpec<ExecutableComposite, ExecutableComposite> modelSpec() {
return model(spec);
}
// anchor:custom-methods:end