Skip to content

Commit 2940ab7

Browse files
Add HbmBatchSchemaGenerator CLI tool for bulk HBM→OpenAPI conversion
Standalone command-line tool that scans a directory of *.hbm.xml files and produces a single OpenAPI 3.0 JSON document with read and write schemas for every entity found. Features: - Deterministic output (sorted file processing) - Per-entity console reporting (field count, relationship count) - Read schema: all fields, generated IDs marked readOnly - Write schema: excludes @GeneratedValue, @Version, custom audit - Exit codes for build tool integration (0=success, 1=args, 2=no input, 3=IO) - No database, SessionFactory, or compiled classes required Usage: java -cp ... HbmBatchSchemaGenerator <hbm-dir> <output.json> [annotations] Updated openapi-jpa-schema.xml documentation with batch generation section covering CLI usage, Ant integration, output format, and build pipeline positioning. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d118fb1 commit 2940ab7

4 files changed

Lines changed: 329 additions & 3 deletions

File tree

Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
20+
package org.apache.axis2.jpa.schema;
21+
22+
import com.fasterxml.jackson.databind.ObjectMapper;
23+
import com.fasterxml.jackson.databind.SerializationFeature;
24+
import com.fasterxml.jackson.databind.node.ObjectNode;
25+
26+
import java.io.File;
27+
import java.io.FileInputStream;
28+
import java.io.FileWriter;
29+
import java.io.IOException;
30+
import java.io.InputStream;
31+
import java.util.Arrays;
32+
import java.util.LinkedHashMap;
33+
import java.util.Map;
34+
35+
/**
36+
* Batch generator that scans a directory of Hibernate XML mapping files
37+
* ({@code *.hbm.xml}) and produces a single JSON file containing OpenAPI
38+
* 3.0 component schemas for every entity found.
39+
*
40+
* <p>Designed to run as a standalone build tool — invoked from Ant, Maven,
41+
* Gradle, or the command line. The output file is a self-contained OpenAPI
42+
* {@code components/schemas} fragment that can be merged into an OpenAPI
43+
* specification or served directly by an Axis2 service.
44+
*
45+
* <h3>Command-line usage</h3>
46+
* <pre>{@code
47+
* java -cp axis2-jpa-schema.jar:jackson-*.jar \
48+
* org.apache.axis2.jpa.schema.HbmBatchSchemaGenerator \
49+
* src/main/resources \
50+
* build/openapi-schemas.json
51+
* }</pre>
52+
*
53+
* <h3>Ant integration</h3>
54+
* <pre>{@code
55+
* <target name="openapi-schema"
56+
* description="Generate OpenAPI schemas from HBM XML mappings">
57+
* <java classname="org.apache.axis2.jpa.schema.HbmBatchSchemaGenerator"
58+
* fork="true" failonerror="true">
59+
* <classpath>
60+
* <fileset dir="lib" includes="axis2-jpa-schema*.jar,jackson-*.jar,commons-logging*.jar"/>
61+
* </classpath>
62+
* <arg value="src/main/resources"/>
63+
* <arg value="build/openapi-schemas.json"/>
64+
* </java>
65+
* </target>
66+
* }</pre>
67+
*
68+
* <h3>Output format</h3>
69+
* <pre>{@code
70+
* {
71+
* "openapi": "3.0.1",
72+
* "info": {
73+
* "title": "Generated from 146 HBM XML mappings",
74+
* "version": "1.0.0"
75+
* },
76+
* "components": {
77+
* "schemas": {
78+
* "CompanyBO": { "type": "object", "properties": { ... } },
79+
* "CompanyBOWrite": { "type": "object", "properties": { ... } },
80+
* "DepartmentBO": { ... },
81+
* "DepartmentBOWrite": { ... },
82+
* ...
83+
* }
84+
* }
85+
* }
86+
* }</pre>
87+
*
88+
* <p>Each entity produces two schemas: a read schema (all fields, IDs
89+
* marked {@code readOnly}) and a write schema (excludes
90+
* {@code @GeneratedValue} IDs, {@code @Version} fields, and any fields
91+
* annotated with custom write-exclude annotations).
92+
*
93+
* <h3>Exit codes</h3>
94+
* <ul>
95+
* <li>0 — success</li>
96+
* <li>1 — invalid arguments</li>
97+
* <li>2 — input directory does not exist or contains no HBM files</li>
98+
* <li>3 — I/O error writing output</li>
99+
* </ul>
100+
*/
101+
public class HbmBatchSchemaGenerator {
102+
103+
public static void main(String[] args) {
104+
if (args.length != 2) {
105+
System.err.println("Usage: HbmBatchSchemaGenerator <hbm-dir> <output-json>");
106+
System.err.println();
107+
System.err.println(" hbm-dir Directory containing *.hbm.xml files");
108+
System.err.println(" output-json Output file path (e.g., openapi-schemas.json)");
109+
System.err.println();
110+
System.err.println("Example:");
111+
System.err.println(" java -cp ... org.apache.axis2.jpa.schema.HbmBatchSchemaGenerator \\");
112+
System.err.println(" src/main/resources build/openapi-schemas.json");
113+
System.exit(1);
114+
}
115+
116+
File inputDir = new File(args[0]);
117+
File outputFile = new File(args[1]);
118+
119+
if (!inputDir.isDirectory()) {
120+
System.err.println("ERROR: Not a directory: " + inputDir.getAbsolutePath());
121+
System.exit(2);
122+
}
123+
124+
File[] hbmFiles = inputDir.listFiles((dir, name) ->
125+
name.endsWith(".hbm.xml"));
126+
if (hbmFiles == null || hbmFiles.length == 0) {
127+
System.err.println("ERROR: No *.hbm.xml files found in: " + inputDir.getAbsolutePath());
128+
System.exit(2);
129+
}
130+
131+
// Sort for deterministic output ordering
132+
Arrays.sort(hbmFiles);
133+
134+
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
135+
Map<String, ObjectNode> allSchemas = new LinkedHashMap<>();
136+
137+
int successCount = 0;
138+
int errorCount = 0;
139+
140+
for (File hbmFile : hbmFiles) {
141+
try (InputStream is = new FileInputStream(hbmFile)) {
142+
EntitySchemaModel model = introspector.introspect(is, hbmFile.getName());
143+
if (model == null) {
144+
System.err.println(" SKIP " + hbmFile.getName() + " (no entity found)");
145+
continue;
146+
}
147+
148+
Map<String, ObjectNode> schemas = JpaSchemaGenerator.generateBothSchemas(model);
149+
allSchemas.putAll(schemas);
150+
successCount++;
151+
152+
int fieldCount = model.getFields().size();
153+
int relCount = (int) model.getFields().values().stream()
154+
.filter(f -> f.getRefEntityName() != null).count();
155+
System.out.println(" OK " + hbmFile.getName()
156+
+ " → " + model.getEntityName()
157+
+ " (" + fieldCount + " fields, " + relCount + " relationships)");
158+
159+
} catch (IOException e) {
160+
System.err.println(" FAIL " + hbmFile.getName() + " (I/O): " + e.getMessage());
161+
errorCount++;
162+
} catch (RuntimeException e) {
163+
System.err.println(" FAIL " + hbmFile.getName() + " (unexpected):");
164+
e.printStackTrace(System.err);
165+
errorCount++;
166+
}
167+
}
168+
169+
if (allSchemas.isEmpty()) {
170+
System.err.println("ERROR: No schemas generated from " + hbmFiles.length + " files");
171+
System.exit(2);
172+
}
173+
174+
// Build the OpenAPI 3.0 document structure
175+
ObjectMapper mapper = new ObjectMapper();
176+
mapper.enable(SerializationFeature.INDENT_OUTPUT);
177+
178+
ObjectNode root = mapper.createObjectNode();
179+
root.put("openapi", "3.0.1");
180+
181+
ObjectNode info = root.putObject("info");
182+
info.put("title", "Generated from " + successCount + " HBM XML mappings");
183+
info.put("version", "1.0.0");
184+
info.put("description",
185+
"Auto-generated OpenAPI component schemas from Hibernate XML mappings. "
186+
+ "Each entity has a read schema (all fields) and a write schema "
187+
+ "(excludes server-managed fields like generated IDs, version, "
188+
+ "and audit timestamps).");
189+
190+
ObjectNode components = root.putObject("components");
191+
ObjectNode schemas = components.putObject("schemas");
192+
for (Map.Entry<String, ObjectNode> entry : allSchemas.entrySet()) {
193+
schemas.set(entry.getKey(), entry.getValue());
194+
}
195+
196+
// Write output
197+
try {
198+
File parentDir = outputFile.getParentFile();
199+
if (parentDir != null && !parentDir.exists()) {
200+
parentDir.mkdirs();
201+
}
202+
try (FileWriter writer = new FileWriter(outputFile)) {
203+
writer.write(mapper.writeValueAsString(root));
204+
}
205+
} catch (IOException e) {
206+
System.err.println("ERROR: Failed to write output: " + e.getMessage());
207+
System.exit(3);
208+
}
209+
210+
System.out.println();
211+
System.out.println("Generated " + allSchemas.size() + " schemas"
212+
+ " (" + successCount + " entities × 2 [read + write])"
213+
+ " from " + hbmFiles.length + " HBM files");
214+
if (errorCount > 0) {
215+
System.out.println("WARNING: " + errorCount + " files had errors");
216+
}
217+
System.out.println("Output: " + outputFile.getAbsolutePath());
218+
}
219+
}

