CodamAIDocs
Topicdone

Code generation in the build

The build fetches the metadata from the hub, stores it as a YAML cache, and generates 15 classes per model from it. This page explains the flow and what is created when.

Variants
online (metadata via REST)offline (CODEGEN_OFFLINE, existing cache)skip fetching entirely (cdms.generator.fetch.skip)export from the hub as a ZIPper modelonce per buildonly the first time

What this is about

You do not write an entity, a DTO, or a controller for a model. The code generator does that in every build. It reads the metadata from the hub and generates all the classes a model needs, from the REST interface down to the database.

The generator is an annotation processor. That is a program the Java compiler calls while compiling, and it is allowed to write new source files itself. The compiler compiles those new files in the same run.

The flow

flowchart LR
    H[("Hub")]
    subgraph GS["Phase generate-sources"]
        F["Fetch metadata<br/>CdmsMetadataFetcher"]
        C[["YAML cache<br/>system, folders,<br/>models, enumerations"]]
        P["Maintain POM"]
    end
    subgraph CO["Phase compile"]
        G["Generator<br/>(annotation processor)"]
        J["javac"]
        E["Enhance entities<br/>(Hibernate)"]
    end
    subgraph PK["Phase package"]
        B["Spring Boot JAR<br/>or native image"]
    end
    H -- "REST + token" --> F
    F --> C
    F --> P
    C --> G
    G -- "target/generated-sources" --> J
    J --> E --> B

You inherit the whole flow from the parent cdms-app-parent. None of it appears in your own pom.xml. The profile becomes active as soon as src/main/java exists.

  1. 1
    Build→Keycloak
    gets a token (client credentials) using the client ID and secret from the configuration
  2. 2
    Build→Hub
    reads the system and fetches folders, models, fields, hooks, endpoints, and enumerations page by page
  3. 3
    Build
    puts everything together and writes the YAML cache: system.yaml, folders.yaml, models.yaml, enumerations.yaml
  4. 4
    Build
    maintains the pom.xml according to the system's settings, see project scaffold
  5. 5
    Generator
    reads the cache and generates the classes of all models into target/generated-sources/annotations/
    The generator reads only the cache, not any annotations in the code. The Java package of a class is the base package plus the folder path from the hub.
  6. 6
    Build
    compiles the generated code and your custom code together, and enhances the entities
    Result: Finished classes in target/classes, ready to package

Online, offline, skip

Where the generator gets its metadata

When: the normal case

The build gets a token and reads the metadata fresh from the hub. After that, the cache holds the current state.

Result: Code matches the model in the hub

When: CODEGEN_OFFLINE=true

No token, no call to the hub. The build requires all four cache files to be present and generates from them. If one is missing, it stops with a hint to build once with CODEGEN_OFFLINE=false. The POM is still maintained.

Result: Code matches the state in the cache

When: -Dcdms.generator.fetch.skip=true

The whole "fetch metadata" step is skipped, and so is the POM maintenance. The generator then only needs models.yaml in the cache. This lets a fresh project scaffold build the first time, without a secret.

Result: Code matches the state in the cache

When: you want to create the cache without a build

GET /api/rest/cms/system/{id}/export returns a ZIP with the four YAML files. Unzip it into the cache directory, and you can then build offline. The project scaffold also already contains this state under .cache/.

The configuration

The build needs a few settings. It looks for them in this order and takes the first match:

Where the build looks for a setting
  1. Build
    Environment variable
    CODEGEN_CLIENT_SECRET
  2. Build
    Derived environment variable
    CDMS_GENERATOR_CLIENT_SECRET
  3. Build
    JVM property
    -Dcdms.generator.clientSecret=…
  4. Build
    File in the project
    clientSecret=… in cdms-generator.properties
  5. otherwise the default value
SettingEnvironment variableDefault
Issuer of the generator’s token, with /realms/<realm>CODEGEN_KEYCLOAK_ISSUER– (required)
Client ID / secretCODEGEN_CLIENT_ID, CODEGEN_CLIENT_SECRET– (required)
Hub addressCODEGEN_CDMS_URLset in the project scaffold
System IDCODEGEN_SYSTEM_ID– (required)
Cache directoryCODEGEN_CACHE_DIRtarget/cache (.cache in the project scaffold)
offlineCODEGEN_OFFLINEfalse
Base packageCODEGEN_BASE_PACKAGEcom.codamai.cdms
Maintain POMCODEGEN_POM_GENERATEtrue

Only the online path needs the required settings. Offline, the cache directory and the base package are enough.

What is generated per model

A normal model gets 15 classes, an abstract model gets 10. The name is always the model name plus a suffix. For a model Order:

The 15 classes of a model by layer
REST layer
Input
  • OrderApi – the REST controller
  • OrderCreatePayload
  • OrderUpdatePayload
  • OrderPayload2DtoMapper
System layer
Business logic
  • OrderSystem – the flow of reading and writing
  • OrderDto
  • OrderMetaService – the metadata
  • OrderDto2EntityMapper
  • OrderMapperService
Authorization
Permissions
  • OrderAuthorizationLayer – checks roles
  • Order{Field}Filter – one per access filter
Persistence
Database
  • OrderEntity
  • OrderDatabase
  • OrderTupleMapperService
  • OrderEntity2DtoMapper
  • OrderMap2EntityMapper
CaseClasses
normal model15, plus one per access filter
singleton15. Api and System are the singleton variants without id in the path
abstract model10. System, MapperService, AuthorizationLayer, Database, TupleMapperService are missing because it has no objects of its own. Its Api searches across all subtypes
enumerationa Java enum with the values

How the shapes payload, DTO, entity, and meta work together is shown in One model, four shapes.

What is generated once per build and once ever

When is which file created?
FileLocationWhen
Classes per model and enumerationtarget/generated-sources/fresh in every build
RoleRegistryService, EntityRegistryService, JpaNativeHints, JacksonNativeHintstarget/generated-sources/once per build, across all models
Start.java, OpenApiConfiguration, NativeHintsConfigurationsrc/main/java/only if the file is missing, never again after that
pom.xml, the section between the cdms-managed markersProject rootupdated in every online or offline build

RoleRegistryService knows all roles and attributes of all models and provides them to CIAS as a declaration, see Declaring roles. EntityRegistryService knows all entities. The two …NativeHints tell the native image compiler which classes it needs at runtime via reflection.

Pitfalls

Sources in the code and the knowledge base
  • CDMS/cdms-parent – cdms-app-parent/pom.xml (profile cdms-application: exec-maven-plugin, maven-compiler-plugin, hibernate-enhance, spring-boot, native)
  • CDMS/cdms-generator – remote/CdmsMetadataFetcher, KeycloakTokenClient, CdmsApiClient, CdmsCacheWriter, CdmsGeneratorConfig
  • CDMS/cdms-generator – generator/CdmsProcessor, loader/CdmsYamlLoader, AbstractProcessor, api/*, system/*, security/*, persistence/*, enumeration/EnumProcessor
  • CDMS/cdms-generator – CdmsPomProcessor, CdmsPomMerger
  • hub-backend – services/SystemExportApi, SystemExportService
  • documentation/10-cdms-grundlagen/03-generierung.md
Search