Skip to content

Latest commit

 

History

History
529 lines (391 loc) · 21.1 KB

File metadata and controls

529 lines (391 loc) · 21.1 KB
type architecture
created 2026-04-06
updated 2026-05-04
status Active

Axis2/Java Modernization Plan

BLUF: Axis2 becomes a multi-protocol service platform — one service implementation serving JSON-RPC (existing callers), REST (Data API consumers), and MCP (AI agents) simultaneously. The Spring Boot starter removes adoption tax. OpenAPI generation makes every Axis2 service AI-discoverable. A native MCP transport eliminates the wrapper layer entirely. No other Java framework can do all three from the same service deployment.

Foundation already in place:

  • springbootdemo-tomcat11 — working reference implementation (Spring Boot 3.x + Axis2 + Tomcat 11 + Java 25)
  • axis2-openapi module — OpenAPI spec served at /openapi.json, /openapi.yaml, /swagger-ui, /openapi-mcp.json
  • modules/transport-h2 — HTTP/2 transport module (proof of concept, tested)
  • modules/spring-boot-starter — functional Spring Boot autoconfiguration
  • ThreadPool instance-field fix committed (27860ddf9f)
  • Java 25 + Tomcat 11.0.20 full end-to-end test passing

Completion Status

Phase Status Notes
Immediate Track (B1-B3, C3, D1-D3) DONE MCP catalog, inputSchema, authScope, streaming, resources, error hardening
Phase 1 — Spring Boot Starter DONE Full autoconfiguration: AxisServlet, AAR/MAR scanning, OpenAPI+MCP endpoints, SOAP/JSON mode. JWT security intentionally deferred (deployment-specific).
Phase A — Error Contracts DONE Axis2JsonErrorResponse, JsonRpcFaultException, 422/429/503 HTTP status codes, OpenAPI ErrorResponse schema, MCP _meta.errorContract
Phase B — MCP Schema Completion DONE Java type introspection for request/response schemas in both OpenAPI and MCP catalog. mcpAuthScope, mcpStreaming parameters.
Phase 2 — OpenAPI Annotation Bridge PARTIAL POJO introspection done. springdoc @Operation/@Parameter annotation processing not yet started.
Phase 3 — REST Transport NOT STARTED Dual-protocol JSON-RPC + REST from same service
Phase 4 — MCP Bridge NOT STARTED OpenAPI-driven MCP wrapper
Phase 5 — HTTP/2 Publication NOT STARTED transport-h2 graduation to supported module
Phase 6 — Native MCP Transport DEFERRED Bridge approach sufficient; prove MCP catalog adoption first
Phase 7 — Community NOT STARTED Blog post, migration guide
NEW Phase JPA — JPA/Hibernate Schema Generation DONE See below
NEW Phase PG — Offset/Limit Pagination DONE See below
NEW Phase TX — Transaction Demarcation Module PLANNED See below

NEW: Phase JPA — JPA/Hibernate Schema Generation (axis2-jpa-schema)

Goal: Auto-generate OpenAPI components/schemas from JPA-annotated entity classes AND Hibernate XML mapping files (.hbm.xml). This makes Axis2 the first framework that can serve OpenAPI schemas for any Hibernate project regardless of mapping style.

Bang: 8/10 | Effort: days

Why both annotation AND hbm.xml support

The original pickup doc rejected "OpenAPI from hbm.xml" as too narrow. That was correct for hbm.xml alone — but the real opportunity is broader:

  • JPA annotations (@Entity, @Column, @ManyToOne) — the standard for new projects and the pattern used by typical financial applications (dozens of entities, Spring Data JPA repositories, Jakarta Persistence)
  • hbm.xml — still in production at many organizations (legacy mapping files, Hibernate 3.0 DTD, backtick-quoted SQL Server columns). These projects won't rewrite to annotations just for OpenAPI support.

Supporting both from one module covers ~95% of Hibernate deployments.

Design

Two metadata extraction strategies, one unified schema output:

┌──────────────────────┐     ┌──────────────────────┐
│  JPA Annotation      │     │  HBM.XML Parser      │
│  Introspector        │     │  (DOM/SAX)            │
│                      │     │                       │
│  @Entity             │     │  <class name="...">   │
│  @Column             │     │  <property>           │
│  @ManyToOne          │     │  <many-to-one>        │
│  @Id/@GeneratedValue │     │  <id>/<generator>     │
│  @Transient          │     │  <version>            │
└──────────┬───────────┘     └──────────┬────────────┘
           │                            │
           ▼                            ▼
   ┌───────────────────────────────────────┐
   │  EntitySchemaModel (unified)          │
   │  - entityName, tableName              │
   │  - fields: name, type, nullable,      │
   │    readOnly, relationship             │
   │  - idFields, versionField             │
   │  - ignoredFields (audit/system)       │
   └──────────────────┬────────────────────┘
                      │
                      ▼
   ┌───────────────────────────────────────┐
   │  OpenAPI Schema Generator             │
   │  - Read schema (all fields)           │
   │  - Write schema (excludes @Id with    │
   │    @GeneratedValue, @IgnoreChanges,   │
   │    version fields)                    │
   │  - $ref for relationships             │
   └───────────────────────────────────────┘

