The Chenile story · Part 15 of 15

Blueprints: author your own generator

A jgen blueprint is a small plugin — a JSON contract, an optional Java hook, and a template tree. Learn its anatomy and scaffold a brand-new blueprint with the jgen-blueprint blueprint (yes, really).

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" }
  ]
}
  • inputFields drive the interactive prompts and the sample file that jgen -g <name> emits. Types are STRING, BOOLEAN (entered as y/n, normalized to "true" when enabled), and FILE (must point to an existing file).
  • defaultValue may reference config placeholders like ${defaultServiceName} or ${defaultVersion}, resolved from the chosen config before prompting.
  • templateFolder names the template directory under src/main/resources.
  • initHook is 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.
  • .mustache contents — any `` inside a .mustache file is substituted; the .mustache suffix 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:

Four steps to a working blueprint
1. Edit 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 contract
  • bp-wfcustom — parses an XML file in its hook
  • bp-jgen-blueprint — a blueprint that writes blueprints
Where to look
Engine & model: 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.