FINXIS.

Java examples

CDM 7.4.0

The pieces every CDM integration needs, as code you can paste into a project: building objects, moving them in and out of JSON, validating and qualifying them, calling the model's own functions, and the post-processing that makes a document internally consistent.

Maven setup

Two ways in: a new project pulling cdm-java from Maven Central, or a module inside the CDM source tree inheriting cdm-parent. Build with JDK 21 — the CDM enforces [21,22) — while the bytecode targets Java 11. Adding a module to the tree also means adding it to the root pom’s <modules>, or the reactor skips it.

Standalone project A new project depending on the published artifact. 34 lines
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example</groupId>
    <artifactId>standalone-cdm-project</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <cdm.version>7.4.0</cdm.version>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.finos.cdm</groupId>
                <artifactId>cdm-java</artifactId>
                <version>${cdm.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.finos.cdm</groupId>
            <artifactId>cdm-java</artifactId>
        </dependency>
    </dependencies>
</project>
Inside the CDM download common-domain-model/your-module/pom.xml — inherits cdm-parent. 67 lines
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.finos.cdm</groupId>
        <artifactId>cdm-parent</artifactId>
        <version>0.0.0.master-SNAPSHOT</version>
    </parent>

    <artifactId>your.example</artifactId>

    <properties>
        <maven.compiler.source>11</maven.compiler.source>
        <maven.compiler.target>11</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.finos.cdm</groupId>
                <artifactId>cdm-java</artifactId>
                <version>${project.parent.version}</version>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.finos.cdm</groupId>
            <artifactId>cdm-java</artifactId>
        </dependency>
        <dependency>
            <groupId>org.finos.rune-common</groupId>
            <artifactId>rune-common</artifactId>
        </dependency>
        <dependency>
            <groupId>org.finos.rune-testing</groupId>
            <artifactId>rune-testing</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.mockito</groupId>
            <artifactId>mockito-core</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.hamcrest</groupId>
            <artifactId>hamcrest</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
        </dependency>
    </dependencies>
</project>

Party builder

Two parties — a legal entity with an LEI, an account and a business unit, and a natural person with a national identity number. Each carries its own external key, so both can sit in one document without their references colliding.

snippet 79 lines
//Party Legal Entity
Party party = Party.builder()
        .setMeta(MetaFields.builder()
                .setExternalKey("partyKey123"))
        .setPartyId(List.of(PartyIdentifier.builder()
                .setIdentifierType(PartyIdentifierTypeEnum.LEI)
                .setIdentifier(FieldWithMetaString.builder()
                        .setMeta(MetaFields.builder()
                                .setExternalKey("partyIdKey123"))
                        .setValue("PartyLEI123"))))
        .setAccount(Account.builder()
                .setAccountNumber(FieldWithMetaString.builder()
                        .setValue("PartyAccountNumber"))
                .setAccountName(FieldWithMetaString.builder()
                        .setValue("PartyAccountName")))
        .setNameValue("PartyName123")
        .setBusinessUnit(List.of(BusinessUnit.builder()
                        .setName("NY_BANK_CAPITAL_MARKETS")
                .setIdentifier(Identifier.builder()
                        .setAssignedIdentifier(List.of(AssignedIdentifier.builder()
                                .setIdentifierValue("NY_BANK_CAPITAL_MARKETS_IDENTIFIER")))
                        .setIssuer(FieldWithMetaString.builder()
                                .setValue("NY_BANK")))
                .setContactInformation(ContactInformation.builder()
                        .setAddress(List.of(Address.builder()
                                .setCountry(
                                        FieldWithMetaString.builder()
                                                .setValue("United States"))
                                .setStreet(List.of("1 Wall Street"))
                                .setCity("New York")
                                .setState("NY")
                                .setPostalCode("10001"))))))
        .build();


//Party as Person
Party person = Party.builder()
        .setMeta(MetaFields.builder()
                .setExternalKey("partyKey456"))
        .setPartyId(List.of(PartyIdentifier.builder()
                .setIdentifierType(PartyIdentifierTypeEnum.LEI)
                .setIdentifier(FieldWithMetaString.builder()
                        .setMeta(MetaFields.builder()
                                .setExternalKey("partyIdKey456"))
                        .setValue("PartyLEI456"))))
        .setPerson(List.of(NaturalPerson.builder()
                .setPersonId(List.of(FieldWithMetaPersonIdentifier.builder()
                        .setValue(PersonIdentifier.builder()
                                .setIdentifierType(PersonIdentifierTypeEnum.NIDN)
                                .setIdentifierValue("ID123"))))
                .setFirstName("FirstName")
                .setSurname("SurName")
                .setContactInformation(ContactInformation.builder()
                        .setAddress(List.of(Address.builder()
                                .setCountry(
                                        FieldWithMetaString.builder()
                                                .setValue("United States"))
                                .setStreet(List.of("1 Market Street"))
                                .setCity("San Francisco")
                                .setState("CA")
                                .setPostalCode("94105"))))))
        .setBusinessUnit(List.of(BusinessUnit.builder()
                .setName("SF_BANK_CAPITAL_MARKETS")
                .setIdentifier(Identifier.builder()
                        .setAssignedIdentifier(List.of(AssignedIdentifier.builder()
                                .setIdentifierValue("SF_BANK_CAPITAL_MARKETS_IDENTIFIER")))
                        .setIssuer(FieldWithMetaString.builder()
                                .setValue("SF_BANK")))
                .setContactInformation(ContactInformation.builder()
                        .setAddress(List.of(Address.builder()
                                .setCountry(
                                        FieldWithMetaString.builder()
                                                .setValue("United States"))
                                .setStreet(List.of("1 Market Street"))
                                .setCity("San Francisco")
                                .setState("CA")
                                .setPostalCode("94105"))))))

        .build();
Imports (16)
imports 16 lines
import cdm.base.staticdata.identifier.AssignedIdentifier;
import cdm.base.staticdata.identifier.Identifier;
import cdm.base.staticdata.party.Account;
import cdm.base.staticdata.party.Address;
import cdm.base.staticdata.party.BusinessUnit;
import cdm.base.staticdata.party.ContactInformation;
import cdm.base.staticdata.party.NaturalPerson;
import cdm.base.staticdata.party.Party;
import cdm.base.staticdata.party.PartyIdentifier;
import cdm.base.staticdata.party.PartyIdentifierTypeEnum;
import cdm.base.staticdata.party.PersonIdentifier;
import cdm.base.staticdata.party.PersonIdentifierTypeEnum;
import cdm.base.staticdata.party.metafields.FieldWithMetaPersonIdentifier;
import com.rosetta.model.metafields.FieldWithMetaString;
import com.rosetta.model.metafields.MetaFields;
import java.util.List;

Types used: Party · PartyIdentifier · Account · BusinessUnit · Identifier · NaturalPerson · PersonIdentifier · Address

Serialization and deserialization

Round-trips an existing tradeState through JSON. Both calls throw JsonProcessingException.

CDM 7 Rune JSON — the default from CDM 7. 6 lines
//Serialize
RuneJsonObjectMapper runeJsonObjectMapper = new RuneJsonObjectMapper();
String tradeStateJson = runeJsonObjectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(tradeState);

//Deserialize
TradeState tsObj = runeJsonObjectMapper.readValue(tradeStateJson, TradeState.class);
Imports (2)
imports 2 lines
import cdm.event.common.TradeState;
import org.finos.rune.mapper.RuneJsonObjectMapper;

Types used: TradeState

Validation

Runs the model’s own validation rules over an object and reports what failed. Needs a Guice injector built from CdmRuntimeModule.

snippet 9 lines
RosettaTypeValidator validateProcessor = injector.getInstance(RosettaTypeValidator.class);
ValidationReport validationReport = validateProcessor.runProcessStep(BusinessEvent.class, businessEvent);
validationReport.logReport();
if (validationReport.success()) {
    System.out.println("Validator Success");
} else {
    validationReport.validationFailures().forEach(f ->
            System.out.println(f.getName() + ": " + f.getFailureReason()));
}
Imports (3)
imports 3 lines
import cdm.event.common.BusinessEvent;
import com.regnosys.rosetta.common.validation.RosettaTypeValidator;
import com.regnosys.rosetta.common.validation.ValidationReport;

Types used: BusinessEvent

Qualification

Asks the model what an object is — which product or event type its economics match — and writes the answer back into the object. Takes a builder rather than a built object, so call toBuilder() on the way in; wfs here is a WorkflowStep from a function call. Needs the same Guice injector as validation.

snippet 9 lines
// Qualify
QualifyProcessorStep qualifyProcessor = injector.getInstance(QualifyProcessorStep.class);
QualificationReport report = qualifyProcessor.runProcessStep(WorkflowStep.class, wfs.toBuilder());
report.logReport();

WorkflowStep qualifiedWorkflowStep = (WorkflowStep) report.getResultObject().build();

RuneJsonObjectMapper objectMapper = new RuneJsonObjectMapper();
LOGGER.info(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(qualifiedWorkflowStep));
Imports (4)
imports 4 lines
import cdm.event.workflow.WorkflowStep;
import com.regnosys.rosetta.common.postprocess.qualify.QualificationReport;
import com.regnosys.rosetta.common.postprocess.qualify.QualifyProcessorStep;
import org.finos.rune.mapper.RuneJsonObjectMapper;

Types used: WorkflowStep

Execute a Rune function

Calls one of the model’s own functions. Create_WorkflowStep takes eight arguments in declaration order — message information, timestamps, identifiers, parties, accounts, the previous step, the action and the business event — and optional ones can be null.

snippet 5 lines
Create_WorkflowStep createWorkflowStep = new Create_WorkflowStep.Create_WorkflowStepDefault();
injector.injectMembers(createWorkflowStep);
WorkflowStep wfs = createWorkflowStep.evaluate(null, eventTimestampList,
        identifierList, null, null, null, ActionEnum.NEW, businessEventResult);
//Action types New, Cancel, Correct
Imports (3)
imports 3 lines
import cdm.event.common.ActionEnum;
import cdm.event.workflow.WorkflowStep;
import cdm.event.workflow.functions.Create_WorkflowStep;

Types used: WorkflowStep · BusinessEvent · EventTimestamp · Identifier · ActionEnum

Post-processing

Run after a function returns and before build(). Four steps in order, mutating the builder in place: rekey, update temporary keys, resolve references, qualify. Covers on its own what the Rekey and Qualification sections do a step at a time — calling those as well as this one repeats work. Despite the name, WorkflowPostProcessor is not WorkflowStep-specific: it takes any root type, a Trade here. PostProcessor is an interface and nothing in CdmRuntimeModule binds it, hence the module — ask for WorkflowPostProcessor by its concrete class instead and you can drop it.

snippet 11 lines
Injector injector = Guice.createInjector(new CdmRuntimeModule(), new AbstractModule() {
    @Override
    protected void configure() {
        bind(PostProcessor.class).to(WorkflowPostProcessor.class);
    }
});

PostProcessor postProcessor = injector.getInstance(PostProcessor.class);
Trade.TradeBuilder builder = trade.toBuilder();
postProcessor.postProcess(Trade.class, builder);
Trade processed = builder.build();
Imports (7)
imports 7 lines
import cdm.event.common.Trade;
import com.google.inject.AbstractModule;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.regnosys.rosetta.common.postprocess.WorkflowPostProcessor;
import com.rosetta.model.lib.process.PostProcessor;
import org.finos.cdm.CdmRuntimeModule;

Types used: Trade

Rekey

Generates the global key on every object that declares [metadata key], then repoints external references at the keys it generated. Neither step is injectable — construct them. Takes a builder, not a built object. This is the first thing post-processing does, so call it on its own only when you want the keys without the rest of the chain.

snippet 5 lines
ReKeyProcessStep reKey = new ReKeyProcessStep(new GlobalKeyProcessStep(NonNullHashCollector::new));

WorkflowStep.WorkflowStepBuilder builder = wfs.toBuilder();
reKey.runProcessStep(WorkflowStep.class, builder);
WorkflowStep wfsRekeyed = builder.build();
Imports (4)
imports 4 lines
import cdm.event.workflow.WorkflowStep;
import com.regnosys.rosetta.common.hashing.GlobalKeyProcessStep;
import com.regnosys.rosetta.common.hashing.NonNullHashCollector;
import com.regnosys.rosetta.common.hashing.ReKeyProcessStep;

Types used: WorkflowStep

Run these against any CDM release

Workbench loads 200+ releases and generates the Java for each, so you can see what a version change does to this code before you commit to it.

CDM Workbench
Written against CDM 7.4.0 8 examples