You are viewing a plain text version of this content. The canonical link for it is here.
Posted to java-commits@axis.apache.org by ro...@apache.org on 2026/10/12 18:45:50 UTC
(axis-axis2-java-core) 07/08: Use a generic library domain for the jpa-schema test mappings
This is an automated email from the ASF dual-hosted git repository.
robertlazarski pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git
commit 0ec78e8e46bd268f21d76ca1232fcd3d1651e54c
Author: Robert Lazarski <ro...@gmail.com>
AuthorDate: Mon Oct 12 08:39:17 2026 -1000
Use a generic library domain for the jpa-schema test mappings
The HBM fixtures, the custom write-exclude annotation in the tests and the
examples in the javadoc and docs now use Library, Branch and Book entities
and an @AuditField marker. Coverage is unchanged: same mapping features,
same 40 tests.
Co-Authored-By: Claude Opus 5.5 <no...@anthropic.com>
---
.../axis2/jpa/schema/AnnotationIntrospector.java | 2 +-
.../apache/axis2/jpa/schema/EntitySchemaModel.java | 2 +-
.../axis2/jpa/schema/HbmBatchSchemaGenerator.java | 10 +-
.../axis2/jpa/schema/HbmXmlIntrospector.java | 16 +-
.../jpa/schema/HbmBatchSchemaGeneratorTest.java | 166 ++++++++--------
.../axis2/jpa/schema/JpaSchemaGeneratorTest.java | 188 +++++++++---------
modules/jpa-schema/src/test/resources/Book.hbm.xml | 59 ++++++
.../jpa-schema/src/test/resources/Branch.hbm.xml | 175 +++++++++++++++++
.../src/test/resources/DepartmentBO.hbm.xml | 213 ---------------------
.../{CompanyBO.hbm.xml => Library.hbm.xml} | 40 ++--
.../src/test/resources/StockExchangeBO.hbm.xml | 60 ------
src/site/xdoc/docs/openapi-jpa-schema.xml | 40 ++--
12 files changed, 466 insertions(+), 505 deletions(-)
diff --git a/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/AnnotationIntrospector.java b/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/AnnotationIntrospector.java
index 807662c0c8..579a9df8cd 100644
--- a/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/AnnotationIntrospector.java
+++ b/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/AnnotationIntrospector.java
@@ -65,7 +65,7 @@ import java.util.Set;
* <p>Usage:
* <pre>{@code
* AnnotationIntrospector introspector = new AnnotationIntrospector();
- * introspector.addWriteExcludeAnnotation("com.example.IgnoreChanges");
+ * introspector.addWriteExcludeAnnotation("com.example.audit.AuditField");
* EntitySchemaModel model = introspector.introspect(Product.class);
* }</pre>
*/
diff --git a/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/EntitySchemaModel.java b/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/EntitySchemaModel.java
index ce3d80aaa1..aadbff9956 100644
--- a/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/EntitySchemaModel.java
+++ b/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/EntitySchemaModel.java
@@ -57,7 +57,7 @@ public class EntitySchemaModel {
private boolean nullable = true;
private boolean generatedValue; // @GeneratedValue or <generator>
private boolean versionField; // @Version or <version>
- private boolean ignoredForWrite; // @IgnoreChanges, @Transient, or custom
+ private boolean ignoredForWrite; // @Transient or a registered write-exclude annotation
private boolean transientField; // @Transient — excluded from ALL schemas
private Integer maxLength; // @Column(length=) or hbm length attribute
private String refEntityName; // for relationships: target entity name
diff --git a/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/HbmBatchSchemaGenerator.java b/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/HbmBatchSchemaGenerator.java
index b6206e9f97..b548e2ddca 100644
--- a/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/HbmBatchSchemaGenerator.java
+++ b/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/HbmBatchSchemaGenerator.java
@@ -70,15 +70,15 @@ import java.util.Map;
* {
* "openapi": "3.0.1",
* "info": {
- * "title": "Generated from 146 HBM XML mappings",
+ * "title": "Generated from 3 HBM XML mappings",
* "version": "1.0.0"
* },
* "components": {
* "schemas": {
- * "CompanyBO": { "type": "object", "properties": { ... } },
- * "CompanyBOWrite": { "type": "object", "properties": { ... } },
- * "DepartmentBO": { ... },
- * "DepartmentBOWrite": { ... },
+ * "Library": { "type": "object", "properties": { ... } },
+ * "LibraryWrite": { "type": "object", "properties": { ... } },
+ * "Branch": { ... },
+ * "BranchWrite": { ... },
* ...
* }
* }
diff --git a/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/HbmXmlIntrospector.java b/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/HbmXmlIntrospector.java
index 9eb4a41fe4..9a1ecf75bb 100644
--- a/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/HbmXmlIntrospector.java
+++ b/modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/HbmXmlIntrospector.java
@@ -44,17 +44,17 @@ import java.util.Map;
*
* <p>Parses the Hibernate 3.0 DTD mapping format:
* <pre>{@code
- * <hibernate-mapping package="com.example.bo">
- * <class name="CompanyBO" table="`COMPANY`">
- * <id name="id" type="java.lang.Long" column="`companyID`">
+ * <hibernate-mapping package="org.example.library">
+ * <class name="Library" table="`LIBRARY`">
+ * <id name="id" type="java.lang.Long" column="`libraryID`">
* <generator class="native"/>
* </id>
* <version name="version" type="java.lang.Long" column="OBJ_VERSION"/>
* <property name="name" type="java.lang.String" not-null="true"/>
- * <many-to-one name="createdUser" class="...UserBO" column="`createdUserID`"/>
- * <set name="departments" cascade="all-delete-orphan">
- * <key column="`companyID`"/>
- * <one-to-many class="...DepartmentBO"/>
+ * <many-to-one name="createdBy" class="...Member" column="`createdByID`"/>
+ * <set name="branches" cascade="all-delete-orphan">
+ * <key column="`libraryID`"/>
+ * <one-to-many class="...Branch"/>
* </set>
* </class>
* </hibernate-mapping>
@@ -108,7 +108,7 @@ public class HbmXmlIntrospector {
* Parse a .hbm.xml file and produce an {@link EntitySchemaModel}.
*
* @param inputStream the .hbm.xml file content
- * @param resourceName name for logging (e.g. "CompanyBO.hbm.xml")
+ * @param resourceName name for logging (e.g. "Library.hbm.xml")
* @return the schema model, or null on parse failure
*/
public EntitySchemaModel introspect(InputStream inputStream, String resourceName) {
diff --git a/modules/jpa-schema/src/test/java/org/apache/axis2/jpa/schema/HbmBatchSchemaGeneratorTest.java b/modules/jpa-schema/src/test/java/org/apache/axis2/jpa/schema/HbmBatchSchemaGeneratorTest.java
index a999f76e75..c654081e38 100644
--- a/modules/jpa-schema/src/test/java/org/apache/axis2/jpa/schema/HbmBatchSchemaGeneratorTest.java
+++ b/modules/jpa-schema/src/test/java/org/apache/axis2/jpa/schema/HbmBatchSchemaGeneratorTest.java
@@ -36,38 +36,38 @@ import static org.junit.Assert.*;
* Tests for {@link HbmBatchSchemaGenerator} — the CLI batch tool that
* converts a directory of HBM XML files into an OpenAPI 3.0 schemas JSON.
*
- * <p>Uses test HBM files on the classpath: CompanyBO.hbm.xml,
- * DepartmentBO.hbm.xml, StockExchangeBO.hbm.xml.
+ * <p>Uses test HBM files on the classpath: Library.hbm.xml,
+ * Branch.hbm.xml, Book.hbm.xml.
*/
public class HbmBatchSchemaGeneratorTest {
private static final ObjectMapper mapper = new ObjectMapper();
// ═══════════════════════════════════════════════════════════════════════
- // StockExchangeBO introspection — validates a production-style HBM
+ // Book introspection — validates an HBM
// mapping with id+generator, version, not-null, many-to-one, set,
// nested column element
// ═══════════════════════════════════════════════════════════════════════
@Test
- public void testStockExchange_parsesEntity() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
- assertNotNull("StockExchangeBO.hbm.xml must be on classpath", is);
+ public void testBook_parsesEntity() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Book.hbm.xml")) {
+ assertNotNull("Book.hbm.xml must be on classpath", is);
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Book.hbm.xml");
assertNotNull(model);
- assertEquals("StockExchangeBO", model.getEntityName());
- assertEquals("STOCK_EXCHANGE", model.getTableName());
+ assertEquals("Book", model.getEntityName());
+ assertEquals("BOOK", model.getTableName());
}
}
@Test
- public void testStockExchange_idAndVersion() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+ public void testBook_idAndVersion() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Book.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Book.hbm.xml");
assertEquals(1, model.getIdFieldNames().size());
assertEquals("id", model.getIdFieldNames().get(0));
@@ -83,25 +83,25 @@ public class HbmBatchSchemaGeneratorTest {
}
@Test
- public void testStockExchange_requiredProperty() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+ public void testBook_requiredProperty() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Book.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Book.hbm.xml");
- EntitySchemaModel.FieldModel name = model.getFields().get("name");
+ EntitySchemaModel.FieldModel name = model.getFields().get("title");
assertNotNull(name);
assertEquals("string", name.getJsonSchemaType());
- assertFalse("name is not-null=true", name.isNullable());
+ assertFalse("title is not-null=true", name.isNullable());
}
}
@Test
- public void testStockExchange_nullableProperties() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+ public void testBook_nullableProperties() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Book.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Book.hbm.xml");
- for (String field : new String[]{"micCode", "country", "city", "website", "timeZoneName"}) {
+ for (String field : new String[]{"isbn", "language", "publisher", "edition", "summary"}) {
EntitySchemaModel.FieldModel f = model.getFields().get(field);
assertNotNull(field + " should exist", f);
assertTrue(field + " should be nullable", f.isNullable());
@@ -110,38 +110,38 @@ public class HbmBatchSchemaGeneratorTest {
}
@Test
- public void testStockExchange_manyToOneRelationship() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+ public void testBook_manyToOneRelationship() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Book.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Book.hbm.xml");
- EntitySchemaModel.FieldModel region = model.getFields().get("region");
- assertNotNull("region relationship should exist", region);
- assertEquals("object", region.getJsonSchemaType());
- assertEquals("RegionBO", region.getRefEntityName());
- assertFalse(region.isCollection());
+ EntitySchemaModel.FieldModel author = model.getFields().get("author");
+ assertNotNull("author relationship should exist", author);
+ assertEquals("object", author.getJsonSchemaType());
+ assertEquals("Author", author.getRefEntityName());
+ assertFalse(author.isCollection());
}
}
@Test
- public void testStockExchange_setCollection() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+ public void testBook_setCollection() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Book.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Book.hbm.xml");
- EntitySchemaModel.FieldModel closedDates = model.getFields().get("closedDates");
- assertNotNull("closedDates set should exist", closedDates);
- assertEquals("array", closedDates.getJsonSchemaType());
- assertTrue(closedDates.isCollection());
- assertEquals("ExchangeClosedDateBO", closedDates.getRefEntityName());
+ EntitySchemaModel.FieldModel loans = model.getFields().get("loans");
+ assertNotNull("loans set should exist", loans);
+ assertEquals("array", loans.getJsonSchemaType());
+ assertTrue(loans.isCollection());
+ assertEquals("Loan", loans.getRefEntityName());
}
}
@Test
- public void testStockExchange_nestedColumnNotNull() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+ public void testBook_nestedColumnNotNull() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Book.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Book.hbm.xml");
EntitySchemaModel.FieldModel deleted = model.getFields().get("deleted");
assertNotNull(deleted);
@@ -152,53 +152,53 @@ public class HbmBatchSchemaGeneratorTest {
}
@Test
- public void testStockExchange_readSchemaIncludesAll() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+ public void testBook_readSchemaIncludesAll() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Book.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Book.hbm.xml");
JsonNode readSchema = JpaSchemaGenerator.generateReadSchema(model);
JsonNode props = readSchema.get("properties");
assertTrue("read has id", props.has("id"));
assertTrue("read has version", props.has("version"));
- assertTrue("read has name", props.has("name"));
- assertTrue("read has region", props.has("region"));
- assertTrue("read has closedDates", props.has("closedDates"));
+ assertTrue("read has title", props.has("title"));
+ assertTrue("read has author", props.has("author"));
+ assertTrue("read has loans", props.has("loans"));
assertTrue("read has deleted", props.has("deleted"));
// ID is readOnly in read schema
assertTrue(props.get("id").has("readOnly"));
- // region is $ref
- assertEquals("#/components/schemas/RegionBO",
- props.get("region").get("$ref").asText());
+ // author is $ref
+ assertEquals("#/components/schemas/Author",
+ props.get("author").get("$ref").asText());
- // closedDates is array of $ref
- assertEquals("array", props.get("closedDates").get("type").asText());
- assertEquals("#/components/schemas/ExchangeClosedDateBO",
- props.get("closedDates").get("items").get("$ref").asText());
+ // loans is array of $ref
+ assertEquals("array", props.get("loans").get("type").asText());
+ assertEquals("#/components/schemas/Loan",
+ props.get("loans").get("items").get("$ref").asText());
}
}
@Test
- public void testStockExchange_writeSchemaExcludesServerManaged() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+ public void testBook_writeSchemaExcludesServerManaged() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Book.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Book.hbm.xml");
JsonNode writeSchema = JpaSchemaGenerator.generateWriteSchema(model);
JsonNode props = writeSchema.get("properties");
assertFalse("write excludes generated ID", props.has("id"));
assertFalse("write excludes version", props.has("version"));
- assertTrue("write includes name", props.has("name"));
- assertTrue("write includes region", props.has("region"));
- assertTrue("write includes closedDates", props.has("closedDates"));
+ assertTrue("write includes title", props.has("title"));
+ assertTrue("write includes author", props.has("author"));
+ assertTrue("write includes loans", props.has("loans"));
// Write schema $refs use "Write" suffix
- assertEquals("#/components/schemas/RegionBOWrite",
- props.get("region").get("$ref").asText());
+ assertEquals("#/components/schemas/AuthorWrite",
+ props.get("author").get("$ref").asText());
}
}
@@ -208,8 +208,8 @@ public class HbmBatchSchemaGeneratorTest {
@Test
public void testBatchGenerator_producesValidOpenApiJson() throws IOException {
- // Find the test resources directory (contains CompanyBO, DepartmentBO, StockExchangeBO)
- URL resourceUrl = getClass().getResource("/StockExchangeBO.hbm.xml");
+ // Find the test resources directory (contains Library, Branch, Book)
+ URL resourceUrl = getClass().getResource("/Book.hbm.xml");
assertNotNull("test resource must be on classpath", resourceUrl);
File resourceDir = new File(resourceUrl.getFile()).getParentFile();
@@ -237,36 +237,36 @@ public class HbmBatchSchemaGeneratorTest {
JsonNode schemas = root.get("components").get("schemas");
// Should have read + write schemas for each entity
- // At minimum: CompanyBO, DepartmentBO, StockExchangeBO (3 entities × 2 = 6 schemas)
+ // At minimum: Library, Branch, Book (3 entities × 2 = 6 schemas)
assertTrue("should have at least 6 schemas, got " + schemas.size(),
schemas.size() >= 6);
// Verify specific entities exist
- assertTrue("CompanyBO read schema", schemas.has("CompanyBO"));
- assertTrue("CompanyBO write schema", schemas.has("CompanyBOWrite"));
- assertTrue("StockExchangeBO read schema", schemas.has("StockExchangeBO"));
- assertTrue("StockExchangeBO write schema", schemas.has("StockExchangeBOWrite"));
- assertTrue("DepartmentBO read schema", schemas.has("DepartmentBO"));
- assertTrue("DepartmentBO write schema", schemas.has("DepartmentBOWrite"));
-
- // Spot-check StockExchangeBO schema content
- JsonNode exchangeRead = schemas.get("StockExchangeBO");
- assertEquals("object", exchangeRead.get("type").asText());
- assertTrue(exchangeRead.get("properties").has("name"));
- assertTrue(exchangeRead.get("properties").has("region"));
- assertTrue(exchangeRead.get("properties").has("closedDates"));
+ assertTrue("Library read schema", schemas.has("Library"));
+ assertTrue("Library write schema", schemas.has("LibraryWrite"));
+ assertTrue("Book read schema", schemas.has("Book"));
+ assertTrue("Book write schema", schemas.has("BookWrite"));
+ assertTrue("Branch read schema", schemas.has("Branch"));
+ assertTrue("Branch write schema", schemas.has("BranchWrite"));
+
+ // Spot-check Book schema content
+ JsonNode bookRead = schemas.get("Book");
+ assertEquals("object", bookRead.get("type").asText());
+ assertTrue(bookRead.get("properties").has("title"));
+ assertTrue(bookRead.get("properties").has("author"));
+ assertTrue(bookRead.get("properties").has("loans"));
// Write schema should not have id/version
- JsonNode exchangeWrite = schemas.get("StockExchangeBOWrite");
- assertFalse(exchangeWrite.get("properties").has("id"));
- assertFalse(exchangeWrite.get("properties").has("version"));
+ JsonNode bookWrite = schemas.get("BookWrite");
+ assertFalse(bookWrite.get("properties").has("id"));
+ assertFalse(bookWrite.get("properties").has("version"));
}
@Test
public void testBatchGenerator_fieldCount() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/StockExchangeBO.hbm.xml")) {
+ try (InputStream is = getClass().getResourceAsStream("/Book.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "StockExchangeBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Book.hbm.xml");
// 11 properties + 1 id + 1 version + 1 many-to-one + 1 set = 15 fields
assertEquals("exact field count", 15, model.getFields().size());
@@ -274,7 +274,7 @@ public class HbmBatchSchemaGeneratorTest {
// Count relationships
long relCount = model.getFields().values().stream()
.filter(f -> f.getRefEntityName() != null).count();
- assertEquals("2 relationships (region + closedDates)", 2, relCount);
+ assertEquals("2 relationships (author + loans)", 2, relCount);
}
}
}
diff --git a/modules/jpa-schema/src/test/java/org/apache/axis2/jpa/schema/JpaSchemaGeneratorTest.java b/modules/jpa-schema/src/test/java/org/apache/axis2/jpa/schema/JpaSchemaGeneratorTest.java
index aa8176fe6a..a042251f0f 100644
--- a/modules/jpa-schema/src/test/java/org/apache/axis2/jpa/schema/JpaSchemaGeneratorTest.java
+++ b/modules/jpa-schema/src/test/java/org/apache/axis2/jpa/schema/JpaSchemaGeneratorTest.java
@@ -59,10 +59,10 @@ public class JpaSchemaGeneratorTest {
// Test entities (annotation-based)
// ═══════════════════════════════════════════════════════════════════════
- /** Custom "ignore for write" annotation — mimics project-specific audit markers. */
+ /** Custom "exclude from write" annotation — stands in for a project-specific audit marker. */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
- public @interface IgnoreChanges {}
+ public @interface AuditField {}
public enum ProductStatus { ACTIVE, INACTIVE, SUSPENDED }
@@ -77,7 +77,7 @@ public class JpaSchemaGeneratorTest {
@Column(nullable = false, length = 200)
private String name;
- private Double maxExposure;
+ private Double unitPrice;
@Enumerated(EnumType.STRING)
private ProductStatus status;
@@ -85,10 +85,10 @@ public class JpaSchemaGeneratorTest {
@Version
private Long objVersion;
- @IgnoreChanges
+ @AuditField
private Timestamp created;
- @IgnoreChanges
+ @AuditField
private BigDecimal createdUserID;
@Transient
@@ -196,11 +196,11 @@ public class JpaSchemaGeneratorTest {
@Test
public void testAnnotationIntrospector_customWriteExcludeAnnotation() {
AnnotationIntrospector introspector = new AnnotationIntrospector();
- introspector.addWriteExcludeAnnotation(IgnoreChanges.class.getName());
+ introspector.addWriteExcludeAnnotation(AuditField.class.getName());
EntitySchemaModel model = introspector.introspect(TestProduct.class);
EntitySchemaModel.FieldModel createdField = model.getFields().get("created");
- assertTrue("@IgnoreChanges should exclude from write", createdField.isIgnoredForWrite());
+ assertTrue("@AuditField should exclude from write", createdField.isIgnoredForWrite());
assertTrue(createdField.isExcludedFromWrite());
EntitySchemaModel.FieldModel createdUserId = model.getFields().get("createdUserID");
@@ -251,7 +251,7 @@ public class JpaSchemaGeneratorTest {
@Test
public void testReadSchema_includesAllFields() {
AnnotationIntrospector introspector = new AnnotationIntrospector();
- introspector.addWriteExcludeAnnotation(IgnoreChanges.class.getName());
+ introspector.addWriteExcludeAnnotation(AuditField.class.getName());
EntitySchemaModel model = introspector.introspect(TestProduct.class);
ObjectNode readSchema = JpaSchemaGenerator.generateReadSchema(model);
@@ -272,7 +272,7 @@ public class JpaSchemaGeneratorTest {
@Test
public void testWriteSchema_excludesServerManagedFields() {
AnnotationIntrospector introspector = new AnnotationIntrospector();
- introspector.addWriteExcludeAnnotation(IgnoreChanges.class.getName());
+ introspector.addWriteExcludeAnnotation(AuditField.class.getName());
EntitySchemaModel model = introspector.introspect(TestProduct.class);
ObjectNode writeSchema = JpaSchemaGenerator.generateWriteSchema(model);
@@ -281,13 +281,13 @@ public class JpaSchemaGeneratorTest {
// Write schema should exclude server-managed fields
assertFalse("@GeneratedValue ID excluded", props.has("productID"));
assertFalse("@Version excluded", props.has("objVersion"));
- assertFalse("@IgnoreChanges excluded", props.has("created"));
- assertFalse("@IgnoreChanges excluded", props.has("createdUserID"));
+ assertFalse("@AuditField excluded", props.has("created"));
+ assertFalse("@AuditField excluded", props.has("createdUserID"));
assertFalse("@Transient excluded", props.has("computedLabel"));
// Regular fields should be present
assertTrue(props.has("name"));
- assertTrue(props.has("maxExposure"));
+ assertTrue(props.has("unitPrice"));
assertTrue(props.has("status"));
assertTrue(props.has("department"));
assertTrue(props.has("lineItems"));
@@ -357,15 +357,15 @@ public class JpaSchemaGeneratorTest {
// ═══════════════════════════════════════════════════════════════════════
@Test
- public void testHbmXml_parsesCompanyEntity() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/CompanyBO.hbm.xml")) {
+ public void testHbmXml_parsesLibraryEntity() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Library.hbm.xml")) {
assertNotNull("Test HBM XML file must be on classpath", is);
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "CompanyBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Library.hbm.xml");
assertNotNull(model);
- assertEquals("CompanyBO", model.getEntityName());
+ assertEquals("Library", model.getEntityName());
assertFalse("Should have at least one ID field", model.getIdFieldNames().isEmpty());
assertNotNull("Should have a version field", model.getVersionFieldName());
assertTrue("Should have multiple fields", model.getFields().size() > 3);
@@ -374,9 +374,9 @@ public class JpaSchemaGeneratorTest {
@Test
public void testHbmXml_idFieldIsGeneratedAndRequired() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/CompanyBO.hbm.xml")) {
+ try (InputStream is = getClass().getResourceAsStream("/Library.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "CompanyBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Library.hbm.xml");
String idName = model.getIdFieldNames().get(0);
EntitySchemaModel.FieldModel idField = model.getFields().get(idName);
@@ -387,9 +387,9 @@ public class JpaSchemaGeneratorTest {
@Test
public void testHbmXml_versionFieldExcludedFromWrite() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/CompanyBO.hbm.xml")) {
+ try (InputStream is = getClass().getResourceAsStream("/Library.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "CompanyBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Library.hbm.xml");
EntitySchemaModel.FieldModel vf = model.getFields().get(model.getVersionFieldName());
assertNotNull(vf);
@@ -400,9 +400,9 @@ public class JpaSchemaGeneratorTest {
@Test
public void testHbmXml_generatesBothSchemas() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/CompanyBO.hbm.xml")) {
+ try (InputStream is = getClass().getResourceAsStream("/Library.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "CompanyBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Library.hbm.xml");
ObjectNode readSchema = JpaSchemaGenerator.generateReadSchema(model);
ObjectNode writeSchema = JpaSchemaGenerator.generateWriteSchema(model);
@@ -424,9 +424,9 @@ public class JpaSchemaGeneratorTest {
@Test
public void testHbmXml_propertyTypesMapCorrectly() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/CompanyBO.hbm.xml")) {
+ try (InputStream is = getClass().getResourceAsStream("/Library.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "CompanyBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Library.hbm.xml");
EntitySchemaModel.FieldModel nameField = model.getFields().get("name");
if (nameField != null) {
@@ -436,32 +436,32 @@ public class JpaSchemaGeneratorTest {
}
// ═══════════════════════════════════════════════════════════════════════
- // HBM XML — production-grade mapping (DepartmentBO: 70+ fields)
+ // HBM XML — large mapping (Branch: 50+ fields)
// ═══════════════════════════════════════════════════════════════════════
@Test
- public void testHbmXml_department_parsesLargeEntity() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/DepartmentBO.hbm.xml")) {
- assertNotNull("DepartmentBO.hbm.xml must be on classpath", is);
+ public void testHbmXml_branch_parsesLargeEntity() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Branch.hbm.xml")) {
+ assertNotNull("Branch.hbm.xml must be on classpath", is);
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "DepartmentBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Branch.hbm.xml");
assertNotNull(model);
- assertEquals("DepartmentBO", model.getEntityName());
- assertEquals("DEPARTMENT", model.getTableName());
+ assertEquals("Branch", model.getEntityName());
+ assertEquals("BRANCH", model.getTableName());
- // 70+ fields: properties + relationships + component fields
+ // 50+ fields: properties + relationships + component fields
assertTrue("Should have 40+ fields, got " + model.getFields().size(),
model.getFields().size() >= 40);
}
}
@Test
- public void testHbmXml_department_allTypesMapped() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/DepartmentBO.hbm.xml")) {
+ public void testHbmXml_branch_allTypesMapped() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Branch.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "DepartmentBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Branch.hbm.xml");
// String property
assertEquals("string", model.getFields().get("name").getJsonSchemaType());
@@ -475,86 +475,86 @@ public class JpaSchemaGeneratorTest {
assertEquals("boolean", model.getFields().get("active").getJsonSchemaType());
// Double property
- assertEquals("number", model.getFields().get("usageScore").getJsonSchemaType());
+ assertEquals("number", model.getFields().get("occupancyRate").getJsonSchemaType());
// Long property
- assertEquals("integer", model.getFields().get("loginCount1Week").getJsonSchemaType());
- assertEquals("int64", model.getFields().get("loginCount1Week").getJsonSchemaFormat());
+ assertEquals("integer", model.getFields().get("loansLast7Days").getJsonSchemaType());
+ assertEquals("int64", model.getFields().get("loansLast7Days").getJsonSchemaFormat());
// text type (maps to string)
- assertEquals("string", model.getFields().get("historicalDataOptions").getJsonSchemaType());
+ assertEquals("string", model.getFields().get("notes").getJsonSchemaType());
}
}
@Test
- public void testHbmXml_department_manyToOneRelationships() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/DepartmentBO.hbm.xml")) {
+ public void testHbmXml_branch_manyToOneRelationships() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Branch.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "DepartmentBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Branch.hbm.xml");
// 4 many-to-one relationships
- EntitySchemaModel.FieldModel company = model.getFields().get("company");
- assertNotNull("company relationship must be parsed", company);
- assertEquals("object", company.getJsonSchemaType());
- assertEquals("CompanyBO", company.getRefEntityName());
- assertFalse("company is not-null", company.isNullable());
+ EntitySchemaModel.FieldModel library = model.getFields().get("library");
+ assertNotNull("library relationship must be parsed", library);
+ assertEquals("object", library.getJsonSchemaType());
+ assertEquals("Library", library.getRefEntityName());
+ assertFalse("library is not-null", library.isNullable());
- EntitySchemaModel.FieldModel policy = model.getFields().get("passwordPolicy");
+ EntitySchemaModel.FieldModel policy = model.getFields().get("accessPolicy");
assertNotNull(policy);
- assertEquals("PasswordPolicyBO", policy.getRefEntityName());
- assertTrue("passwordPolicy is nullable", policy.isNullable());
+ assertEquals("AccessPolicy", policy.getRefEntityName());
+ assertTrue("accessPolicy is nullable", policy.isNullable());
}
}
@Test
- public void testHbmXml_department_collectionSets() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/DepartmentBO.hbm.xml")) {
+ public void testHbmXml_branch_collectionSets() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Branch.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "DepartmentBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Branch.hbm.xml");
// 4 <set> collections
- EntitySchemaModel.FieldModel users = model.getFields().get("users");
- assertNotNull("users collection must be parsed", users);
- assertEquals("array", users.getJsonSchemaType());
- assertTrue(users.isCollection());
- assertEquals("UserBO", users.getRefEntityName());
-
- EntitySchemaModel.FieldModel portfolios = model.getFields().get("portfolios");
- assertNotNull("portfolios collection must be parsed", portfolios);
- assertEquals("PortfolioBO", portfolios.getRefEntityName());
-
- EntitySchemaModel.FieldModel customFields = model.getFields().get("customFields");
- assertNotNull(customFields);
- assertEquals("CustomFieldBO", customFields.getRefEntityName());
-
- EntitySchemaModel.FieldModel ipRanges = model.getFields().get("ipAddressRanges");
- assertNotNull(ipRanges);
- assertEquals("IPAddressRangeBO", ipRanges.getRefEntityName());
+ EntitySchemaModel.FieldModel members = model.getFields().get("members");
+ assertNotNull("members collection must be parsed", members);
+ assertEquals("array", members.getJsonSchemaType());
+ assertTrue(members.isCollection());
+ assertEquals("Member", members.getRefEntityName());
+
+ EntitySchemaModel.FieldModel catalogItems = model.getFields().get("catalogItems");
+ assertNotNull("catalogItems collection must be parsed", catalogItems);
+ assertEquals("CatalogItem", catalogItems.getRefEntityName());
+
+ EntitySchemaModel.FieldModel events = model.getFields().get("events");
+ assertNotNull(events);
+ assertEquals("BranchEvent", events.getRefEntityName());
+
+ EntitySchemaModel.FieldModel reservations = model.getFields().get("reservations");
+ assertNotNull(reservations);
+ assertEquals("RoomReservation", reservations.getRefEntityName());
}
}
@Test
- public void testHbmXml_department_componentFlattened() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/DepartmentBO.hbm.xml")) {
+ public void testHbmXml_branch_componentFlattened() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Branch.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "DepartmentBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Branch.hbm.xml");
- // Component fields are flattened with prefix: departmentAddress.city
- EntitySchemaModel.FieldModel city = model.getFields().get("departmentAddress.city");
- assertNotNull("component field departmentAddress.city must be present", city);
+ // Component fields are flattened with prefix: branchAddress.city
+ EntitySchemaModel.FieldModel city = model.getFields().get("branchAddress.city");
+ assertNotNull("component field branchAddress.city must be present", city);
assertEquals("string", city.getJsonSchemaType());
- EntitySchemaModel.FieldModel zip = model.getFields().get("departmentAddress.zip");
+ EntitySchemaModel.FieldModel zip = model.getFields().get("branchAddress.zip");
assertNotNull(zip);
assertEquals("integer", zip.getJsonSchemaType());
}
}
@Test
- public void testHbmXml_department_nestedColumnNotNull() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/DepartmentBO.hbm.xml")) {
+ public void testHbmXml_branch_nestedColumnNotNull() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Branch.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "DepartmentBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Branch.hbm.xml");
// "deleted" uses nested <column not-null="true"> instead of attribute
EntitySchemaModel.FieldModel deleted = model.getFields().get("deleted");
@@ -566,10 +566,10 @@ public class JpaSchemaGeneratorTest {
}
@Test
- public void testHbmXml_department_schemaGeneration() throws IOException {
- try (InputStream is = getClass().getResourceAsStream("/DepartmentBO.hbm.xml")) {
+ public void testHbmXml_branch_schemaGeneration() throws IOException {
+ try (InputStream is = getClass().getResourceAsStream("/Branch.hbm.xml")) {
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
- EntitySchemaModel model = introspector.introspect(is, "DepartmentBO.hbm.xml");
+ EntitySchemaModel model = introspector.introspect(is, "Branch.hbm.xml");
ObjectNode readSchema = JpaSchemaGenerator.generateReadSchema(model);
ObjectNode writeSchema = JpaSchemaGenerator.generateWriteSchema(model);
@@ -581,9 +581,9 @@ public class JpaSchemaGeneratorTest {
assertTrue(readProps.has("id"));
assertTrue(readProps.has("version"));
assertTrue(readProps.has("name"));
- assertTrue(readProps.has("users"));
- assertTrue(readProps.has("company"));
- assertTrue(readProps.has("departmentAddress.city"));
+ assertTrue(readProps.has("members"));
+ assertTrue(readProps.has("library"));
+ assertTrue(readProps.has("branchAddress.city"));
// Write schema excludes generated ID and version
assertFalse(writeProps.has("id"));
@@ -591,16 +591,16 @@ public class JpaSchemaGeneratorTest {
// Write schema still has regular fields, relationships, collections
assertTrue(writeProps.has("name"));
- assertTrue(writeProps.has("users"));
- assertTrue(writeProps.has("company"));
- assertTrue(writeProps.has("departmentAddress.city"));
+ assertTrue(writeProps.has("members"));
+ assertTrue(writeProps.has("library"));
+ assertTrue(writeProps.has("branchAddress.city"));
// Relationship refs in read schema
- assertEquals("#/components/schemas/CompanyBO",
- readProps.get("company").get("$ref").asText());
- assertEquals("array", readProps.get("users").get("type").asText());
- assertEquals("#/components/schemas/UserBO",
- readProps.get("users").get("items").get("$ref").asText());
+ assertEquals("#/components/schemas/Library",
+ readProps.get("library").get("$ref").asText());
+ assertEquals("array", readProps.get("members").get("type").asText());
+ assertEquals("#/components/schemas/Member",
+ readProps.get("members").get("items").get("$ref").asText());
}
}
}
diff --git a/modules/jpa-schema/src/test/resources/Book.hbm.xml b/modules/jpa-schema/src/test/resources/Book.hbm.xml
new file mode 100644
index 0000000000..8a9c4c9b52
--- /dev/null
+++ b/modules/jpa-schema/src/test/resources/Book.hbm.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+ "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
+
+<!--
+ Mapping for batch generator testing: a book with its author and loans.
+ Exercises: id+generator, version, not-null properties, nullable properties,
+ many-to-one, set collection, nested column element with default.
+-->
+<hibernate-mapping package="org.example.library">
+ <class name="Book" table="BOOK" dynamic-update="false"
+ dynamic-insert="true" select-before-update="false" optimistic-lock="version"
+ lazy="true" batch-size="300">
+
+ <id name="id" type="java.lang.Long" column="bookID">
+ <generator class="native"/>
+ </id>
+ <version name="version" type="java.lang.Long" access="field"
+ column="OBJ_VERSION" />
+
+ <property name="title" column="title" type="java.lang.String"
+ unique="true" not-null="true" />
+ <property name="isbn" column="isbn" type="java.lang.String"
+ not-null="false" />
+ <property name="subtitle" column="subtitle" type="java.lang.String"
+ not-null="false" />
+ <property name="language" column="language" type="java.lang.String"
+ not-null="false" />
+ <property name="publisher" column="publisher" type="java.lang.String"
+ not-null="false" />
+ <property name="edition" column="edition" type="java.lang.String"
+ not-null="false" />
+ <property name="genre" column="genre" type="java.lang.String"
+ not-null="false" />
+ <property name="summary" column="summary" type="java.lang.String"
+ not-null="false" />
+ <property name="coverUrl" column="coverUrl" type="java.lang.String"
+ not-null="false" />
+ <property name="seriesName" column="seriesName" type="java.lang.String"
+ not-null="false" />
+
+ <many-to-one name="author"
+ class="org.example.library.Author"
+ column="authorID" cascade="save-update" />
+
+ <set name="loans" table="BOOK_LOAN" inverse="true" lazy="true">
+ <key>
+ <column name="bookID" not-null="true" />
+ </key>
+ <one-to-many class="org.example.library.Loan" />
+ </set>
+
+ <property name="deleted" type="java.lang.Boolean">
+ <column name="deleted" not-null="true" default="0" />
+ </property>
+
+ </class>
+</hibernate-mapping>
diff --git a/modules/jpa-schema/src/test/resources/Branch.hbm.xml b/modules/jpa-schema/src/test/resources/Branch.hbm.xml
new file mode 100644
index 0000000000..bb61389017
--- /dev/null
+++ b/modules/jpa-schema/src/test/resources/Branch.hbm.xml
@@ -0,0 +1,175 @@
+<?xml version="1.0"?>
+<!DOCTYPE hibernate-mapping PUBLIC
+ "-//Hibernate/Hibernate Mapping DTD 3.0//EN"
+ "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
+
+<!-- Large mapping (50+ fields): many property types, text and sql-type columns,
+ several many-to-one relationships, a component, set collections with filters,
+ nested column defaults and a placeholder in the generator. -->
+
+<hibernate-mapping package="org.example.library">
+ <class name="Branch"
+ table="`BRANCH`"
+ dynamic-update="false"
+ dynamic-insert="true"
+ select-before-update="false"
+ optimistic-lock="version"
+ lazy="true"
+ batch-size="300"
+ >
+
+ <id name="id" type="java.lang.Long" column="`branchID`">
+ <generator class="native">
+ ${branch_branchid_seq}
+ </generator>
+ </id>
+ <version name="version" type="java.lang.Long" access="field" column="OBJ_VERSION"/>
+
+ <property name="name" column="`name`"
+ type="java.lang.String" not-null="true" />
+ <property name="code" column="`code`"
+ type="java.lang.String" not-null="false" />
+ <property name="created" column="`created`"
+ type="timestamp" not-null="true" length="6" />
+ <property name="modified" column="`modified`"
+ type="timestamp" not-null="true" length="6" />
+ <property name="openedOn" column="`openedOn`"
+ type="timestamp" not-null="false" length="6" />
+ <property name="closedOn" column="`closedOn`"
+ type="timestamp" not-null="false" length="6" />
+ <property name="active" column="`active`"
+ type="java.lang.Boolean" not-null="false" />
+ <property name="wheelchairAccessible" column="`wheelchairAccessible`"
+ type="java.lang.Boolean" not-null="false" />
+ <property name="hasCafe" column="`hasCafe`"
+ type="java.lang.Boolean" not-null="false" />
+ <property name="hasParking" column="`hasParking`"
+ type="java.lang.Boolean" not-null="false" />
+ <property name="hasMakerSpace" column="`hasMakerSpace`"
+ type="java.lang.Boolean" not-null="false" />
+ <property name="eveningHours" column="`eveningHours`"
+ type="java.lang.Boolean" not-null="false" />
+ <property name="weekendHours" column="`weekendHours`"
+ type="java.lang.Boolean" not-null="false" />
+ <property name="notes" column="`notes`"
+ type="text" not-null="false" />
+
+ <!-- sql-type override: varchar(max) - no length constraint -->
+ <property name="description" type="java.lang.String">
+ <column name="`description`" sql-type="varchar(max)" not-null="false" />
+ </property>
+
+ <property name="websiteUrl" column="`websiteUrl`"
+ type="java.lang.String" not-null="false" length="8000" />
+ <property name="catalogUrl" column="`catalogUrl`"
+ type="java.lang.String" not-null="false" length="8000" />
+ <property name="occupancyRate" column="`occupancyRate`"
+ type="java.lang.Double" not-null="false" />
+ <property name="loansLast7Days" column="`loansLast7Days`"
+ type="java.lang.Long" not-null="false" />
+ <property name="loansLast30Days" column="`loansLast30Days`"
+ type="java.lang.Long" not-null="false" />
+ <property name="loansLast90Days" column="`loansLast90Days`"
+ type="java.lang.Long" not-null="false" />
+ <property name="loansLast365Days" column="`loansLast365Days`"
+ type="java.lang.Long" not-null="false" />
+ <property name="visitAvg7Days" column="`visitAvg7Days`"
+ type="java.lang.Double" not-null="false" />
+ <property name="visitAvg30Days" column="`visitAvg30Days`"
+ type="java.lang.Double" not-null="false" />
+ <property name="visitAvg90Days" column="`visitAvg90Days`"
+ type="java.lang.Double" not-null="false" />
+ <property name="visitAvg365Days" column="`visitAvg365Days`"
+ type="java.lang.Double" not-null="false" />
+ <property name="memberCount7Days" column="`memberCount7Days`"
+ type="java.lang.Long" not-null="false" />
+ <property name="memberCount30Days" column="`memberCount30Days`"
+ type="java.lang.Long" not-null="false" />
+ <property name="staffed" column="`staffed`"
+ type="java.lang.Boolean" not-null="true" />
+ <property name="lastInventory" column="`lastInventory`"
+ type="timestamp" not-null="false" length="6" />
+ <property name="createdByName" column="`createdByName`"
+ type="java.lang.String" not-null="false" />
+ <property name="modifiedByName" column="`modifiedByName`"
+ type="java.lang.String" not-null="false" />
+ <property name="requiresLibraryCard" column="`requiresLibraryCard`"
+ type="java.lang.Boolean" not-null="false" />
+ <property name="deleted" type="java.lang.Boolean">
+ <column name="deleted" not-null="true" default="0"/>
+ </property>
+
+ <property name="openingNotes" column="`openingNotes`"
+ type="text" not-null="false" />
+ <property name="accessibilityNotes" column="`accessibilityNotes`"
+ type="text" not-null="false" />
+ <property name="announcement" column="`announcement`"
+ type="text" not-null="false" />
+ <property name="announcementExpires" column="`announcementExpires`"
+ type="timestamp" not-null="false" length="6" />
+
+ <!-- Multiple many-to-one relationships -->
+ <many-to-one name="createdBy" column="`createdByID`"
+ class="org.example.library.Member" not-null="true"/>
+
+ <many-to-one name="modifiedBy" column="`modifiedByID`"
+ class="org.example.library.Member" not-null="true"/>
+
+ <many-to-one name="library" column="`libraryID`"
+ class="org.example.library.Library"
+ cascade="save-update" not-null="true"/>
+
+ <many-to-one name="accessPolicy" column="`accessPolicyID`"
+ class="org.example.library.AccessPolicy"
+ cascade="save-update" not-null="false"/>
+
+ <!-- Embedded component -->
+ <component name="branchAddress" class="org.example.library.Address">
+ <property name="address1" type="java.lang.String"
+ column="`branch_address1`" not-null="false"/>
+ <property name="address2" type="java.lang.String"
+ column="`branch_address2`" not-null="false"/>
+ <property name="city" type="java.lang.String"
+ column="`branch_city`" not-null="false"/>
+ <property name="state" type="java.lang.String"
+ column="`branch_state`" not-null="false"/>
+ <property name="zip" type="java.lang.Integer"
+ column="`branch_zip`" not-null="false"/>
+ <property name="country" type="java.lang.String"
+ column="`branch_country`" not-null="false"/>
+ <property name="phone" type="java.lang.String"
+ column="`branch_phone`" not-null="false"/>
+ </component>
+
+ <!-- Collection mappings (one-to-many via <set>) -->
+ <set name="reservations" fetch="subselect" table="`ROOM_RESERVATION`"
+ cascade="all-delete-orphan" inverse="true" lazy="true">
+ <key column="`branchID`"/>
+ <one-to-many class="org.example.library.RoomReservation"/>
+ <filter name="deletedFilter"/>
+ </set>
+
+ <set name="members" fetch="subselect" table="`MEMBER`"
+ cascade="all-delete-orphan" inverse="true" lazy="true">
+ <key column="`branchID`"/>
+ <one-to-many class="org.example.library.Member"/>
+ <filter name="deletedFilter"/>
+ </set>
+
+ <set name="events" fetch="subselect" table="`BRANCH_EVENT`"
+ cascade="all-delete-orphan" inverse="true" lazy="true">
+ <key column="`branchID`"/>
+ <one-to-many class="org.example.library.BranchEvent"/>
+ <filter name="deletedFilter"/>
+ </set>
+
+ <set name="catalogItems" fetch="subselect" table="`CATALOG_ITEM`"
+ cascade="all-delete-orphan" inverse="true" lazy="true">
+ <key column="`branchID`"/>
+ <one-to-many class="org.example.library.CatalogItem"/>
+ <filter name="deletedFilter"/>
+ </set>
+
+ <filter name="deletedFilter"/>
+ </class>
+</hibernate-mapping>
diff --git a/modules/jpa-schema/src/test/resources/DepartmentBO.hbm.xml b/modules/jpa-schema/src/test/resources/DepartmentBO.hbm.xml
deleted file mode 100644
index ae1f61ce0c..0000000000
--- a/modules/jpa-schema/src/test/resources/DepartmentBO.hbm.xml
+++ /dev/null
@@ -1,213 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE hibernate-mapping PUBLIC
- "-//Hibernate/Hibernate Mapping DTD//EN"
- "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd" >
-
-<!-- Sanitized production-grade HBM mapping: 70+ fields, multiple types,
- collections, components, sql-type overrides, nested column defaults.
- Tests that the parser handles real-world complexity. -->
-
-<hibernate-mapping package="com.example.bo">
- <class name="DepartmentBO"
- table="`DEPARTMENT`"
- dynamic-update="false"
- dynamic-insert="true"
- select-before-update="false"
- optimistic-lock="version"
- lazy="true"
- batch-size="300"
- >
-
- <id name="id" type="java.lang.Long" column="`departmentID`">
- <generator class="native">
- ${department_departmentid_seq}
- </generator>
- </id>
- <version name="version" type="java.lang.Long" access="field" column="OBJ_VERSION"/>
-
- <property name="name" column="`name`"
- type="java.lang.String"
- not-null="true" />
-
- <property name="codeName" column="`codeName`"
- type="java.lang.String"
- not-null="false" />
-
- <property name="created" column="`created`"
- type="timestamp" not-null="true" length="6" />
-
- <property name="modified" column="`modified`"
- type="timestamp" not-null="true" length="6" />
-
- <property name="trialBegin" column="`trialBegin`"
- type="timestamp" not-null="false" length="6" />
-
- <property name="trialEnd" column="`trialEnd`"
- type="timestamp" not-null="false" length="6" />
-
- <property name="active" column="`active`"
- type="java.lang.Boolean" not-null="false" />
-
- <property name="restrictDetails" column="`restrictDetails`"
- type="java.lang.Boolean" not-null="false" />
-
- <property name="dataDeletionRequested" column="`dataDeletionRequested`"
- type="java.lang.Boolean" not-null="false" />
-
- <property name="historicalDataApproved" column="`historicalDataApproved`"
- type="java.lang.Boolean" not-null="false" />
-
- <property name="historicalDataOptions" column="`historicalDataOptions`"
- type="text" not-null="false" />
-
- <property name="aggregateJobRunning" column="`aggregateJobRunning`"
- type="java.lang.Boolean" not-null="false" />
-
- <property name="aggregateJobQueued" column="`aggregateJobQueued`"
- type="java.lang.Boolean" not-null="false" />
-
- <property name="useBeta" column="`useBeta`"
- type="java.lang.Boolean" not-null="false" />
-
- <property name="isReportable" column="`isReportable`"
- type="java.lang.Boolean" not-null="false" />
-
- <property name="ssoEnabled" column="`ssoEnabled`"
- type="java.lang.Boolean" not-null="false" />
-
- <!-- sql-type override: nvarchar(max) — no length constraint -->
- <property name="ssoMetadataXML" type="java.lang.String">
- <column name="`ssoMetadataXML`" sql-type="nvarchar(max)" not-null="false" />
- </property>
-
- <property name="ssoLoginURL" column="`ssoLoginURL`"
- type="java.lang.String" not-null="false" length="8000" />
-
- <property name="ssoLogoutURL" column="`ssoLogoutURL`"
- type="java.lang.String" not-null="false" length="8000" />
-
- <property name="usageScore" column="`usageScore`"
- type="java.lang.Double" not-null="false" />
-
- <property name="loginCount1Week" column="`loginCount1Week`"
- type="java.lang.Long" not-null="false" />
- <property name="loginCount4Week" column="`loginCount4Week`"
- type="java.lang.Long" not-null="false" />
- <property name="loginCount12Week" column="`loginCount12Week`"
- type="java.lang.Long" not-null="false" />
- <property name="loginCount52Week" column="`loginCount52Week`"
- type="java.lang.Long" not-null="false" />
-
- <property name="loginAvg1Week" column="`loginAvg1Week`"
- type="java.lang.Double" not-null="false" />
- <property name="loginAvg4Week" column="`loginAvg4Week`"
- type="java.lang.Double" not-null="false" />
- <property name="loginAvg12Week" column="`loginAvg12Week`"
- type="java.lang.Double" not-null="false" />
- <property name="loginAvg52Week" column="`loginAvg52Week`"
- type="java.lang.Double" not-null="false" />
-
- <property name="userCount1Week" column="`userCount1Week`"
- type="java.lang.Long" not-null="false" />
- <property name="userCount4Week" column="`userCount4Week`"
- type="java.lang.Long" not-null="false" />
-
- <property name="nonLocked" column="`nonLocked`"
- type="java.lang.Boolean" not-null="true" />
-
- <property name="dataModified" column="`dataModified`"
- type="timestamp" not-null="false" length="6" />
-
- <property name="modifiedAppInstance" column="`modifiedAppInstance`"
- type="java.lang.String" not-null="false" />
-
- <property name="createdDBUserName" column="`createdDBUserName`"
- type="java.lang.String" not-null="false" />
-
- <property name="modifiedDBUserName" column="`modifiedDBUserName`"
- type="java.lang.String" not-null="false" />
-
- <property name="require2FactorAuth" column="`require2FactorAuth`"
- type="java.lang.Boolean" not-null="false" />
-
- <property name="deleted" type="java.lang.Boolean">
- <column name="deleted" not-null="true" default="0"/>
- </property>
-
- <property name="configurationOverrides" column="`configurationOverrides`"
- type="text" not-null="false" />
-
- <property name="layoutOverrides" column="`layoutOverrides`"
- type="text" not-null="false" />
-
- <property name="customMessage" column="`customMessage`"
- type="text" not-null="false" />
-
- <property name="customMessageExpiration" column="`customMessageExpiration`"
- type="timestamp" not-null="false" length="6" />
-
- <!-- Multiple many-to-one relationships -->
- <many-to-one name="createdUser" column="`createdUserID`"
- class="com.example.bo.UserBO" not-null="true"/>
-
- <many-to-one name="modifiedUser" column="`modifiedUserID`"
- class="com.example.bo.UserBO" not-null="true"/>
-
- <many-to-one name="company" column="`companyID`"
- class="com.example.bo.CompanyBO"
- cascade="save-update" not-null="true"/>
-
- <many-to-one name="passwordPolicy" column="`passwordPolicyID`"
- class="com.example.bo.PasswordPolicyBO"
- cascade="save-update" not-null="false"/>
-
- <!-- Embedded component -->
- <component name="departmentAddress" class="com.example.bo.component.Address">
- <property name="address1" type="java.lang.String"
- column="`department_address1`" not-null="false"/>
- <property name="address2" type="java.lang.String"
- column="`department_address2`" not-null="false"/>
- <property name="city" type="java.lang.String"
- column="`department_city`" not-null="false"/>
- <property name="state" type="java.lang.String"
- column="`department_state`" not-null="false"/>
- <property name="zip" type="java.lang.Integer"
- column="`department_zip`" not-null="false"/>
- <property name="country" type="java.lang.String"
- column="`department_country`" not-null="false"/>
- <property name="phone" type="java.lang.String"
- column="`department_phone`" not-null="false"/>
- </component>
-
- <!-- Collection mappings (one-to-many via <set>) -->
- <set name="ipAddressRanges" fetch="subselect" table="`IPADDRESSRANGE`"
- cascade="all-delete-orphan" inverse="true" lazy="true">
- <key column="`departmentID`"/>
- <one-to-many class="com.example.bo.IPAddressRangeBO"/>
- <filter name="deletedFilter"/>
- </set>
-
- <set name="users" fetch="subselect" table="`APPUSER`"
- cascade="all-delete-orphan" inverse="true" lazy="true">
- <key column="`departmentID`"/>
- <one-to-many class="com.example.bo.UserBO"/>
- <filter name="deletedFilter"/>
- </set>
-
- <set name="customFields" fetch="subselect" table="`CUSTOMFIELD`"
- cascade="all-delete-orphan" inverse="true" lazy="true">
- <key column="`departmentID`"/>
- <one-to-many class="com.example.bo.CustomFieldBO"/>
- <filter name="deletedFilter"/>
- </set>
-
- <set name="portfolios" fetch="subselect" table="`PORTFOLIO`"
- cascade="all-delete-orphan" inverse="true" lazy="true">
- <key column="`departmentID`"/>
- <one-to-many class="com.example.bo.PortfolioBO"/>
- <filter name="deletedFilter"/>
- </set>
-
- <filter name="deletedFilter"/>
- </class>
-</hibernate-mapping>
diff --git a/modules/jpa-schema/src/test/resources/CompanyBO.hbm.xml b/modules/jpa-schema/src/test/resources/Library.hbm.xml
similarity index 57%
rename from modules/jpa-schema/src/test/resources/CompanyBO.hbm.xml
rename to modules/jpa-schema/src/test/resources/Library.hbm.xml
index e001f7578c..1f7d8ca681 100644
--- a/modules/jpa-schema/src/test/resources/CompanyBO.hbm.xml
+++ b/modules/jpa-schema/src/test/resources/Library.hbm.xml
@@ -3,15 +3,17 @@
"-//Hibernate/Hibernate Mapping DTD 3.0//EN"
"http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd">
-<hibernate-mapping package="com.example.bo">
+<!-- Small mapping in quoted-identifier style: backtick-quoted table and columns,
+ id+generator, version, typed properties, many-to-one, set and component. -->
+<hibernate-mapping package="org.example.library">
- <class name="CompanyBO" table="`COMPANY`"
+ <class name="Library" table="`LIBRARY`"
dynamic-insert="true"
optimistic-lock="version"
lazy="true"
batch-size="300">
- <id name="id" type="java.lang.Long" column="`companyID`">
+ <id name="id" type="java.lang.Long" column="`libraryID`">
<generator class="native"/>
</id>
@@ -28,13 +30,13 @@
<property name="created" column="`created`"
type="timestamp" not-null="true" length="6"/>
- <property name="require2FactorAuth" column="`require2FactorAuth`"
+ <property name="requiresMembership" column="`requiresMembership`"
type="java.lang.Boolean" not-null="false"/>
- <property name="maxUsers" column="`maxUsers`"
+ <property name="maxMembers" column="`maxMembers`"
type="java.lang.Integer" not-null="false"/>
- <property name="annualFee" column="`annualFee`"
+ <property name="annualBudget" column="`annualBudget`"
type="java.lang.Double" not-null="false"/>
<property name="deleted" type="java.lang.Boolean">
@@ -42,27 +44,27 @@
</property>
<many-to-one
- name="createdUser"
- column="`createdUserID`"
- class="com.example.bo.UserBO"
+ name="createdBy"
+ column="`createdByID`"
+ class="org.example.library.Member"
not-null="true"/>
<many-to-one
- name="passwordPolicy"
- class="com.example.bo.PasswordPolicyBO"
- column="`passwordPolicyID`"
+ name="accessPolicy"
+ class="org.example.library.AccessPolicy"
+ column="`accessPolicyID`"
not-null="false"/>
- <set name="departments" table="`Department`"
+ <set name="branches" table="`BRANCH`"
inverse="true" cascade="all-delete-orphan" lazy="true">
- <key column="`companyID`"/>
- <one-to-many class="com.example.bo.DepartmentBO"/>
+ <key column="`libraryID`"/>
+ <one-to-many class="org.example.library.Branch"/>
</set>
- <component name="companyAddress" class="com.example.bo.component.Address">
- <property name="address1" type="java.lang.String" column="`company_address1`" not-null="false"/>
- <property name="city" type="java.lang.String" column="`company_city`" not-null="false"/>
- <property name="state" type="java.lang.String" column="`company_state`" not-null="false"/>
+ <component name="libraryAddress" class="org.example.library.Address">
+ <property name="address1" type="java.lang.String" column="`library_address1`" not-null="false"/>
+ <property name="city" type="java.lang.String" column="`library_city`" not-null="false"/>
+ <property name="state" type="java.lang.String" column="`library_state`" not-null="false"/>
</component>
</class>
diff --git a/modules/jpa-schema/src/test/resources/StockExchangeBO.hbm.xml b/modules/jpa-schema/src/test/resources/StockExchangeBO.hbm.xml
deleted file mode 100644
index 2ae97d4434..0000000000
--- a/modules/jpa-schema/src/test/resources/StockExchangeBO.hbm.xml
+++ /dev/null
@@ -1,60 +0,0 @@
-<?xml version="1.0"?>
-<!DOCTYPE hibernate-mapping PUBLIC
- "-//Hibernate/Hibernate Mapping DTD//EN"
- "http://www.hibernate.org/dtd/hibernate-mapping-3.0.dtd" >
-
-<!--
- Sanitized HBM mapping for batch generator testing.
- Models a stock exchange with trading hours, location, and relationships.
- Exercises: id+generator, version, not-null properties, nullable properties,
- many-to-one, set collection, nested column element with default.
--->
-<hibernate-mapping package="org.example.bo">
- <class name="StockExchangeBO" table="STOCK_EXCHANGE" dynamic-update="false"
- dynamic-insert="true" select-before-update="false" optimistic-lock="version"
- lazy="true" batch-size="300">
-
- <id name="id" type="java.lang.Long" column="exchangeID">
- <generator class="native"/>
- </id>
- <version name="version" type="java.lang.Long" access="field"
- column="OBJ_VERSION" />
-
- <property name="name" column="name" type="java.lang.String"
- unique="true" not-null="true" />
- <property name="micCode" column="micCode"
- type="java.lang.String" not-null="false" />
- <property name="operatingMicCode" column="operatingMicCode"
- type="java.lang.String" not-null="false" />
- <property name="country" column="country"
- type="java.lang.String" not-null="false" />
- <property name="isoCountryCode" column="isoCountryCode"
- type="java.lang.String" not-null="false" />
- <property name="city" column="city"
- type="java.lang.String" not-null="false" />
- <property name="website" column="website"
- type="java.lang.String" not-null="false" />
- <property name="openTimeLocal" column="openTimeLocal"
- type="java.lang.String" not-null="false" />
- <property name="closeTimeLocal" column="closeTimeLocal"
- type="java.lang.String" not-null="false" />
- <property name="timeZoneName" column="timeZoneName"
- type="java.lang.String" not-null="false" />
-
- <many-to-one name="region"
- class="org.example.bo.RegionBO"
- column="regionID" cascade="save-update" />
-
- <set name="closedDates" table="ExchangeClosed" inverse="true" lazy="true">
- <key>
- <column name="exchangeID" not-null="true" />
- </key>
- <one-to-many class="org.example.bo.ExchangeClosedDateBO" />
- </set>
-
- <property name="deleted" type="java.lang.Boolean">
- <column name="deleted" not-null="true" default="0" />
- </property>
-
- </class>
-</hibernate-mapping>
diff --git a/src/site/xdoc/docs/openapi-jpa-schema.xml b/src/site/xdoc/docs/openapi-jpa-schema.xml
index bc317d232b..9e2dc17047 100644
--- a/src/site/xdoc/docs/openapi-jpa-schema.xml
+++ b/src/site/xdoc/docs/openapi-jpa-schema.xml
@@ -135,17 +135,17 @@ Map<String, ObjectNode> schemas = JpaSchemaGenerator.generateBothSchemas(m
<h2 id="custom_annotations">Custom Audit Annotation Support</h2>
<p>Many enterprise codebases have project-specific annotations that mark
-fields as server-managed (e.g., <code>@IgnoreChanges</code> for audit
+fields as server-managed (e.g., an <code>@AuditField</code> marker for audit
timestamps, <code>@CreatedBy</code>, <code>@LastModifiedDate</code>).
Register these with the introspector to exclude them from write schemas:</p>
<pre>
AnnotationIntrospector introspector = new AnnotationIntrospector();
-introspector.addWriteExcludeAnnotation("com.example.IgnoreChanges");
+introspector.addWriteExcludeAnnotation("com.example.audit.AuditField");
introspector.addWriteExcludeAnnotation("com.example.audit.CreatedBy");
EntitySchemaModel model = introspector.introspect(Product.class);
-// Fields annotated with @IgnoreChanges or @CreatedBy are now
+// Fields annotated with @AuditField or @CreatedBy are now
// excluded from the write schema but present in the read schema.
</pre>
@@ -157,8 +157,8 @@ files and produces the same <code>EntitySchemaModel</code>:</p>
<pre>
HbmXmlIntrospector introspector = new HbmXmlIntrospector();
-try (InputStream is = getClass().getResourceAsStream("/DepartmentBO.hbm.xml")) {
- EntitySchemaModel model = introspector.introspect(is, "DepartmentBO.hbm.xml");
+try (InputStream is = getClass().getResourceAsStream("/Branch.hbm.xml")) {
+ EntitySchemaModel model = introspector.introspect(is, "Branch.hbm.xml");
ObjectNode readSchema = JpaSchemaGenerator.generateReadSchema(model);
}
</pre>
@@ -198,8 +198,8 @@ try (InputStream is = getClass().getResourceAsStream("/DepartmentBO.hbm.xml")) {
<li><strong>Schema generation</strong> (6 tests): read includes all fields, write excludes
server-managed fields, required array, relationship $refs, enum values,
generateBothSchemas</li>
- <li><strong>HBM XML introspection</strong> (10 tests): parses CompanyBO and DepartmentBO
- (70+ field production-grade entity), verifies type mapping, relationships,
+ <li><strong>HBM XML introspection</strong> (10 tests): parses Library and Branch
+ (a 50+ field entity), verifies type mapping, relationships,
collections, component flattening, nested column not-null</li>
</ul>
@@ -270,17 +270,17 @@ directory, different output artifact:</p>
{
"openapi": "3.0.1",
"info": {
- "title": "Generated from 146 HBM XML mappings",
+ "title": "Generated from 3 HBM XML mappings",
"version": "1.0.0"
},
"components": {
"schemas": {
- "CompanyBO": { "type": "object", "properties": { ... } },
- "CompanyBOWrite": { "type": "object", "properties": { ... } },
- "DepartmentBO": { ... },
- "DepartmentBOWrite": { ... },
- "ProductBO": { ... },
- "ProductBOWrite": { ... }
+ "Book": { "type": "object", "properties": { ... } },
+ "BookWrite": { "type": "object", "properties": { ... } },
+ "Branch": { ... },
+ "BranchWrite": { ... },
+ "Library": { ... },
+ "LibraryWrite": { ... }
}
}
}
@@ -288,19 +288,17 @@ directory, different output artifact:</p>
<p>Each entity produces two schemas:</p>
<ul>
- <li><strong>EntityBO</strong> — read schema (GET responses): all fields,
+ <li><strong>Entity</strong> — read schema (GET responses): all fields,
generated IDs marked <code>readOnly</code></li>
- <li><strong>EntityBOWrite</strong> — write schema (POST/PUT requests):
+ <li><strong>EntityWrite</strong> — write schema (POST/PUT requests):
excludes generated IDs, version fields, and custom audit annotations</li>
</ul>
<p>Console output reports each entity processed:</p>
<pre>
- OK CompanyBO.hbm.xml → CompanyBO (12 fields, 2 relationships)
- OK DepartmentBO.hbm.xml → DepartmentBO (58 fields, 8 relationships)
- OK ProductBO.hbm.xml → ProductBO (8 fields, 1 relationships)
- OK OrderBO.hbm.xml → OrderBO (15 fields, 3 relationships)
- SKIP HistoryData.hbm.xml (no entity found)
+ OK Book.hbm.xml → Book (15 fields, 2 relationships)
+ OK Branch.hbm.xml → Branch (55 fields, 8 relationships)
+ OK Library.hbm.xml → Library (15 fields, 3 relationships)
Generated 8 schemas (4 entities × 2 [read + write]) from 5 HBM files
Output: /path/to/resources-axis2/openapi-schemas.json