JPA annotation mapping

JPA Annotation Schema Effect
@Entity Top-level schema object
@Table(name=...) Schema title / x-table-name
@Column(nullable=false) Added to required array
@Column(length=255) maxLength: 255
@Id Marked in schema description
@Id + @GeneratedValue readOnly: true in write schema
@ManyToOne / @OneToOne $ref to related entity schema
@OneToMany / @ManyToMany type: array, items: {$ref: ...}
@Transient Excluded from schema
@Version Excluded from write schema
@Enumerated type: string, enum: [...]
BigDecimal (ID context) type: integer
Double / Float type: number
Byte (boolean context) type: boolean
Timestamp / Date type: string, format: date-time

HBM.XML mapping

HBM Element/Attribute Schema Effect
<class name="..." table="..."> Top-level schema object
<property not-null="true"> Added to required array
<property type="java.lang.String"> type: string
<property type="java.lang.Long"> type: integer
<property type="java.lang.Boolean"> type: boolean
<property type="timestamp"> type: string, format: date-time
<id> + <generator> readOnly: true in write schema
<version> Excluded from write schema
<many-to-one class="..."> $ref to related entity schema
<set> / <bag> with <one-to-many> type: array, items: {$ref: ...}
<component> Inline object properties (flattened)
Column with sql-type="nvarchar(max)" type: string (no maxLength)

Custom annotation support

Projects may use custom annotations that carry business semantics:

Custom Annotation Schema Effect
@IgnoreChanges Excluded from write schema (audit fields)
@IncludeInUpdate Included in write schema (explicitly)

The module accepts a configurable list of "exclude from write" annotation class names so any project can declare their own audit-field markers.

Integration with OpenApiSpecGenerator

Plugs into the existing addComponents() method. When axis2-jpa-schema is on the classpath and a service's parameter or return type is an @Entity class (or has a companion .hbm.xml), the JPA introspector generates the schema instead of the generic POJO introspector.

Key files

  • modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/JpaSchemaGenerator.java
  • modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/EntitySchemaModel.java
  • modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/AnnotationIntrospector.java
  • modules/jpa-schema/src/main/java/org/apache/axis2/jpa/schema/HbmXmlIntrospector.java

Dependency

None — optional module. Works standalone or with the Spring Boot starter.


NEW: Phase TX — Transaction Demarcation Module (axis2-tx)

Goal: A .mar module that wraps service invocations in JTA transactions. Deploy the module, set <parameter name="transactional">true</parameter> in services.xml, and every operation on that service gets automatic begin/commit/rollback.

Bang: 7/10 | Effort: days

How it works

Axis2 already has the plumbing:

  • Axis2UserTransaction in modules/kernel wraps JTA UserTransaction
  • TransactionConfiguration provides JNDI-based transaction manager lookup
  • AbstractMessageReceiver.receive() sets SET_ROLLBACK_ONLY on fault
  • AbstractTemplatedHandler provides shouldInvoke() / doInvoke() separation
  • Handler flowComplete() is called in reverse order after processing (for cleanup)

The module injects a TransactionHandler into the OperationInPhase:

InFlow phases:
  Transport → Addressing → Security → PreDispatch → Dispatch
    → OperationInPhase:
        [TransactionHandler.invoke()]     ← BEGIN transaction
        [other handlers]
        [MessageReceiver.receive()]       ← service method runs
    → OperationOutPhase

OutFlow / flowComplete:
    [TransactionHandler.flowComplete()]   ← COMMIT or ROLLBACK

module.xml

<module name="axis2-tx" class="org.apache.axis2.tx.TransactionModule">
    <InFlow>
        <handler name="TransactionHandler"
                 class="org.apache.axis2.tx.TransactionHandler">
            <order phase="OperationInPhase" phaseFirst="true"/>
        </handler>
    </InFlow>
</module>

TransactionHandler behavior

  • shouldInvoke(): checks service-level transactional parameter
  • doInvoke(): begins JTA transaction, stores ref in MessageContext
  • flowComplete(): if SET_ROLLBACK_ONLY or AxisFault occurred → rollback; else → commit

Configuration in services.xml

<service name="PortfolioAssetService">
    <parameter name="transactional">true</parameter>
    <parameter name="transactionTimeout">30</parameter>
    <!-- operations... -->
</service>

