Blueprints are the unit of reuse
jgen generates projects from blueprints, and the blueprints are not magic — each is a tiny, self-describing plugin you can read, copy and write yourself. Encoding a team’s conventions as a blueprint means every new service, query or interceptor starts correct: right structure, right dependencies, tests and diagrams already in place.
This chapter is the authoring guide: what a blueprint is made of, and how to create a new one.
Anatomy of a blueprint
Every blueprint lives in a bp-<name> Maven module with three parts:
1 · blueprint.json
The declarative contract: name, description, the input fields to prompt for, the template folder, and an optional init hook. Lives at src/main/resources/META-INF/blueprint.json.
2 · Init hook (optional)
A Java class implementing InitHook that computes extra template variables — capitalized names, or data parsed from an input file.
3 · Template tree
A real directory of Mustache templates. Placeholder names in files and folders get substituted; conditional folders/sections switch modules on and off.
jgen discovers blueprints by scanning the classpath for every META-INF/blueprint.json, so dropping a bp-* jar on the classpath registers a new blueprint automatically.
1 · The JSON contract
{
"name": "chenile-service",
"description": "Generates a Chenile Service",
"templateFolder": "service-template",
"initHook": "org.chenile.jgen.blueprint.service.InitServiceBlueprint",
"inputFields": [
{ "name": "service", "type": "STRING", "description": "Name of Service", "defaultValue": "${defaultServiceName}" },
{ "name": "jpa", "type": "BOOLEAN", "description": "Enable JPA", "defaultValue": "y" },
{ "name": "security","type": "BOOLEAN", "description": "Enable Security", "defaultValue": "n" }
]
}
inputFieldsdrive the interactive prompts and the sample file thatjgen -g <name>emits. Types areSTRING,BOOLEAN(entered asy/n, normalized to"true"when enabled), andFILE(must point to an existing file).defaultValuemay reference config placeholders like${defaultServiceName}or${defaultVersion}, resolved from the chosen config before prompting.templateFoldernames the template directory undersrc/main/resources.initHookis the fully-qualified class name of an optional hook.
2 · The init hook
The JSON is declarative; the hook is imperative. It implements InitHook and configures a BlueprintConfig — most commonly by setting a postInputCaptureHook that derives extra variables after the user’s answers are captured:
public class InitServiceBlueprint implements InitHook {
@Override
public void init(BlueprintConfig cfg) {
cfg.postInputCaptureHook = (Map<String,Object> map) -> {
String service = (String) map.get("service");
map.put("Service", CapUtils.capitalizeFirst(service)); // now templates can use
};
}
}
BlueprintConfig also exposes a postProcessHook (run after files are generated) and the blueprint’s name, description, templateFolder and inputFields. Simple blueprints just capitalize a name; advanced ones do real work — bp-wfcustom parses a workflow STM XML file and injects state/transition and test-case data into the map; bp-batch reads a batch-definition JSON and computes parent/child process metadata — all before a single template is rendered.
3 · The template tree
The template folder is an ordinary directory that jgen copies and renders:
service-template/
└── __service__/ ← folder name is a placeholder
├── Makefile.mustache ← ".mustache" files are rendered
├── pom.xml.mustache
├── __service__-api/ … ← nested placeholders
└── __service__-service/ …
Three rules cover almost everything:
- Placeholder paths —
__service__,__com__,__company__,__org__in file and folder names are replaced with the resolved values, so packages and module names come out right. .mustachecontents — any `` inside a.mustachefile is substituted; the.mustachesuffix is dropped on output.- Conditionals —
…/…toggle blocks of content, and marker folders such as%%gitInit=true%%toggle whole subtrees and post-actions (like initializing a git repo).
Write a new blueprint — with a blueprint
The fastest way to author one is the recursive jgen-blueprint blueprint, which scaffolds a complete bp-* module for you:
jgen
# choose: jgen-blueprint
# blueprintName: awesomething
That generates bp-awesomething/ with a ready blueprint.json, an InitAwesomethingBlueprint hook, and a starter template folder. From there:
blueprint.json — declare your inputFields and description.
2. Build your template tree — add files, use __placeholders__ in paths and / inside .mustache files.
3. (Optional) enrich the map in your InitHook — derive names or parse an input FILE.
4. Build the module and put its jar on jgen's classpath — it now appears in the menu and via jgen -g awesomething.
Why author blueprints
✅ Payoff
- Encode your conventions once; every new module inherits them
- New services start with tests, diagrams and the right dependencies
- Onboarding shrinks — "run jgen, pick the blueprint"
- Blueprints are versioned and shared like any other artifact
🧰 The built-ins to learn from
bp-service— the minimal pattern (capitalize a name)bp-mybatisQuery— a query module contractbp-wfcustom— parses an XML file in its hookbp-jgen-blueprint— a blueprint that writes blueprints
jgen-base (BlueprintConfig, InitHook, the classpath registry and file pipeline). CLI: jgen-cli (GenMain, InputCapture). Blueprints: the bp-* modules in chenile-gen/jgen. Emit a sample input for any blueprint with jgen -g <name> -o input.json.