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 78234f36a89ebebc8f7232ff1b090fd317fe7101 Author: Robert Lazarski <[email protected]> AuthorDate: Mon May 11 10:59:53 2026 -1000 docs: Add JPA schema generation and pagination guides Two xdoc documentation files for implemented features that had TOC entries but no docs: openapi-jpa-schema.xml — Documents the axis2-jpa-schema module: - JPA annotation introspection (@Entity, @Column, @Id, etc.) - Hibernate XML mapping introspection (.hbm.xml) - Read vs Write schema generation (write excludes @GeneratedValue, @Version, custom audit annotations) - Custom write-exclude annotation registration - Relationship $ref convention for OpenAPI components - Type mapping tables for both annotation and HBM XML modes - 28 tests documented json-pagination.xml — Documents PaginatedResponse<T> and PaginationRequest: - Wire format with pagination metadata (offset, limit, totalCount, hasMore) - Service integration pattern (DAO offset/limit) - Safety: maxLimit clamping, negative offset handling - Frontend patterns: page controls, virtual scroll, SmartClient - Why offset/limit instead of cursor - 20 tests documented Also fixed: - TOC numbering (7.7.x → 7.6.x for JPA schema subsections) - json-rpc-mcp-guide.xml: updated pagination from "not implemented" to "offset/limit implemented" with link to new guide Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]> --- src/site/xdoc/docs/json-pagination.xml | 172 ++++++++++++++++++++++++ src/site/xdoc/docs/json-rpc-mcp-guide.xml | 10 +- src/site/xdoc/docs/openapi-jpa-schema.xml | 211 ++++++++++++++++++++++++++++++ src/site/xdoc/docs/toc.xml | 10 +- 4 files changed, 393 insertions(+), 10 deletions(-) diff --git a/src/site/xdoc/docs/json-pagination.xml b/src/site/xdoc/docs/json-pagination.xml new file mode 100644 index 0000000000..5a09d5aeb7 --- /dev/null +++ b/src/site/xdoc/docs/json-pagination.xml @@ -0,0 +1,172 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + --> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head><title>Offset/Limit Pagination for JSON-RPC Services</title></head> +<body> + +<h1 id="overview">Offset/Limit Pagination for JSON-RPC Services</h1> + +<p>Axis2 provides a generic pagination framework for JSON-RPC services +backed by SQL databases. The two classes — +<code>PaginationRequest</code> and <code>PaginatedResponse<T></code> — +map directly to JPA/Hibernate's <code>setFirstResult(offset)</code> and +<code>setMaxResults(limit)</code> pattern.</p> + +<p><strong>Package:</strong> <code>org.apache.axis2.json.rpc</code></p> + +<h2 id="wire_format">Wire Format</h2> + +<p>A paginated response wraps the result list with metadata:</p> + +<pre> +{ + "response": { + "data": [ ... ], + "pagination": { + "offset": 0, + "limit": 50, + "totalCount": 1247, + "hasMore": true + } + } +} +</pre> + +<ul> + <li><strong>offset</strong> — zero-based index of the first item in this page</li> + <li><strong>limit</strong> — maximum items requested (page size)</li> + <li><strong>totalCount</strong> — total items matching the query across all pages</li> + <li><strong>hasMore</strong> — true when <code>offset + limit < totalCount</code></li> +</ul> + +<h2 id="service_integration">Service Integration</h2> + +<p>A typical service method delegates offset/limit to the DAO:</p> + +<pre> +public PaginatedResponse<AssetBO> findAssets(AssetQuery query) { + List<AssetBO> items = dao.findList(query.getOffset(), query.getLimit()); + long total = dao.count(query); + return PaginatedResponse.of(items, query.getOffset(), query.getLimit(), total); +} +</pre> + +<p>The request POJO can embed <code>PaginationRequest</code> fields directly +or accept them as separate parameters:</p> + +<pre> +// Client sends: +{ + "searchTerm": "AAPL", + "offset": 100, + "limit": 50 +} +</pre> + +<h3>Unpaginated Responses</h3> + +<p>For small lookup tables (e.g., a list of 15 departments), use the +convenience factory to wrap the full list with <code>hasMore=false</code>:</p> + +<pre> +return PaginatedResponse.unpaginated(departments); +// → offset=0, limit=15, totalCount=15, hasMore=false +</pre> + +<h2 id="safety">Safety: maxLimit Clamping and Input Validation</h2> + +<p><code>PaginationRequest</code> enforces safety constraints at the getter level:</p> + +<table border="1"> +<tr><th>Input</th><th>Behavior</th></tr> +<tr><td><code>offset < 0</code></td><td>Clamped to 0</td></tr> +<tr><td><code>limit <= 0</code></td><td>Default: 50</td></tr> +<tr><td><code>limit > maxLimit</code></td><td>Capped at maxLimit (default: 2000)</td></tr> +</table> + +<p>Services that handle expensive entities can lower the cap per-operation:</p> + +<pre> +// Large text fields — cap at 100 per page +request.setMaxLimit(100); +int safeLimit = request.getLimit(); // capped at 100 +</pre> + +<h2 id="frontend_patterns">Frontend Patterns</h2> + +<h3>Page Controls ("Showing 151–200 of 1,247")</h3> +<pre> +// JavaScript / TypeScript +const { offset, limit, totalCount } = pagination; +const currentPage = Math.floor(offset / limit) + 1; +const totalPages = Math.ceil(totalCount / limit); +const showingFrom = offset + 1; +const showingTo = offset + data.length; +</pre> + +<h3>Virtual Scroll / Infinite Scroll</h3> +<pre> +// Load next chunk when user scrolls +const nextOffset = pagination.offset + pagination.limit; +if (pagination.hasMore) { + fetchPage(nextOffset, pagination.limit); +} +</pre> + +<h3>SmartClient startRow/endRow</h3> +<pre> +// SmartClient sends startRow=300, endRow=350 +// Service translates: offset = startRow, limit = endRow - startRow +int offset = startRow; +int limit = endRow - startRow; +</pre> + +<h2>Why Offset/Limit Instead of Cursor</h2> + +<ul> + <li><strong>DAO compatibility</strong> — existing Hibernate/JPA DAOs use + <code>query.setFirstResult(offset)</code> and <code>query.setMaxResults(limit)</code>. + Cursor pagination requires a stable sort key and stateful server-side tokens.</li> + <li><strong>Frontend grids</strong> — SmartClient, AG Grid, and React Table natively + speak offset/limit via <code>startRow</code>/<code>endRow</code> or + <code>page</code>/<code>pageSize</code>.</li> + <li><strong>totalCount</strong> — enables "Showing 1–50 of 1,247" UI patterns + and page-count calculations. Cursor APIs typically omit total counts because + they are expensive for the cursor model, but they are cheap when the DAO + already runs <code>SELECT COUNT(*)</code>.</li> +</ul> + +<h2>Test Coverage</h2> + +<p>The <code>PaginatedResponseTest</code> class provides 20 tests covering:</p> +<ul> + <li>First page, last page, partial last page, single page, empty result</li> + <li>Null data treated as empty list</li> + <li>Unpaginated convenience factory</li> + <li>Negative offset clamping, zero/negative limit defaults, maxLimit enforcement</li> + <li>Enterprise scenarios: 8,543-item virtual scroll, soft-delete filtering, + service-specific maxLimit, SmartClient startRow/endRow translation</li> + <li>Request → response round-trip simulation</li> +</ul> + +</body> +</html> diff --git a/src/site/xdoc/docs/json-rpc-mcp-guide.xml b/src/site/xdoc/docs/json-rpc-mcp-guide.xml index 7a73c1bdc7..a0f8b6d173 100644 --- a/src/site/xdoc/docs/json-rpc-mcp-guide.xml +++ b/src/site/xdoc/docs/json-rpc-mcp-guide.xml @@ -602,9 +602,11 @@ Each item notes whether the gap is architectural (won't be added to Axis2) or de <td>When set to <code>ServiceName/operationName</code>, <code>_meta.tickerResolveEndpoint</code> is added to the catalog. Omitted entirely when not configured so deployments without a ticker service are unaffected.</td> </tr> <tr> - <td>Cursor-based pagination</td> - <td>Not implemented</td> - <td>Axis2 operations return their full result sets. Cursor-based pagination would need to be implemented in a separate REST layer.</td> + <td>Pagination</td> + <td><strong>Offset/limit implemented</strong> — see <a href="json-pagination.html">Pagination Guide</a></td> + <td>Axis2 provides <code>PaginatedResponse<T></code> and <code>PaginationRequest</code> + for offset/limit pagination with maxLimit clamping. + Cursor-based pagination is not implemented (offset/limit maps directly to JPA/Hibernate DAO patterns).</td> </tr> <tr> <td>RFC 7807 Problem Details error format</td> @@ -638,7 +640,7 @@ uses determines what limitations apply:</p> <tr><td>Auth</td><td>email + password → Bearer token (loginService)</td><td>API key + secret → scoped JWT</td></tr> <tr><td>Query semantics</td><td>Per-operation parameters in arg0</td><td>Uniform filter/sort/fields on every resource</td></tr> <tr><td>Error format</td><td>SOAP fault with correlation ID UUID</td><td>RFC 7807 Problem Details (JSON, per-field)</td></tr> -<tr><td>Pagination</td><td>Full result sets only</td><td>Cursor-based</td></tr> +<tr><td>Pagination</td><td>Offset/limit (<a href="json-pagination.html">PaginatedResponse</a>)</td><td>Cursor-based</td></tr> <tr><td>inputSchema</td><td>Full JSON Schema via <code>mcpInputSchema</code> in services.xml (hand-authored)</td><td>Full JSON Schema from OpenAPI annotations (auto-generated)</td></tr> </table> diff --git a/src/site/xdoc/docs/openapi-jpa-schema.xml b/src/site/xdoc/docs/openapi-jpa-schema.xml new file mode 100644 index 0000000000..badb7c6c04 --- /dev/null +++ b/src/site/xdoc/docs/openapi-jpa-schema.xml @@ -0,0 +1,211 @@ +<!-- + ~ Licensed to the Apache Software Foundation (ASF) under one + ~ or more contributor license agreements. See the NOTICE file + ~ distributed with this work for additional information + ~ regarding copyright ownership. The ASF licenses this file + ~ to you under the Apache License, Version 2.0 (the + ~ "License"); you may not use this file except in compliance + ~ with the License. You may obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, + ~ software distributed under the License is distributed on an + ~ "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + ~ KIND, either express or implied. See the License for the + ~ specific language governing permissions and limitations + ~ under the License. + --> + +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" + "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<head><title>JPA/Hibernate Schema Generation for OpenAPI</title></head> +<body> + +<h1>JPA/Hibernate Schema Generation for OpenAPI</h1> + +<p>The <code>axis2-jpa-schema</code> module generates JSON Schema definitions +from JPA annotations or Hibernate XML mappings (<code>.hbm.xml</code>). +These schemas can be embedded in OpenAPI 3.0 specifications to document +request/response bodies that map directly to database entities.</p> + +<p><strong>Module:</strong> <code>modules/jpa-schema</code><br/> +<strong>Package:</strong> <code>org.apache.axis2.jpa.schema</code></p> + +<h2 id="annotation_mode">JPA Annotation Mode</h2> + +<p>The <code>AnnotationIntrospector</code> reads standard Jakarta Persistence +annotations (<code>@Entity</code>, <code>@Column</code>, <code>@Id</code>, +<code>@ManyToOne</code>, <code>@OneToMany</code>, etc.) and produces an +<code>EntitySchemaModel</code> that the <code>JpaSchemaGenerator</code> +converts to JSON Schema.</p> + +<h3>Supported Annotations</h3> + +<table border="1"> +<tr><th>Annotation</th><th>Schema Effect</th></tr> +<tr><td><code>@Entity</code></td><td>Entity is eligible for introspection</td></tr> +<tr><td><code>@Table(name="...")</code></td><td>Recorded in schema description</td></tr> +<tr><td><code>@Id</code></td><td>Marked as required; <code>readOnly=true</code> in read schema</td></tr> +<tr><td><code>@GeneratedValue</code></td><td>Excluded from write schema (server-managed)</td></tr> +<tr><td><code>@Column(nullable, length)</code></td><td>Maps to <code>required</code> and <code>maxLength</code></td></tr> +<tr><td><code>@Version</code></td><td>Excluded from write schema (server-managed)</td></tr> +<tr><td><code>@Transient</code></td><td>Excluded from all schemas</td></tr> +<tr><td><code>@Enumerated(STRING)</code></td><td>Emitted as <code>{"type":"string","enum":[...]}</code></td></tr> +<tr><td><code>@ManyToOne</code></td><td>Emitted as <code>{"$ref":"#/components/schemas/Entity"}</code></td></tr> +<tr><td><code>@OneToMany</code></td><td>Emitted as <code>{"type":"array","items":{"$ref":"..."}}</code></td></tr> +</table> + +<h3>Example</h3> + +<pre> +@Entity +@Table(name = "PRODUCT") +public class Product { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private BigDecimal productID; + + @Column(nullable = false, length = 200) + private String name; + + @Enumerated(EnumType.STRING) + private ProductStatus status; + + @Version + private Long objVersion; + + @ManyToOne + private Department department; + + @OneToMany + private List<LineItem> lineItems; +} +</pre> + +<pre> +AnnotationIntrospector introspector = new AnnotationIntrospector(); +EntitySchemaModel model = introspector.introspect(Product.class); +ObjectNode readSchema = JpaSchemaGenerator.generateReadSchema(model); +ObjectNode writeSchema = JpaSchemaGenerator.generateWriteSchema(model); +</pre> + +<h2 id="read_write_schemas">Read vs Write Schema Generation</h2> + +<p>The generator produces two schema variants per entity:</p> + +<table border="1"> +<tr><th>Variant</th><th>Includes</th><th>Use Case</th></tr> +<tr><td><strong>Read schema</strong></td><td>All fields (IDs as <code>readOnly</code>)</td><td>GET response bodies</td></tr> +<tr><td><strong>Write schema</strong></td><td>Excludes <code>@GeneratedValue</code> IDs, <code>@Version</code>, and custom audit fields</td><td>POST/PUT request bodies</td></tr> +</table> + +<p>The <code>generateBothSchemas()</code> method returns both at once:</p> +<pre> +Map<String, ObjectNode> schemas = JpaSchemaGenerator.generateBothSchemas(model); +// "Product" → read schema +// "ProductWrite" → write schema +</pre> + +<h3>What Gets Excluded from Write</h3> +<ul> + <li><code>@Id @GeneratedValue</code> — server assigns the ID</li> + <li><code>@Version</code> — server manages optimistic locking</li> + <li><code>@Transient</code> — excluded from both read and write</li> + <li>Custom annotations (see below)</li> +</ul> + +<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 +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.CreatedBy"); + +EntitySchemaModel model = introspector.introspect(Product.class); +// Fields annotated with @IgnoreChanges or @CreatedBy are now +// excluded from the write schema but present in the read schema. +</pre> + +<h2 id="hbm_xml_mode">Hibernate XML Mapping Mode (.hbm.xml)</h2> + +<p>For codebases that use Hibernate XML mappings instead of (or alongside) +JPA annotations, the <code>HbmXmlIntrospector</code> parses <code>.hbm.xml</code> +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"); + ObjectNode readSchema = JpaSchemaGenerator.generateReadSchema(model); +} +</pre> + +<h3>Supported HBM XML Elements</h3> + +<table border="1"> +<tr><th>HBM XML Element</th><th>Schema Effect</th></tr> +<tr><td><code><id></code> with <code><generator></code></td><td>Required, <code>readOnly</code>, excluded from write</td></tr> +<tr><td><code><version></code></td><td>Excluded from write schema</td></tr> +<tr><td><code><property></code></td><td>Mapped by Hibernate type → JSON Schema type</td></tr> +<tr><td><code><many-to-one></code></td><td><code>$ref</code> to referenced entity</td></tr> +<tr><td><code><set></code> / <code><list></code></td><td>Array of <code>$ref</code></td></tr> +<tr><td><code><component></code></td><td>Flattened with dot-notation prefix (e.g., <code>address.city</code>)</td></tr> +<tr><td>Nested <code><column not-null="true"></code></td><td>Maps to required</td></tr> +</table> + +<h3>Type Mapping</h3> + +<table border="1"> +<tr><th>Hibernate / Java Type</th><th>JSON Schema</th></tr> +<tr><td><code>string</code>, <code>text</code></td><td><code>{"type":"string"}</code></td></tr> +<tr><td><code>integer</code>, <code>int</code></td><td><code>{"type":"integer","format":"int32"}</code></td></tr> +<tr><td><code>long</code>, <code>big_integer</code></td><td><code>{"type":"integer","format":"int64"}</code></td></tr> +<tr><td><code>double</code>, <code>float</code>, <code>big_decimal</code></td><td><code>{"type":"number"}</code></td></tr> +<tr><td><code>boolean</code>, <code>yes_no</code></td><td><code>{"type":"boolean"}</code></td></tr> +<tr><td><code>timestamp</code>, <code>date</code></td><td><code>{"type":"string","format":"date-time"}</code></td></tr> +</table> + +<h2>Test Coverage</h2> + +<p>The <code>JpaSchemaGeneratorTest</code> class provides comprehensive tests:</p> +<ul> + <li><strong>Annotation introspection</strong> (12 tests): entity detection, ID fields, + column constraints, version, transient, enums, ManyToOne, OneToMany, + custom write-exclude annotations</li> + <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, + collections, component flattening, nested column not-null</li> +</ul> + +<h2>Relationship $ref Convention</h2> + +<p>Relationships are emitted as <code>$ref</code> pointers using the OpenAPI +components convention:</p> + +<pre> +// ManyToOne +{"$ref": "#/components/schemas/Department"} + +// OneToMany +{"type": "array", "items": {"$ref": "#/components/schemas/LineItem"}} + +// Write schema uses "Write" suffix +{"$ref": "#/components/schemas/DepartmentWrite"} +</pre> + +<p>The caller is responsible for ensuring that referenced entity schemas +are also generated and added to the OpenAPI components section.</p> + +</body> +</html> diff --git a/src/site/xdoc/docs/toc.xml b/src/site/xdoc/docs/toc.xml index 1f5299472a..14c8f5e94d 100644 --- a/src/site/xdoc/docs/toc.xml +++ b/src/site/xdoc/docs/toc.xml @@ -63,10 +63,10 @@ Guide</a></li> <li>7.5 <a href="openapi-rest-advanced-userguide.html">Advanced Enterprise OpenAPI Features</a></li> <li>7.6 <a href="openapi-jpa-schema.html">JPA/Hibernate Schema Generation</a> <ul> -<li>7.7.1 <a href="openapi-jpa-schema.html#annotation_mode">JPA Annotation Mode (@Entity, @Column, @ManyToOne)</a></li> -<li>7.7.2 <a href="openapi-jpa-schema.html#hbm_xml_mode">Hibernate XML Mapping Mode (.hbm.xml)</a></li> -<li>7.7.3 <a href="openapi-jpa-schema.html#read_write_schemas">Read vs Write Schema Generation</a></li> -<li>7.7.4 <a href="openapi-jpa-schema.html#custom_annotations">Custom Audit Annotation Support</a></li> +<li>7.6.1 <a href="openapi-jpa-schema.html#annotation_mode">JPA Annotation Mode (@Entity, @Column, @ManyToOne)</a></li> +<li>7.6.2 <a href="openapi-jpa-schema.html#hbm_xml_mode">Hibernate XML Mapping Mode (.hbm.xml)</a></li> +<li>7.6.3 <a href="openapi-jpa-schema.html#read_write_schemas">Read vs Write Schema Generation</a></li> +<li>7.6.4 <a href="openapi-jpa-schema.html#custom_annotations">Custom Audit Annotation Support</a></li> </ul> </li> </ul> @@ -171,8 +171,6 @@ Support</a></li> <li><a href="json-rpc-mcp-guide.html#tested_features">Unit Test Feature Coverage</a></li> <li><a href="json-rpc-mcp-guide.html#not_implemented">Not Implemented / Limitations</a></li> <li><a href="json-streaming-formatter.html">Streaming Formatter for Large MCP Tool Responses</a></li> - <!-- data_api_relationship anchor removed from guide; link to top-level page --> - <li><a href="json-rpc-mcp-guide.html">Relationship to Data API Vision</a></li> <li><a href="json-rpc-mcp-guide.html#python_compat">Python MCP Compatibility Notes</a></li> </ul> </li>