src/site/markdown/docs/mcp-examples.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ All curl examples below include paired MCP stdio equivalents.
9393

9494
---
9595

96-
## Live Examples (Tested on WildFly 32.0.1.Final, 2026-04-08)
96+
## Live Examples (Tested on WildFly 32)
9797

9898
### Portfolio Variance — 5 assets
9999

src/site/xdoc/docs/openapi-jpa-schema.xml

Lines changed: 108 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,114 @@ components convention:</p>
205205
</pre>
206206

207207
<p>The caller is responsible for ensuring that referenced entity schemas
208-
are also generated and added to the OpenAPI components section.</p>
208+
are also generated and added to the OpenAPI components section. The
209+
batch generator (below) handles this automatically by processing all
210+
HBM files in a directory at once.</p>
211+
212+
<h2 id="batch_generation">Batch Generation from HBM XML Directory</h2>
213+
214+
<p><code>HbmBatchSchemaGenerator</code> is a standalone command-line tool
215+
that scans a directory of <code>*.hbm.xml</code> files and produces a single
216+
OpenAPI 3.0 JSON document containing read and write schemas for every
217+
entity found. This is the recommended approach for projects with many
218+
HBM-mapped entities.</p>
219+
220+
<h3>Command Line</h3>
221+
<pre>
222+
java -cp axis2-jpa-schema.jar:jackson-databind.jar:jackson-core.jar:jackson-annotations.jar:commons-logging.jar \
223+
org.apache.axis2.jpa.schema.HbmBatchSchemaGenerator \
224+
src/main/resources \
225+
resources-axis2/openapi-schemas.json
226+
</pre>
227+
228+
<h3>Ant Integration</h3>
229+
230+
<p>The batch generator follows the same pattern as Hibernate Tools'
231+
<code>hbm2java</code> and <code>hbm2ddl</code> tasks — same HBM input
232+
directory, different output artifact:</p>
233+
234+
<pre>
235+
&lt;target name="openapi-schema"
236+
description="Generate OpenAPI schemas from HBM XML mappings"&gt;
237+
&lt;java classname="org.apache.axis2.jpa.schema.HbmBatchSchemaGenerator"
238+
fork="true" failonerror="true"&gt;
239+
&lt;classpath&gt;
240+
&lt;fileset dir="lib" includes="axis2-jpa-schema*.jar,jackson-*.jar,commons-logging*.jar"/&gt;
241+
&lt;/classpath&gt;
242+
&lt;!-- Input: directory containing *.hbm.xml --&gt;
243+
&lt;arg value="src/main/resources"/&gt;
244+
&lt;!-- Output: OpenAPI 3.0 JSON with all schemas --&gt;
245+
&lt;arg value="build/openapi-schemas.json"/&gt;
246+
&lt;/java&gt;
247+
&lt;/target&gt;
248+
</pre>
249+
250+
<h3>Output</h3>
251+
252+
<p>The tool produces a valid OpenAPI 3.0 document:</p>
253+
254+
<pre>
255+
{
256+
"openapi": "3.0.1",
257+
"info": {
258+
"title": "Generated from 146 HBM XML mappings",
259+
"version": "1.0.0"
260+
},
261+
"components": {
262+
"schemas": {
263+
"CompanyBO": { "type": "object", "properties": { ... } },
264+
"CompanyBOWrite": { "type": "object", "properties": { ... } },
265+
"DepartmentBO": { ... },
266+
"DepartmentBOWrite": { ... },
267+
"ProductBO": { ... },
268+
"ProductBOWrite": { ... }
269+
}
270+
}
271+
}
272+
</pre>
273+
274+
<p>Each entity produces two schemas:</p>
275+
<ul>
276+
<li><strong>EntityBO</strong> — read schema (GET responses): all fields,
277+
generated IDs marked <code>readOnly</code></li>
278+
<li><strong>EntityBOWrite</strong> — write schema (POST/PUT requests):
279+
excludes generated IDs, version fields, and custom audit annotations</li>
280+
</ul>
281+
282+
<p>Console output reports each entity processed:</p>
283+
<pre>
284+
OK CompanyBO.hbm.xml → CompanyBO (12 fields, 2 relationships)
285+
OK DepartmentBO.hbm.xml → DepartmentBO (58 fields, 8 relationships)
286+
OK ProductBO.hbm.xml → ProductBO (8 fields, 1 relationships)
287+
OK OrderBO.hbm.xml → OrderBO (15 fields, 3 relationships)
288+
SKIP HistoryData.hbm.xml (no entity found)
289+
290+
Generated 8 schemas (4 entities × 2 [read + write]) from 5 HBM files
291+
Output: /path/to/resources-axis2/openapi-schemas.json
292+
</pre>
293+
294+
<h3>Build Pipeline Position</h3>
295+
296+
<p>The schema generator reads HBM XML files directly — it does not
297+
depend on compiled Java classes, a running database, or a Hibernate
298+
<code>SessionFactory</code>. This means it can run:</p>
299+
300+
<ul>
301+
<li><strong>Before <code>codegen</code></strong> — HBM files are the
302+
source of truth; the generator reads them before Java classes are
303+
generated</li>
304+
<li><strong>In CI</strong> — no database connection required, so it
305+
runs in any CI environment</li>
306+
<li><strong>On schema change</strong> — regenerate whenever an HBM
307+
file changes; diff the output JSON to see exactly which fields or
308+
relationships changed</li>
309+
</ul>
310+
311+
<p>For projects that use Ant with Hibernate Tools, add
312+
<code>openapi-schema</code> to your existing build pipeline after
313+
code generation and before packaging. The schemas will reflect the
314+
same entity definitions that the generated Java code and DDL use.</p>
209315