Key files

  • modules/tx/src/main/java/org/apache/axis2/tx/TransactionModule.java
  • modules/tx/src/main/java/org/apache/axis2/tx/TransactionHandler.java
  • modules/tx/src/META-INF/module.xml

Dependency

Requires JTA API on classpath (provided by WildFly, Tomcat with Atomikos/Narayana, etc.).


DONE: Phase PG — Offset/Limit Pagination (PaginatedResponse)

Goal: Provide a standard pagination envelope that works with existing DAO layers using setFirstResult(offset) / setMaxResults(limit) (the standard JPA/Hibernate query pattern), giving frontend grids and API consumers the metadata they need to render paging controls or implement infinite scroll.

Bang: 7/10 | Effort: hours

Why offset/limit (not cursor)

Most enterprise applications paginate with integer offsets because:

  1. DAO compatibility — existing service layers pass firstResult/maxResult to Hibernate's Query API. Cursor pagination requires a stable sort key and stateful server-side tokens — added complexity with no benefit when the query is already offset-based.

  2. Frontend grid compatibility — SmartClient, AG Grid, React Table, and MUI DataGrid all natively speak offset/limit via startRow/endRow or page/pageSize. Cursor tokens require client-side adaptation.

  3. Total count is cheap — offset-based APIs can include totalCount (from a parallel SELECT COUNT(*)) to enable "Showing 1–50 of 1,247" UI patterns. Cursor APIs typically omit totals because they are expensive for the cursor model.

  4. Virtual scrolling — grids that load the next chunk as the user scrolls use hasMore to decide whether to fetch. This maps directly to offset + limit < totalCount.

Cursor pagination remains valuable for append-only feeds (activity logs, message streams) where offset instability is a real problem. The framework does not preclude adding cursor support later — it simply does not ship cursor tokens by default because the common case does not need them.

