Expander Tags
ExpandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. 6.4.0 introduced a systemSystemA set of interconnected components that work together to achieve a specific purpose or function. of Tags. These tags are designed to simplify conditions, group related ExpandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. and Features and allow for better traceability.
Tags as conditions
Any ExpanderExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact., Feature or ExpansionStep can define one or more tags. They will only be included if all tags are present on the target element.
- Expander
- Feature
<expander xmlns="http://nsx.normalizedsystems.org/20204/expander" name="DeleteMultipleActionExpander">
<packageName>net.democritus.expander.dataElement.bulk</packageName>
<layerType name="CONTROL_LAYER"/>
<technology name="STRUTS2"/>
<sourceType name="JAVA"/>
<tags>
<tag>#cruds.bulk.delete</tag>
</tags>
<elementTypeName>DataElement</elementTypeName>
<artifactModifiers/>
<artifactName>$dataElement.name$DeleteMultipleAction.java</artifactName>
<artifactPath>$componentRoot.directory$/$artifactSubFolders$/$dataElement.packageName;format="toPath"$/action</artifactPath>
<isApplicable>true</isApplicable>
<phase>expansion</phase>
</expander>
<feature xmlns="https://schemas.normalizedsystems.org/xsd/prime-core/2024/0/0/feature" name="BulkDelete">
<packageName>net.democritus.expander.dataElement.bulk</packageName>
<condition>true</condition>
<tags>
<tag>#cruds.bulk.delete</tag>
</tags>
<type name="custom"/>
<expanderFeatures>
<!-- ... -->
</expanderFeatures>
</feature>
Defining Tags
Tags can be defined by adding Tag rules. These rules are defined in an ElementTagRule DataResource.
Each rule defines a premise and a consequence as <consequence> :- <premise>.
- The premise defines what conditions need to be fulfilled to trigger the rule. If it does not contain any conditions, it is always true.
- The consequence contains all tags that are enabled if the premise is true.
<dataResource type="expansionControl::ElementTagRule">
<rule name="Example Tag Rule">
#tag1, #tag2 :- condition1, condition2, condition3
</rule>
</dataResource>
In this example, #tag1 and #tag2 are enabled if condition1 AND condition2 AND condition3 are all true.
Tags are always resolved against a target element. They are not necessarily shared along the model hierarchy.
E.g. the tag #cruds.bulk.delete could apply to a DataElement, but not to the FieldsFieldA property of a DataElement expanded to a database column and user interface input element. or FindersFinderA search specification combining field-operator pairs to execute standard data queries on a DataElement. of that DataElement.
Rules always need to be interpreted in the context of the target element.
In order to share tags with other parts of the model, rules need to be defined to cascade the tags.
(See below)
If 2 or more rules define the same tag as a consequence, either rule can be used to resolve the tag. As a result, multiple rules can be used to define different ways to enable a tag.
Adding Tags through Options
The following rule enables the #cruds.bulk.delete for all elements that have the option cruds.bulk.delete.
<rule name="Bulk Delete Option">
#cruds.bulk.delete :- option(cruds.bulk.delete)
</rule>
The following rule enables the #integrationtest.stdapi for all elements that have the option integrationtest.api with value stdapi.
<rule name="Integration Test Standard API">
#integrationtest.stdapi :- option(integrationtest.api, stdapi)
</rule>
This rule is only valid for a DataElement if the option is set directly to that DataElement. However, when combined with the cascading flag on the OptionType, it can also work when set on the Component or ApplicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution..
Activating related Features
Sometimes, a feature relies on other features to work. E.g. the 'Bulk Delete' feature mentioned earlier relies on the 'Multi-select' feature in the view layerLayerLogical separation of functionality within a single system/application, focused on code organization. to be able to select and delete multiple instances.
We can use tags to represent this reliance. This causes the 'Multi-select' feature to be automatically enabled in case 'Bulk Delete' was activated.
<rule name="Bulk Delete requires multi-select">
#view.list.multiselect :- #cruds.bulk.delete
</rule>
Other Predicates
In addition to the option(type) predicate, there are a handful of other predicates.
Predicates are similar to functions.
not($tag)inverts a tag. It only resolves if the given tag does not resolve.is($elementType, [$elementKey])checks whether the element is of a specific type (e.g.elements::DataElement). When the key is provided it will also check the functional key (e.g.workflow::EngineNode).where($field, [$tag,...])attempts to resolve the given tag on a linked element. Supports element references, booleans (true), Strings (not empty).existsIn($collection, [$tag,...])attempts to resolve the given tag to one of the items in a collection.dependsOn([$tag,...])attempts to resolve the given tag to one of the dependencies of a Program or Module.instanceOf($elementType)checks whether the element is an instance of DataElement, TaskElement etc. Useisinstead.
<rule name="Enable Typed Finders unless disabled">
#typedFinders :- not(#legacy.typedFinders.disable)
</rule>
<rule name="Enable IO Import on DataElements with CSV Import">
#io.import :- instanceOf(elements::DataElement), #csv.import
</rule>
<rule name="Cascade useAccount to ApplicationInstance">
#security.useAccount :- where(application, #security.useAccount)
</rule>
<rule name="Expand DataState if DataElement has a status field">
#common.dataState :- existsIn(fields, #workflow.statusField)
</rule>
The where() predicate can be used to cascade tags down the model hierarchy.
Here the #security.useAccount is added to the ApplicationInstance if it present on the ApplicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution..
<rule name="Cascade useAccount to ApplicationInstance">
#security.useAccount :- where(application, #security.useAccount)
</rule>
In reverse, the existsIn() predicate can be used to cascade up.
With this rule, we enable the DataState artifactArtifactA build or deployment result, such as a Docker container image or Java archive, produced by a software project. if one of the fieldsFieldA property of a DataElement expanded to a database column and user interface input element. of a DataElement is a status fieldFieldA property of a DataElement expanded to a database column and user interface input element..
<rule name="Expand DataState if DataElement has a status field">
#common.dataState :- existsIn(fields, #workflow.statusField)
</rule>
Profiles
Profiles are currently functional, but most expandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. have not been migrated yet to leverage this. Hence, the example here presents tags that could be used in the future.
Profiles have been introduced to replace variant elements in the model, such as ApplicationInstance. A profile can be applied to an expansion in the expansionSettings.xml:
<expansionSettings>
<modelDirectory>..</modelDirectory>
<expansionDirectory>../expansions</expansionDirectory>
<expansions>
<expansion>
<type component="elements" name="JeeApplication"/>
<target>spaceApp::1.0.0</target>
<profile name="backend"/>
</expansion>
</expansions>
<expansionResources>...</expansionResources>
</expansionSettings>
A profile can be defined as a DataResource, or in a settings directory (in settings/expansionProfile/{yourProfile}.xml).
<profile name="backend">
<tags>
<tag>#server.tomee</tag>
<tag>#db.postgresql</tag>
<tag>#data.hibernate</tag>
<tag>#logic.ejb3</tag>
<tag>#proxy.rmi</tag>
<tag>#control.struts</tag>
<tag>#view.knockout</tag>
<tag>#jee7</tag>
<tag>#java17</tag>
</tags>
</profile>
The tags of the profile will be applied to the Program, e.g. the ApplicationApplicationThe overarching model container grouping all components, domain logic, and configurations for an enterprise solution. (not ApplicationInstance!).
In addition, a rule can look at the tags in the profile in their premise. These tags are also known as root tags. This rule enables several JEE specification versions based on the JEE version defined in the profile.
<rule name="JEE 7 specification versions">
#logic.ejb3_1, #control.servlet3_1, #view.jsp2_3, #jee :- $jee7
</rule>
Take care when defining rules based on root tags that you do not add a rule that can recurse on itself. This can cause infinite recursion to occur.
<rule name="Infinite recursion">
#recursive.tag :- $recursive.tag
</rule>
This might be fixed in a future release by adding some loop detection.
Other Uses
LayerImplementations
LayerImplementations can also define tags to replace their conditions.
<layerImplementation name="JaxRsController">
<layerType name="CONTROL_LAYER"/>
<technology name="JAXRS"/>
<condition>true</condition>
<tags>
<tag>#control.cruds.jaxrs</tag>
</tags>
<!-- ... -->
</layerImplementation>
Expander Mapping
You can also check for the presence of tags in mapping files.
<value name="enableImport" eval="tags.has('#io.import')"/>
<value name="enableExport" eval="tags.has('#io.export')"/>
These tags are resolved against the target element of the ExpanderExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact. or Feature.
Sometimes, you will need to check for tags on other elements.
They can be accessed with tags.on(target).has(tag)
<value name="isIoEnabled" eval="tags.on(dataElement).has('#io.page')"/>
Adding Technologies
ExpandersExpanderA unit of generation that combines a data tree instance (functional M1 model) with a template to generate an artifact., Features etc. are filtered on technologies. These technologies are usually provided based on the ApplicationInstance. It is possible to enable a technology with a Tag rule:
<rule name="EJB3 Technology">
#technology:EJB3 :- #logic.ejb3
</rule>
Testing
expanders-test-utils
The ExpanderTester and FeatureTester classes have specific methods to test Tag resolution:
@Test
public void test_isApplicable() {
DataElementComposite model = baseModel();
tester.testTagIsMissing("#cruds.bulk.delete", model);
modelBuilder.extendModel(model, option("cruds.bulk.delete"));
tester.testTagsArePresent(model);
}
Tags can be added with the TestTagProvider class:
@Test
public void testBase() {
tester.extendContext(expansionContext -> TestTagProvider.addRootTags(expansionContext, "#java11"));
tester.testBase(baseModel());
}
expander-assert
Use the @TestProfile annotation to define tags for a test.
The Assert classes have methods to test for tags.
@Test
@TestProfile(tags = "#test.tag")
void test_tags(TestExpansion<DataElementComposite> testExpansion) {
testExpansion.assertThatExpander().hasAllTags();
}
@Test
void test_tag_notPresent(TestExpansion<DataElementComposite> testExpansion) {
testExpansion.assertThatExpander().doesNotHaveTag("#sample.feature");
}
You can also test tags directly with assertThatTags():
@Test
@TestProfile(tags = {"#control.struts2"})
void test_tags(TestExpansion<DataElementComposite> expansion) {
expansion.assertThatTags()
.contain("$control.struts2")
.contain("$java11")
.contain("$jee7")
.doesNotContain("#control.struts2");
}