210316
</body>
211317
</html>
318+

src/site/xdoc/docs/toc.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ Support</a></li>
194194
</li>
195195
<li><strong>23.7 <a href="mcp-examples.html">MCP Examples — Financial Services Benchmarks</a></strong>
196196
<ul>
197-
<li><a href="mcp-examples.html#Live_Examples_.28Tested_on_WildFly_32.0.1.Final.2C_2026-04-08.29">Live Examples (WildFly 32)</a></li>
197+
<li><a href="mcp-examples.html#Live_Examples_.28Tested_on_WildFly_32.29">Live Examples (WildFly 32)</a></li>
198198
<li><a href="mcp-examples.html#Demo_1.3A_Stress-test_.E2.80.94_.E2.80.9CWhat_if_correlations_spike.3F.E2.80.9D">Demo 1: Stress Test</a></li>
199199
<li><a href="mcp-examples.html#Demo_2.3A_Pre-trade_risk_.E2.80.94_.E2.80.9CShould_I_add_this_name.3F.E2.80.9D">Demo 2: Pre-Trade Risk</a></li>
200200
<li><a href="mcp-examples.html#Demo_3.3A_Convergence_.E2.80.94_.E2.80.9CHow_much_compute_do_I_actually_need.3F.E2.80.9D">Demo 3: Convergence</a></li>

0 commit comments

Comments
 (0)