What NOT to build

  • No cursor tokens (UUID-based cursors add complexity most deployments don't need)
  • No GraphQL relay-style edges / pageInfo / Connection types
  • No Spring Data Pageable / Page<T> integration (many projects don't use Spring Data)

Wire format

{
  "response": {
    "data": [ ... ],
    "pagination": {
      "offset": 100,
      "limit": 50,
      "totalCount": 1247,
      "hasMore": true
    }
  }
}

Classes

PaginatedResponse<T> — generic response wrapper with data (list) and pagination (metadata). Factory methods:

  • PaginatedResponse.of(items, offset, limit, totalCount) — standard paged response
  • PaginatedResponse.unpaginated(items) — wrap a full result set with no pagination

PaginationRequest — request-side helper with safe defaults:

  • offset defaults to 0, negative values clamped
  • limit defaults to 50, capped at configurable maxLimit (default 2000)
  • Services can embed these fields in their request POJO or accept separately

Frontend integration pattern

// React / TypeScript — infinite scroll
const { data, pagination } = await fetchPage(offset, limit);
setItems(prev => [...prev, ...data]);
if (pagination.hasMore) {
    setNextOffset(pagination.offset + pagination.limit);
}

// React / TypeScript — page controls
const totalPages = Math.ceil(pagination.totalCount / pagination.limit);
const currentPage = Math.floor(pagination.offset / pagination.limit) + 1;

Serialization compatibility

PaginatedResponse is a plain POJO — serializes identically across all four Axis2 JSON formatters (Gson, Moshi, EnhancedGson/H2, EnhancedMoshi/H2) with no custom adapters. The data list elements serialize as their runtime type regardless of generic type erasure.

Key files

  • modules/json/src/org/apache/axis2/json/gson/rpc/PaginatedResponse.java
  • modules/json/src/org/apache/axis2/json/gson/rpc/PaginationRequest.java
  • modules/json/test/org/apache/axis2/json/gson/rpc/PaginatedResponseTest.java (22 tests)

Dependency

None — zero-dependency POJO pattern. Works with any JSON formatter.


Immediate Track — MCP inputSchema + Axis2/C + Apache httpd Demo

Status: DONE — all steps completed.

Step B1 — mcpInputSchema static parameter support (Java + C)

DONE. Both Option 1 (static declaration in services.xml) and Option 2 (auto-generated from Java type introspection) are implemented and tested. OpenApiSpecGenerator.generateMcpCatalogJson() reads mcpInputSchema param with Jackson validation, falls back to auto-introspection via generateSchemaFromServiceClass(), then falls back to empty schema.

Step B2 — mcpAuthScope per-operation parameter

DONE. Reads via getMcpStringParam(), emits x-authScope in tool node. Tests cover operation-level, service-level, and absent cases.

Step B3 — mcpStreaming hint

DONE. Boolean mcpStreaming parameter adds x-streaming: true. Absent/false suppresses the field entirely (compact catalog).

Step C3 — MCP Resources endpoint

DONE. generateMcpResourcesJson() returns resources/list response with URI, name, description, mimeType, and metadata (wsdlUrl, operations, requiresAuth) for each deployed service.

Step D1 — Axis2/C MCP catalog handler

Axis2/C implementation — tracked separately in axis-axis2-c-core repo.

Step D2 — Axis2/C correlation ID error hardening

Axis2/C implementation — tracked separately.

Step D3 — Populate mcpInputSchema in all 5 financial benchmark operations

DONE. All three Java financial benchmark operations have explicit mcpInputSchema in services.xml with full JSON Schema definitions.


Phase 1 — Spring Boot Starter

Status: DONE.

Delivered as modules/spring-boot-starter with:

  • Axis2AutoConfiguration — master switch, respects axis2.enabled
  • Axis2RepositoryAutoConfiguration — stages axis2.xml (SOAP or JSON mode templates)
  • Axis2ServletAutoConfiguration — registers AxisServlet with configurable path
  • Axis2OpenApiAutoConfiguration — registers OpenAPI/MCP servlet endpoints
  • Axis2Properties — externalized configuration via application.properties
  • Built-in axis2-soap.xml and axis2-json.xml templates
  • WildFly VFS compatibility
  • Full test suite

Not included (intentional): JWT/Spring Security autoconfiguration — deployment-specific, consuming apps provide their own SecurityFilterChain.


Phase 2 — OpenAPI Generation (springdoc-openapi Bridge)

Status: PARTIAL.

Done:

  • POJO introspection for request/response types → components/schemas
  • Structured error response schema (ErrorResponse) with 422/429/503 responses
  • MCP tool definitions with inputSchema auto-generated from Java types
  • Request body and 200 response schemas linked via $ref

Remaining:

  • springdoc/Swagger annotation processing (@Operation, @Parameter, @ApiResponse)
  • OpenAPI 3.1 output (currently 3.0.1)

Dependency

Phase 1 (starter) — satisfied.


Phase 3 — REST Transport (Dual-Protocol Services)

Status: NOT STARTED.

Goal: Same Axis2 @WebService class reachable via both JSON-RPC and REST paths.

Tasks

  1. REST dispatcher activation in autoconfiguration
  2. @RestMapping annotation for URL templates
  3. HTTP method routing (GET/POST/PUT/DELETE → 405 enforcement)
  4. Parallel transports — same service, no code duplication
  5. OpenAPI spec reflects REST paths when @RestMapping present

Dependency

Phase 1 (starter), Phase 2 (REST paths in OpenAPI spec).


Phase 4 — MCP Path 1: OpenAPI-Driven MCP Wrapper

Status: NOT STARTED.

Thin Spring Boot app that reads /openapi-mcp.json and implements MCP protocol (initialize, tools/list, tools/call). Forwards calls to Axis2 endpoints.

Dependency

Phase 2 (requires /openapi-mcp.json endpoint — satisfied).


Phase 5 — HTTP/2 Transport Publication

Status: NOT STARTED.

Graduate modules/transport-h2 to supported module with formal test suite, performance benchmarks, and starter integration.

Dependency

Phase 1 (starter) — satisfied.


Phase 6 — Native MCP Transport (axis2-transport-mcp)

Status: DEFERRED.

Bridge approach (Phase 4) sufficient. Prove MCP catalog adoption before investing in native transport. Build when there's demand.


Phase 7 — Community and Positioning

Status: NOT STARTED.

Apache blog post, migration guide, reference implementation documentation.


Summary Timeline (Updated)

Phase Deliverable Status
Immediate Track MCP catalog, inputSchema, resources DONE
1 axis2-spring-boot-starter DONE
A Error contracts (422/429/503) DONE
B MCP schema completion DONE
JPA JPA/Hibernate → OpenAPI schema generation DONE
PG Offset/limit pagination envelope DONE
TX Transaction demarcation .mar module PLANNED
2 OpenAPI annotation bridge (springdoc) PARTIAL
3 REST transport + dual-protocol NOT STARTED
4 MCP bridge wrapper NOT STARTED
5 HTTP/2 transport publication NOT STARTED
6 Native MCP transport DEFERRED
7 Community posts, migration guide NOT STARTED

End State

A single Axis2 service deployment, configured once, serves:

Claude Desktop / AI agent  →  MCP (bridge Phase 4, or native Phase 6)
                                         ↓
Data API / React frontend  →  REST (Phase 3)    ──►  Axis2 Service
                                         ↑           (one implementation)
Existing JSON-RPC callers  →  JSON-RPC (unchanged)

With:

  • OpenAPI spec and MCP tool definitions auto-generated (Phase 2 + JPA)
  • JPA entity schemas for any Hibernate project (Phase JPA)
  • Automatic transaction management (Phase TX)
  • Spring Boot starter (Phase 1) — single Maven dependency
  • Structured error contracts with proper HTTP status codes (Phase A)