Skip to content

Profile strategies and calculated properties

Profile Strategies let a Facility calculate a model-property value while JoinedWorkz transforms a CMN model into the Core Model. A Cartridge or generator can then consume that value without knowing how it was calculated.

This is an advanced Facility-author contract. Start with Build a custom Facility and the Profile author reference before adding a Strategy.

Scope

This page documents the supported use of Strategy properties contributed to complextype and field. It covers Maven-based Facility development. Custom Settings, Studio authoring and Studio-specific diagnostics are outside this contract.

1. Model properties are not project configuration

A Strategy calculates a CMN model property. The value belongs to one transformed Core Model object and can influence the generated output for that object.

For example, reportLabel below belongs to a transformed complex type:

profile
platform TextReport {
    contribute to complextype {
        property reportLabel: STRING strategy=TextReportLabelStrategy
    }
}

This is different from a key in joinedworkz.properties:

  • a model property is declared for selected model-element kinds and is stored on a transformed CmnObject;
  • a joinedworkz.properties key configures a project build or a Facility consumer; and
  • declaring either kind of property does not automatically implement a generated effect.

A Strategy can use dependencies supplied by the effective Setting, including Facility-owned helpers. When its calculation depends on project configuration, every supported key must still be registered and documented through the separate joinedworkz.properties contract.

2. Declare and assign a Strategy

A top-level Strategy declaration gives the implementation a Profile name:

profile
strategy TextReportLabelStrategy
    implementation="com.example.textreport.TextReportLabelStrategy"

Assign that Strategy to a typed property in a Platform Contribution:

profile
platform TextReport specialization of Base {
    apply cartridge TextReportCartridge

    contribute to complextype {
        property reportLabel: STRING strategy=TextReportLabelStrategy
    }
}

implementation is the fully qualified name of a class available on the Consumer's compile classpath. The class must extend AbstractStrategy. The Strategy reference after strategy= is linked as a Profile declaration; a name that is not visible through the current Profile and its imports does not link.

The selected Contribution determines the Core object passed to the Strategy:

  • complextype supplies a CmnComplexType;
  • field supplies a CmnField.

Do not infer support for every model-element name accepted by the Profile grammar. In particular, this release does not establish a public Strategy contract for package Contributions or for properties declared locally on a Stereotype.

3. Implement a small, injected calculation

A Strategy extends AbstractStrategy and implements:

java
public Object apply(CmnObject cmnObject)

Keep the Strategy focused on calculating one value. The TextReport example delegates formatting to an injected helper:

java
@Singleton
public class TextReportLabelStrategy extends AbstractStrategy {

    @Inject
    protected TextReportLabelFormatter textReportLabelFormatter;

    @Override
    public Object apply(CmnObject cmnObject) {
        if (cmnObject instanceof CmnComplexType complexType) {
            return textReportLabelFormatter.format(complexType);
        }
        return null;
    }
}

The implementation class and its dependencies are created through the effective Platform Setting. Use injected dependencies; do not construct a fallback formatter inside the Strategy. A concrete replacement contract through a custom Setting is outside the contract documented on this page.

JoinedWorkz can reuse one Strategy instance for multiple model elements and models in the same project runtime. Therefore, keep the Strategy stateless and side-effect-free:

  • do not retain the current CmnObject in an instance field;
  • do not write files, mutate unrelated model objects or report diagnostics from apply(...);
  • do not make the result depend on invocation count; and
  • do not use mutable per-model caches in the Strategy instance.

The @Singleton annotation makes this intended lifecycle visible in the implementation. It is not permission to accumulate build state.

4. Understand the transformation lifecycle

Strategy calculation is part of Core Model transformation. It is not a Cartridge Preparation and it is not generation.

For one applicable property, the relevant flow is:

  1. JoinedWorkz transforms the source model element and collects its available property values.
  2. JoinedWorkz invokes the property's Strategy with the current transformed CmnObject.
  3. A non-null result is offered under the contributed property name as a calculated CmnProperty.
  4. A value already present under that name keeps priority. The calculated result does not replace it.
  5. After model transformation has finished, Cartridge Preparation runs, followed by Cartridge Validation and then Cartridge generation.

