Tentackle Wurblets — Interface-Level Code Generation¶
Overview and Motivation¶
tentackle-wurblets is the build-time code generation module that produces the interface side of a
Tentackle application: the constants, accessor declarations, relation methods, unique-domain-key types, model
documentation, and Data Transfer Objects that belong to a PDO
interface or operation interface. Together with its sibling
tentackle-persistence-wurblets — which generates the matching
implementation side — it turns the application
model into compilable Java.
The generators are wurblets: templates written for the Wurbelizer
engine. A wurblet is a .wrbl file mixing literal output with embedded Java; at build time the
wurbelizer-maven-plugin runs each wurblet (the compiled templates; "wurbiled" in Wurbelizer lingo), weaving the generated text into
guarded regions of your handwritten source files. The author keeps full control of the surrounding class; the
wurblet owns only the fenced block between its anchors, which it regenerates on every build.
A wurblet is invoked from an anchor comment inside the source file. The generated text is written into the
guarded region that immediately follows it (delimited by NetBeans-style //GEN-BEGIN:<tag> … //GEN-END:<tag>
fold markers) and re-derived on every build:
public interface CustomerPersistence extends PersistentDomainObject<Customer> {
// @wurblet membernames AttributeNames
//<editor-fold ... desc="code 'membernames' generated by wurblet AttributeNames">//GEN-BEGIN:membernames
// ... generated AN_/RN_ constants appear here ...
//</editor-fold>//GEN-END:membernames
// @wurblet methods Methods
//<editor-fold ... desc="code 'methods' generated by wurblet Methods">//GEN-BEGIN:methods
// ... generated getter/setter declarations ...
//</editor-fold>//GEN-END:methods
}
The first token after @wurblet is the tag (used to name the generated element and as the guard name); the
second is the wurblet name (the template to run); the rest are options and arguments. The author writes only the
anchor line — the fold markers and their contents are managed by the wurbelizer. A tag-less wurblet such as
Inject instead uses the pseudo-tags < or > to fill an inline /*@*/…/*@*/ region, e.g.
@TableName(/*@*/"customer"/*@*/) // @wurblet < Inject --string $tablename.
Why generate the interfaces?¶
Tentackle's PDO pattern emulates multiple inheritance by splitting every entity into cooperating domain and persistence interfaces and their injected implementations. That gives a clean separation of concerns but produces a large amount of strictly mechanical, model-derived boilerplate: column-name constants, typed getters and setters, relation navigation methods, the unique-domain-key record, and a documentation comment describing how the entity relates to the rest of the model. Writing this by hand would be tedious and error-prone, and it would drift out of sync with the model. Wurblets keep it correct by construction and regenerate it on every build, while leaving the developer free to add custom code outside the guarded regions.
Design principles¶
- Single source of truth. Everything a wurblet emits is derived from the
model (and the data-type / backend information
from
tentackle-sql), so generated and handwritten code can never disagree about column names, sizes, or types. - Woven, not owned. Generated code lives inside the developer's own files within guarded regions, so a class can freely mix generated and bespoke members. The build re-derives only the fenced blocks.
- Interface vs. implementation split. This module generates declarations that go into PDO/operation
interfaces (and DTOs);
tentackle-persistence-wurbletsgenerates the executable persistence implementation. The split mirrors the runtime separation of the domain/persistence layers. - Backend-aware validation. Wurblets know which database backends the project targets and can refuse to generate code that uses a feature an active backend does not support.
How a Wurblet Is Built and Run¶
The module is itself produced by the Wurbelizer: the wurbelizer-maven-plugin's wurbile goal compiles every
.wrbl under src/main/wurblets into a Java wurblet class, which is then compiled normally and shipped in the
jar. At application build time, the same plugin (configured in the application's POM, usually via the
Tentackle Maven plugin) loads these
wurblets and runs them against the project sources.
Most wurblets share a small preamble pulled in from
header.incl, which imports the model and SQL APIs and declares
@{extends ModelWurblet} — making ModelWurblet the base class for the
generated wurblet. The DTO and Include wurblets extend their own bases (DTOWurblet, IncludeWurblet) instead.
The module is packaged with an
Automatic-Module-Nameoforg.tentackle.wurbletrather than an explicitmodule-info.java, because it is a build-time tool loaded on the wurbelizer plugin's classpath, not a runtime module of the application.
Phases¶
Some wurblets declare @{phase 2} (e.g. ModelComment and
UniqueDomainKey). Wurblets run in phase order, so phase-2 wurblets see the results of phase-1
generation. This matters when a generated comment or nested type needs constants or members emitted earlier in the
same file.
The Wurblets¶
All templates live under src/main/wurblets/org/tentackle/wurblet. Each carries its own usage documentation in
its leading @{comment ...}@ block.
| Wurblet | Generates | Notable options |
|---|---|---|
AttributeNames |
AN_* attribute-name and RN_* relation-name constants for the entity. |
--noif (add public static final when not inside an interface) |
ColumnNames |
CN_* database column-name constants, handling multi-column and backend-specific data types and single-table inheritance. |
|
ColumnLengths |
CL_* constants giving the maximum character length of sized String columns. |
--noif |
Methods |
Getter/setter declarations for every (non-muted, non-hidden) attribute, plus the selectByUniqueDomainKey declaration. |
--noudk (omit the UDK select method) |
Relations |
Relation navigation methods (getters/setters, n:m link methods) according to each relation's type, scope and read/write rules. | |
DomainMethods |
Domain-side unique-domain-key methods (isUniqueDomainKeyProvided, getUniqueDomainKeyType, findByUniqueDomainKey). |
--noif |
UniqueDomainKey |
The unique-domain-key type (a nested record) for root entities that define one. (phase 2) | |
ModelComment |
A Javadoc comment documenting the entity's referencing, composite and inheritance relationships. (phase 2) | |
DTO |
A complete Data Transfer Object from a property model. | see DTO below |
Include |
Inlines another file into the source. | --comment, --missingok, --translate (expand $variables), --delete |
Inject |
Injects literal text from the argument list — a tag-less wurblet that fills the gap between two empty /**/.../**/ comment markers. |
--string (wrap in double quotes) |
The DTO wurblet¶
DTO is the most feature-rich generator and the only one not tied
to a persistent entity. It produces a Data Transfer Object from a small property model, usually written as a
here-document (a "heap file" named .<filename>) inside the leading comment block of the DTO source file, or
inferred directly from a Java record. Each model line is <type> <name> <comment>, with single-character
prefixes selecting per-property behavior:
| Prefix | Meaning |
|---|---|
^ |
property is passed up to the super entity's constructor (non-builder mode) |
= |
mutable property — a setter is generated |
~ |
mutable and transient |
! |
builder mode: required property — build() throws if it was never set (implies immutable) |
+ |
generate a from<Name>(...) method returning a modified copy (a "frommer"; "wither" with --with) |
Comments may carry per-property annotations or an access scope in square brackets ([@Bindable(...)] [protected]),
and a leading [...] line defines global annotations applied to all properties (removable per property with
-@Anno). Major options include --builder/--builderScope/--builderPrefix (builder pattern),
--equals, --hashCode, --validate (requires Validateable; integrates with
validation), --names (name constants), --from/--with
(copy factories), --canonical (emit @Canonical* annotations for TRIP),
--nofinal (serialization-friendly non-final fields) and --nott (no Tentackle dependency in the generated code).
Key Concepts¶
ModelWurblet — the shared base¶
ModelWurblet extends the Wurbelizer's AbstractJavaWurblet
and is the workhorse behind every model-driven wurblet. Its responsibilities:
- Locate the target. From the source file it figures out whether the anchor sits in a PDO or an operation, an
interface or an (abstract) class, by inspecting service annotations
(
@DomainObjectService,@PersistentObjectService,@DomainOperationService,@PersistentOperationService), interfaceextendsclauses, and class definitions — exposed viagetPdoClassName(),isPdo(),isOperation(),isInterface(),isGenerified(),isPartOfInheritanceHierarchy(). - Load the model. In
run()it reads the current module's model directory plus any dependency models (otherModels), applies the sharedmodelDefaultsandentityAliases, registers the target backends for validation, and resolves theEntitythe wurblet operates on — turning model errors into wurbel termination so they don't clutter the generated sources. - Parse options. Arguments beginning with
--become options (getOption,getOptionArgs); the rest are positionalgetWurbletArgs(). Common options understood by all model wurblets are--method,--model,--remoteand--noremote. - Provide template helpers. A large toolkit used inside the
.wrblJava blocks: column-name/constant resolution (getColumnName,getColumnNameConstant,getEffectiveDataType), relation method-name builders, accessor-path code generation (createAccessorCode), doc-comment helpers, id/serial checks, andassertSupportedByBackends(...)to fail the build when a chosen feature isn't supported by every active backend.
Wurblet arguments — the query mini-language¶
Several generators (above all the persistence-side select/update/delete wurblets, but the grammar is defined
here) take a compact expression describing a WHERE clause, sorting, attribute updates, and eager-loading joins.
The parsing lives in WurbletArgumentParser and
WurbletArgument. An expression is a tree of operands joined
by AND (the default), OR and NOT, with parentheses for grouping, followed by optional sorting and load-join
terms:
Each WurbletArgument has one of four
types:
- property condition — an attribute (optionally reached through a relation path, optionally a single column of a multi-column type) constrained by a relational operator and value;
- property update — an attribute to be assigned;
- property sorting —
+ascending /-descending; - load join —
*marks a relation to eagerly load; join paths can themselves be filtered.
The expression model is supported by
WurbletArgumentExpression,
WurbletArgumentOperand,
WurbletArgumentOperator and
WurbletRelation, while
Join,
JoinPath and
JoinPathFactory model the eager-loading joins (including
joins reaching into aggregate components, described by
ComponentInfo).
Supporting classes¶
| Class | Role |
|---|---|
DTOWurblet |
Base class for the DTO wurblet — parses the property model and options. |
IncludeWurblet |
Base class for the Include wurblet (overrides run()). |
TentackleWurbletsModel |
@Service(Model.class) replacement that understands heap files and defers model-loading exceptions to the entity they relate to. |
ModelCommentSupport |
Helper logic for the ModelComment wurblet. |
AnnotationOption |
Parses the annotation modifiers carried by model attributes. |
CodeGenerator |
Functional interface for emitting nested structures. |
How It Fits Together¶
model (*.map / entity definitions) tentackle-sql (DataType, Backend)
│ │
└───────────────┬───────────────────────┘
▼
tentackle-wurblets ──► PDO / operation *interfaces*,
(this module) unique-domain-key types, DTOs,
AN_/RN_/CN_/CL_ constants, model docs
│
tentackle-persistence-wurblets ──► PDO / operation *implementations*
(selects, updates, deletes, caching, remoting)
During an application build the Tentackle Maven plugin
arranges for the wurbelizer plugin to run both wurblet sets over the sources. Because both read the same model and
the same DataType/Backend definitions, the generated
interfaces, implementations, and the DDL produced by the
SQL Maven plugin all agree on
the schema.
Module Dependencies¶
- Wurbelizer (
org.wurbelizer:wurbelizer) — the template engine providingAbstractJavaWurblet,AbstractWurblet, theWurblerruntime and the weaving of guarded regions. tentackle-model— the model API (Entity,Attribute,Relation,ModelDefaults, …) every model wurblet reads.tentackle-build-support— shared build-time utilities.- Transitively,
tentackle-sqlforDataTypeandBackend, used to resolve column types, sizes, and backend capabilities.
This is a build-time only module — it is on the wurbelizer plugin's classpath and is never shipped inside the running application.
Related Documentation¶
- Model Definition — the model the wurblets consume.
- PDO — Persistent Domain Objects — the interfaces these wurblets populate.
- Tentackle SQL —
DataType/Backendused during generation. - Tentackle Maven Plugin — drives wurblet execution during the build.
- Tentackle SQL Maven Plugin — generates DDL from the same model.
- Wurbelizer — How it works — the underlying code-generation engine.