The Core wrapper marks an accepted Strategy result with CmnProperty.isCalculated() == true. Most generators do not need to inspect that flag; they read the effective property value.

When apply(...) returns null, JoinedWorkz does not add a calculated value. An existing explicit value remains available. If neither an explicit nor a calculated value exists, the property is absent and the consuming Facility must treat it as optional or report its own validated contract.

Result type

Return a value compatible with the type declared by the Profile property. There is no early, Strategy-result-specific type diagnostic. An incompatible value can therefore fail later when a Core Model consumer reads or casts it. Cover the result type with a focused transformation test.

5. Explicit values override calculations

A CMN author can set an applicable Strategy property explicitly:

cmn
package org.joinedworkz.examples.textreport.consumer

type Example reportLabel='Explicit label for Example' {
}

The explicit reportLabel wins over the value returned by TextReportLabelStrategy. This lets a Strategy supply a derived default while one model element selects an intentional label.

Another applicable type can omit the property:

cmn
package org.joinedworkz.examples.textreport.imported

type ImportedExample {
}

For ImportedExample, the Strategy result becomes the calculated reportLabel. The exact label text remains the responsibility of TextReportLabelFormatter; it is not defined by the Profile syntax.

Do not use Strategy side effects to distinguish these cases. The Strategy can be invoked even when an explicit value already exists; priority is applied when JoinedWorkz stores the result.

6. Consume the Core property once

A Cartridge or generator reads the completed property from the transformed Core Model:

java
protected String reportLabel(
        CmnComplexType complexType) {
    String reportLabel = complexType.getString("reportLabel");
    if (reportLabel == null) {
        throw new IllegalStateException(
                "Required Core property 'reportLabel' is missing "
                + "for CMN complex type '"
                + complexType.getName() + "'.");
    }
    return reportLabel;
}

The generator must not inject TextReportLabelStrategy, call apply(...) again or reimplement its formatting rule. Recalculation would bypass the established property priority and could produce a different value from the one validated on the Core Model.

Use Cartridge Validation when a missing calculated value is an expected model contract violation. Reserve a generator exception for an unexpected broken invariant. See Cartridge validation and diagnostics.

7. Do not build Strategy dependency chains

JoinedWorkz does not define a public dependency scheduler for calculated properties. A Strategy must not rely on:

  • another Strategy having run first;
  • declaration order between contributed properties;
  • a calculated property from another model element being available; or
  • automatic cycle detection between Strategy calculations.

Calculate a property from stable input already present on the supplied Core object and from injected, stateless helpers. If two results require one shared calculation, place that calculation in an injected helper rather than making one Strategy depend on the other Strategy's output.

The supplied object is still being transformed. In particular, a CmnComplexType Strategy must not assume that fields, operations or other child collections have already been completed. Base a calculation on stable properties of that object, not on the final child graph.

Property propagation is a separate transformation contract. Generic propagation does not imply that a calculated property is copied to another model element. Verify every required source-to-target path and use Profile-controlled property propagation for the supported propagation relationships.

8. Package and test the contract

The Facility JAR must contain the .profile resource, the Strategy implementation, its injected helpers and the Cartridge or generator that consumes the calculated property. Build the Facility before the separate Consumer runs JoinedWorkz generation.

At minimum, verify:

  • Profile parsing and linking for the Strategy declaration and assignment;
  • the expected CmnObject subtype passed to apply(...);
  • a non-null result stored with the calculated flag;
  • a null result leaving the property absent;
  • an explicit CMN value retaining priority;
  • the injected helper being used without local fallback construction;
  • repeated use of the same stateless Strategy instance;
  • the generator reading the transformed property without invoking the Strategy; and
  • the packaged Facility in an independent Consumer build.

An implementation class that cannot be loaded, a Strategy exception or an incompatible result can stop Maven generation. Test the actionable failure at the Consumer boundary. This page does not define corresponding Studio marker behavior.