diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d0436b5..7bc3ba6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,7 +1,7 @@ -# CI workflow for the main line (JDK 17) +# CI workflow for the feature/1.0.x line (JDK 8) # # Triggers: -# - push / pull_request on the main branch +# - push / pull_request on the feature/1.0.x branch # - manual workflow_dispatch # # Runs `mvn -B clean verify` which includes the JaCoCo coverage gate @@ -10,9 +10,9 @@ name: CI on: push: - branches: [main] + branches: [feature/1.0.x] pull_request: - branches: [main] + branches: [feature/1.0.x] workflow_dispatch: permissions: @@ -20,24 +20,24 @@ permissions: jobs: build: - name: JDK 17 Build & Verify + name: JDK 8 Build & Verify runs-on: ubuntu-latest timeout-minutes: 30 steps: - name: Checkout source uses: actions/checkout@v7 - - name: Set up JDK 17 + - name: Set up JDK 8 uses: actions/setup-java@v5 with: distribution: temurin - java-version: '17' + java-version: '8' cache: maven - name: Install Easy4J snapshot dependencies env: EASY4J_BRANCH: ${{ github.base_ref || github.ref_name }} - EASY4J_DEPENDENCIES: "javassist-plus" + EASY4J_DEPENDENCIES: "javassist-extension" run: | set -euo pipefail dependency_root="${RUNNER_TEMP}/easy4j-dependencies" diff --git a/.gitignore b/.gitignore index f4b6243..8a6aaa8 100644 --- a/.gitignore +++ b/.gitignore @@ -1,111 +1,135 @@ -target/ -bin/ -!.mvn/wrapper/maven-wrapper.jar -!**/src/main/**/target/ -!**/src/test/**/target/ -.flattened-pom.xml -.fastRequest -Cargo.lock -*.swp -*.swo - -### STS ### -.apt_generated +### IDE ### +*.iml +*.ipr +*.iws +.idea/ .classpath .factorypath .project .settings .springBeans +.apt_generated .sts4-cache +.metadata +.recommenders rebel.xml +.vscode/ -### IntelliJ IDEA ### -.idea -*.iws -*.iml -*.ipr +### Gradle ### +.gradle +!gradle/wrapper/gradle-wrapper.jar + +### Maven ### +target/ +dependency-reduced-pom.xml +pom.xml.versionsBackup +.flattened-pom.xml +!.mvn/wrapper/maven-wrapper.jar +!**/src/main/**/target/ +!**/src/test/**/target/ ### NetBeans ### -/nbproject/private/ -/nbbuild/ -/dist/ -/nbdist/ -/.nb-gradle/ +nbproject/private/ +nbbuild/ +nbdist/ +.nb-gradle/ +dist/ + +### Build ### build/ !**/src/main/**/build/ !**/src/test/**/build/ +build.log +bin/ +lib/ +codes/ +/code +gen_code -### VS Code ### -.vscode/ -.claude/ - -### Mac OS ### -.DS_Store - -**/dependency-reduced-pom.xml - -### Python -__pycache__/ -*.py[cod] -*$py.class -*.egg-info/ +### Logs ### +logs/ +*.log +log.*/ +transaction-logs -### Docs -docs/_build -.venv/ +### Archives ### +*.jar +*.war +*.ear +*.tar +*.gz +!/libs/*.jar +### Temporary files ### +*# +*.sw? +*~ +.#* +.*.md.html +*.cache +*.diff +*.patch +*.tmp +*.java~ +*.properties~ +*.xml~ -# vibe coding -.claude -openspec -AGENTS.md -CLAUDE.md +### System files ### +.DS_Store +Thumbs.db +Servers -##Log -logs/ +### Application specific ### +MANIFEST.MF +_site/ +activemq-data +dump.rdb +interpolated*.xml +manifest.yml +overridedb.* +secrets.yml +upload -## Frontend (all frontend/ directories across examples and extensions) -# Dependencies -**/node_modules/ -# Build output -**/dist/ -**/build/ +### Node / Frontend ### +node_modules/ **/.output/ -# Vite / bundler caches **/.vite/ **/.cache/ **/.parcel-cache/ -# Type-check / tsc output **/*.tsbuildinfo -# Env files (may contain secrets) **/.env.local **/.env.*.local -# Coverage **/coverage/ -# Storybook **/storybook-static/ -# boba-tea-shop: generated static assets served by the supervisor-agent **/boba-tea-shop/supervisor-agent/**/static/ -##agentscope -.agentscope/ +### Python ### +__pycache__/ +*.py[cod] +*$py.class +*.egg-info/ +.venv/ + +### Docs ### +docs/_build + +### Rust ### +Cargo.lock + +### Server ### +/server/nacos-server/data +/server/nacos-server/logs -## harness db +### AI / vibe coding ### +.claude +openspec +AGENTS.md +CLAUDE.md +.agentscope/ **/*.db -### Apache CXF (from cxf-rt-javassist) ### -.metadata -.recommenders -/code -MANIFEST.MF -_site/ -activemq-data -build.log -lib/ -manifest.yml -overridedb.* -settings.xml -transaction-logs -secrets.yml -dump.rdb -interpolated*.xml +### Development tools ### +.fastRequest +*.http +*.json +qodana.yaml diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties index 8d6254f..216df05 100644 --- a/.mvn/wrapper/maven-wrapper.properties +++ b/.mvn/wrapper/maven-wrapper.properties @@ -1 +1,3 @@ -distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.16/apache-maven-3.9.16-bin.zip diff --git a/README.md b/README.md index f362610..8fb1d1d 100644 --- a/README.md +++ b/README.md @@ -1,153 +1,183 @@ # cxf-rt-javassist -[English](./README.md) | [简体中文](./README.zh-CN.md) + -[![Java](https://img.shields.io/badge/Java-17-orange)](https://github.com/easy-4-java/cxf-rt-javassist) [![License](https://img.shields.io/badge/license-Apache%202.0-green)](./LICENSE) +
-> Generate Apache CXF JAX-WS / JAX-RS implementations with -> [Javassist](https://www.javassist.org): fluent `CtClass` builders that produce -> `@WebService` / JAX-RS annotated endpoint classes at runtime. +**Runtime Apache CXF JAX-WS / JAX-RS implementation generation with Javassist.** -## Table of Contents +[![Java](https://img.shields.io/badge/Java-8%20%7C%2017%20%7C%2021-orange)](#3-requirements--compatibility) +[![License](https://img.shields.io/badge/license-Apache%202.0-green)](./LICENSE) +[![Maven Central placeholder](https://img.shields.io/badge/Maven%20Central-pending-lightgray)](#5-installation) -- [1. Project Overview](#1-project-overview) -- [2. Features & Status](#2-features--status) -- [3. Requirements & Compatibility](#3-requirements--compatibility) -- [4. Architecture & Modules](#4-architecture--modules) -- [5. Installation](#5-installation) -- [6. Quick Start](#6-quick-start) -- [7. Configuration](#7-configuration) -- [8. Core Usage / API](#8-core-usage--api) -- [9. Testing & Build](#9-testing--build) -- [10. Versioning & Branches](#10-versioning--branches) -- [11. Contributing & License](#11-contributing--license) +[English](./README.md) · [简体中文](./README.zh-CN.md) · [Technical Design & Bug Fix Report (zh-CN)](./TECHNICAL-DESIGN.md) -## 1. Project Overview +[Overview](#1-project-overview) · [Features](#2-features--status) · [Compatibility](#3-requirements--compatibility) · [Architecture](#4-architecture--modules) · +[Installation](#5-installation) · [Quick Start](#6-quick-start) · [Core API](#7-core-usage--api) · [Build & Test](#8-testing--build) · +[Versioning](#9-versioning--branches) · [FAQ](#10-faq) · [Contributing](#11-contributing--license) -`cxf-rt-javassist` generates Apache CXF JAX-WS / JAX-RS endpoint implementations at -runtime using [Javassist](https://www.javassist.org). Two fluent builders turn a -declarative description into real, loadable classes: +
-- **`JaxwsEndpointApiCtClassBuilder`** — generates `@WebService` endpoint classes - (name, target namespace, service name, WSDL provider mode, addressing, service - mode, bound data, fields and SOAP methods). -- **`JaxrsEndpointApiCtClassBuilder`** — generates JAX-RS resource classes - (`@Path`, produces, REST methods with `HttpMethodEnum` / `RestParam`, - bound data, fields). +--- -Generated classes extend the shared `EndpointApi` base class; with -`toInstance(InvocationHandler)` the builder returns an instance whose method calls -are dispatched to your handler — ideal for proxied / pluggable endpoint logic. +> **Current version** (depends on branch, see §9): `3.0.x.x-SNAPSHOT`
+> **JDK baselines**: 8 / 17 / 21 (four long-term worktree branches maintained in parallel)
+> **Build tool**: Maven 3.0+ (Maven Wrapper `./mvnw` included)
+> **Last verified**: 2026-08-20 (`mvn test` green across all four branches: 202 / 202 / 202 / 224 tests) -The builders rely on the `io.github.easy4j:javassist-plus` companion library -(`CtFieldBuilder`, `ClassPoolFactory`, `JavassistUtils`). +## 1. Project Overview -What it is **not**: +**`cxf-rt-javassist` is a runtime bytecode-generation library for Java framework / dynamic-gateway developers. Built on top of [Javassist](https://www.javassist.org) and `io.github.easy4j:javassist-extension`, it translates declarative SOAP / REST endpoint definitions into loadable `Class` objects carrying the correct `@WebService` or JAX-RS annotations.** -- Not the CXF runtime itself — Apache CXF frontend artifacts are required at - runtime to serve the generated endpoints. -- Not a Spring Boot starter — no auto-configuration is provided. +| Dimension | Positioning | +| :--- | :--- | +| What it is | A runtime bytecode generator; single-module jar | +| Consumers | Spring Boot Starters, API gateways, generic proxy frameworks, dynamically exported endpoints | +| Core capabilities | ① JAX-WS endpoint class generation ② JAX-RS resource class generation ③ three outputs (bytecode / `Class` / proxied instance) ④ fully symmetric SOAP ↔ REST builder design | +| JDK (multi-line) | `feature/1.0.x` → JDK 8; `feature/2.0.x` → JDK 17; `feature/3.0.x` → JDK 21; `main` → primary JDK 17/21 release line | +| Coordinates | `io.github.easy4j:cxf-rt-javassist` | +| Property prefix | None (everything is driven by fluent builder APIs; no files are read) | -Typical scenarios: +### 1.1 What it is NOT -| Scenario | What you use | -| :--- | :--- | -| Generate a `@WebService` endpoint class at runtime | `JaxwsEndpointApiCtClassBuilder` + `webService(...)` / `newMethod(...)` | -| Generate a JAX-RS resource class at runtime | `JaxrsEndpointApiCtClassBuilder` + `path(...)` / `newMethod(...)` | -| Dispatch generated calls to a handler | `toInstance(InvocationHandler)` | -| Export generated bytecode for offline inspection | `build()` → `CtClass` → `toBytecode()` / `writeFile()` | +- Not the Apache CXF runtime itself. To actually expose the generated SOAP / REST endpoints, you also need `cxf-rt-frontend-jaxws` / `cxf-rt-frontend-jaxrs` on the runtime classpath. +- Not a Spring Boot Starter. No auto-configuration; no Spring dependency is ever pulled by this artifact. +- No commitment to unlisted JDK combinations (e.g. JDK 11); only 8 / 17 / 21 are long-term lines. -## 2. Features & Status +### 1.2 Typical use cases -| Capability | Status | Notes | +| Scenario | How to use | Outcome | | :--- | :--- | :--- | -| JAX-WS class builder | Stable | `webService`, `webServiceProvider`, `addressing`, `serviceMode`, `bind`, `makeField`, `makeMethod`, `newMethod`, `build` / `toClass` / `toInstance` | -| JAX-RS class builder | Stable | `path`, `produces`, `bind`, `makeField`, `newField`, `removeField`, `newMethod` (with `HttpMethodEnum` / `RestMethod`), `removeMethod`, `build` / `toClass` / `toInstance` | -| SOAP definition model | Stable | `SoapService`, `SoapMethod`, `SoapParam`, `SoapResult`, `SoapBound` | -| REST definition model | Stable | `RestBound`, `RestMethod`, `RestParam`, `RestProduce`, `HttpMethodEnum`, `HttpParamEnum` | -| Utility helpers | Stable | `JaxwsEndpointApiUtils`, `JaxrsEndpointApiUtils` | -| Tests | Stable | `JaxwsApiCtClassBuilder_Test`, `JaxrsApiCtClassBuilder_Test` + samples (`EndpointApiSample`, `EndpointApiInvocationHandler`, `Customer`) | +| Generic proxy gateway | `JaxrsEndpointApiCtClassBuilder → toInstance(InvocationHandler)` | Every generated method call dispatches to your handler | +| Import a 3rd-party OpenAPI on the fly | Parse OpenAPI → materialise `RestMethod[]` → call `Jaxrs*Builder.newMethod(...)` repeatedly | A bundle of resource classes with fully-populated `@Path / @GET / @PathParam` annotations | +| Automatically proxy legacy SOAP services | `JaxwsEndpointApiCtClassBuilder.webService(...) + toInstance(handler)` | A `@WebService` class whose every method enters your dispatch handler | +| Offline bytecode analysis | `build() → CtClass → toBytecode() / writeFile(dumpDir)` | Bytecode written to disk; readable via javap / IDA / annotation reflect | + +## 2. Features & Status + +| Capability | Status | Details | Evidence | +| :--- | :---: | :--- | :--- | +| JAX-WS endpoint Builder | ✅ Stable | `webService / webServiceProvider / addressing / serviceMode / bind / makeField / makeMethod / newMethod / build / toClass / toInstance` | `JaxwsEndpointApiImplCtClassBuilderTest` (13 tests) | +| JAX-RS resource Builder | ✅ Stable | `path / produces / bind / makeField / newField / removeField / newMethod(rtClass, HttpMethodEnum, name, path, RestParam...) / removeMethod / build / toClass / toInstance` | `JaxrsEndpointApiCtClassBuilderTest` (26 tests) | +| SOAP definition objects | ✅ Stable | `SoapService / SoapMethod / SoapParam / SoapResult / SoapBound` | Corresponding POJO tests cover all 4 non-default fields | +| REST definition objects | ✅ Stable | `RestBound / RestMethod / RestParam / RestProduce / HttpMethodEnum / HttpParamEnum` | `RestParamTest` (8 tests) + builder regressions (26+26+10) | +| Utils / annotation injection | ✅ Stable (**Bug#3 fixed 2026-08-20**) | `JaxwsEndpointApiUtils.annotParams` / `JaxrsEndpointApiUtils.annotParams` translate definition POJOs → Javassist `Annotation[][]` | `Jaxrs*InterfaceCtClassBuilderTest` (26 tests) | +| Bytecode export + `toInstance(InvocationHandler)` | ✅ Stable | `CtClass.toClass()`, `toBytecode()`, `writeFile()`, plus `Proxy.newProxyInstance` | `EndpointApiSample` / `EndpointApiInvocationHandler` / `Customer` | + +> For the 2026-08-20 CodeGraph-based fix details see [TECHNICAL-DESIGN.md §3 / §4](./TECHNICAL-DESIGN.md#3-codegraph-语义代码审查结论) (Chinese; all code snippets and anchors are still readable to English readers). ## 3. Requirements & Compatibility -| Requirement | Version / Notes | -| :--- | :--- | -| JDK | 17+ | -| Maven | 3.0+ (enforced; Maven Wrapper `./mvnw` included) | -| Apache CXF | 4.x (`cxf-rt-frontend-jaxws` / `cxf-rt-frontend-jaxrs`, managed by this pom) | -| Javassist | 3.30.2-GA (managed by this pom) | -| Companion library | `io.github.easy4j:javassist-plus` (same version line) | +### 3.1 Baseline requirements -Version lines: +| Dependency | Minimum version | Recommended version | Notes | +| :--- | ---: | ---: | :--- | +| JDK (branch-dependent) | see §9 | 8u3xx / 17u20 / latest 21 | Long-term parallel maintenance on 3 baselines | +| Maven | 3.0+ | 3.9.16 | Maven Enforcer rule in pom.xml | +| Apache CXF | 4.x | 4.0.x (1.0.x) / 4.1.x (2.0.x) / 4.2.x (3.0.x / main) | Locked via dependencyManagement in each line | +| Javassist | 3.30.2-GA | 3.30.2-GA | Same | +| javassist-extension | Same as this artifact | Same | `io.github.easy4j:javassist-extension`, released together | +| commons-lang3 | 3.20.0 | 3.20.0 | Final JDK 8-compatible release; shared across 4 lines (see §10.2) | +| commons-io | 2.22.0 | 2.22.0 | Final JDK 8-compatible release; shared across 4 lines | +| commons-beanutils | 1.11.0 | 1.11.0 | Latest 1.x release (beanutils 2.x not GA); shared across 4 lines | -| Branch | JDK | Version | -| :--- | :--- | :--- | -| `feature/1.0.x` | 8 | `1.0.x.*` | -| `feature/2.0.x` | 17 | `2.0.x.*` | -| `feature/3.0.x` | 21 | `3.0.x.*` | +### 3.2 Compatibility matrix + +| Line | JDK | CXF baseline | Status | Maintenance policy | +| :--- | :---: | :---: | :---: | :--- | +| `feature/3.0.x` | 21 | CXF 4.2.x | ✅ Active dev | New features + bug fixes | +| `main` | 17 / 21 | CXF 4.2.x | ✅ Primary release | Syncs fixes from 3.0.x; GA channel | +| `feature/2.0.x` | 17 | CXF 4.1.x | 🛠️ Maintenance | Severe bugs, CVE, dependency bumps only | +| `feature/1.0.x` | 8 | CXF 4.0.x | 🛠️ Maintenance | Blocking bugs + CVE only; JDK-11+ only dependencies are FORBIDDEN | + +### 3.3 Dependency boundary + +- This project is a **single-module jar**. Dependencies are strictly kept to the "bytecode-generation minimal set": Javassist, javassist-extension, CXF frontends (compile scope because generated bytecodes require annotation types resolvable in the ClassPool), plus the Apache Commons trio for JSON binding / bean-copy utility. +- No Spring, Jakarta Servlet, Jakarta Validation, or any container-level dependency is introduced. Downstream consumers bring their own container. ## 4. Architecture & Modules +### 4.1 High-level view + ```text -+------------------+ +------------------------------------------+ -| Developer | | cxf-rt-javassist | -| |-->| JaxwsEndpointApiCtClassBuilder | -| declarative | | (webService / newMethod / bind) | -| endpoint spec | | JaxrsEndpointApiCtClassBuilder | -| | | (path / produces / newMethod) | -| | | definitions: Soap*, Rest* | -| | | base: EndpointApi (shared) | -+------------------+ +-------------------+----------------------+ - | - v - +-------------------------------------------+ - | CtClass -> Class / instance | - | toClass / toInstance(handler) | - | -> CXF JAX-WS / JAX-RS implementation | - +-------------------------------------------+ +┌─────────────── Business app ───────────────┐ +│ Gateway / Proxy / Dynamic exporter │ +│ pulls: io.github.easy4j:cxf-rt-javassist │ +└──────────────┬─────────────────────────────┘ + │ Declarative: SoapBound / RestBound + ▼ +┌──────────────────────────────────────────────────────┐ +│ cxf-rt-javassist (single-module jar) │ +│ ┌──────────────────────┐ ┌──────────────────────┐ │ +│ │ Jaxws builder side │ │ Jaxrs builder side │ │ +│ │ JaxwsEndpointApiCt… │ │ JaxrsEndpointApiCt… │ │ +│ │ definition: Soap* │ │ definition: Rest* │ │ +│ └──────────┬────────────┘ └──────────┬────────────┘ │ +│ ▼ shared EndpointApi base ▼ │ +│ EndpointApi (implements IEndpointApi) │ +│ Utils: JaxwsEndpointApiUtils / JaxrsEndpointApiUtils │ +└───────────────────────┬────────────────────────────────┘ + │ CtClass / Class / Proxied instance + ▼ + Apache CXF 4.x frontend runtime + ┌─ JAX-WS Endpoint.publish ─┐ + └─ JAX-RS Server / Feature ─┘ ``` -Single-module Maven project (`packaging: jar`). No child modules. +### 4.2 Symmetric builder pattern -| Artifact | Responsibility | -| :--- | :--- | -| `io.github.easy4j:cxf-rt-javassist` | Runtime class builders, SOAP / REST definition model, utils | +`JaxwsEndpointApiCtClassBuilder` ↔ `JaxrsEndpointApiCtClassBuilder` are **sibling builders** with strictly isomorphic API shapes: -Key packages: +| Stage | SOAP (Jaxws) | REST (Jaxrs) | +| :--- | :--- | :--- | +| Init | `new JaxwsEndpointApiCtClassBuilder(fullyQualifiedClassName)` | `new JaxrsEndpointApiCtClassBuilder(fullyQualifiedClassName)` | +| Header annotations | `webService(name, tns, serviceName)` / `webServiceProvider(...)` / `addressing(...)` / `serviceMode(Mode.PAYLOAD)` | `path(root)` + `produces(MediaType...)` | +| Bind | `bind(String uid, String json)` / `bind(SoapBound)` | `bind(String uid, String json)` / `bind(RestBound)` | +| Fields | `makeField(src)` / `newField(type, name, value)` / `removeField(name)` | identical API | +| Methods | `makeMethod(src)` / `newMethod(name, SoapParam...)` / `removeMethod(...)` | `newMethod(rtClass, HttpMethodEnum, name, subPath, RestParam...)` / `removeMethod(...)` | +| Outputs | `build() → CtClass` / `toClass() → Class` / `toInstance(InvocationHandler) → Object` | identical API | -| Package | Content | -| :--- | :--- | -| `org.apache.cxf.endpoint.jaxws` | `JaxwsEndpointApiCtClassBuilder`, `JaxwsEndpointApiImplCtClassBuilder`, `JaxwsEndpointApiInterfaceCtClassBuilder` | -| `org.apache.cxf.endpoint.jaxws.definition` | `SoapService`, `SoapMethod`, `SoapParam`, `SoapResult`, `SoapBound` | -| `org.apache.cxf.endpoint.jaxrs` | `JaxrsEndpointApiCtClassBuilder`, `JaxrsEndpointApiImplCtClassBuilder`, `JaxrsEndpointApiInterfaceCtClassBuilder` | -| `org.apache.cxf.endpoint.jaxrs.definition` | `RestBound`, `RestMethod`, `RestParam`, `RestProduce`, `HttpMethodEnum`, `HttpParamEnum` | -| `org.apache.cxf.endpoint` | `EndpointApi` (base class) | -| `org.apache.cxf.endpoint.utils` | `JaxwsEndpointApiUtils`, `JaxrsEndpointApiUtils` | +> Review rule (ADR-002): When adding capabilities or fixing bugs, always walk both sides; only when the SOAP / REST semantic gap is fundamental (e.g. REST has `@PathParam` / `@DefaultValue`, SOAP does not) may a capability land on a single side without corresponding touch. -## 5. Installation +### 4.3 Package map (maps directly to §1.2 use cases) -The project is **not yet published to Maven Central**. Snapshots/releases are -distributed through the Aliyun Maven repository and GitHub Releases. +| Package | Content | Typical scenario | +| :--- | :--- | :--- | +| `org.apache.cxf.endpoint` | `EndpointApi` base class, `IEndpointApi` interface | Reuse `bind` + `toInstance(InvocationHandler)` contract | +| `org.apache.cxf.endpoint.jaxws` | `JaxwsEndpointApiCtClassBuilder`, `…ImplCtClassBuilder`, `…InterfaceCtClassBuilder` | Generate SOAP impl / interface / impl-with-bound-data classes | +| `org.apache.cxf.endpoint.jaxws.definition` | `SoapService / SoapMethod / SoapParam / SoapResult / SoapBound` | Declarative SOAP description | +| `org.apache.cxf.endpoint.jaxrs` | `JaxrsEndpointApiCtClassBuilder`, `…ImplCtClassBuilder`, `…InterfaceCtClassBuilder` | Generate JAX-RS impl / interface / impl-with-bound-data classes | +| `org.apache.cxf.endpoint.jaxrs.definition` | `RestBound / RestMethod / RestParam / RestProduce / HttpMethodEnum / HttpParamEnum` | Declarative REST description | +| `org.apache.cxf.endpoint.utils` | `JaxwsEndpointApiUtils / JaxrsEndpointApiUtils` | Translate POJOs → Javassist `Annotation[][]` then inject into `CtMethod` / `CtClass` | -Maven: +## 5. Installation + +### 5.1 Maven ```xml io.github.easy4j cxf-rt-javassist - 2.0.x.x.20260630-SNAPSHOT + + 3.0.x.x.20260630-SNAPSHOT ``` -Gradle: +### 5.2 Gradle -```groovy -implementation 'io.github.easy4j:cxf-rt-javassist:2.0.x.x.20260630-SNAPSHOT' +```kotlin +dependencies { + implementation("io.github.easy4j:cxf-rt-javassist:3.0.x.x.20260630-SNAPSHOT") +} ``` +### 5.3 Repository note + +The project is **not yet published to Maven Central**. Snapshots / releases are distributed through the Aliyun private Maven repository and GitHub Releases until Central onboarding is complete. Contact repository maintainers for the private repo URL. + ## 6. Quick Start -Build a JAX-WS endpoint class at runtime (adapted from the checked-in test): +### 6.1 Minimal JAX-WS example (build `@WebService` class then `toClass`) ```java import javassist.CtClass; @@ -162,25 +192,13 @@ CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.apache.cxf.spring.boot Class clazz = ctClass.toClass(); Object endpoint = clazz.getConstructor().newInstance(); +// endpoint is now a fully-annotated @WebService instance; pass it to +// org.apache.cxf.jaxws.EndpointImpl.publish(addr) to expose. ``` -Expected result: a new class `org.apache.cxf.spring.boot.FirstCaseV1` is created, -annotated with `@WebService`, with a `String sayHello(String text)` method — usable -as a CXF JAX-WS endpoint implementation. - -## 7. Configuration - -The library has no configuration file or property prefix. Everything is driven by -the builder chain and the definition objects: +**Expected**: A `sayHello(String text)` method exists on the generated class; reflection can read `@WebService(name="get", targetNamespace="http://ws.cxf.com", serviceName="getxx")`; the `uid` field is initialised in the zero-arg constructor. -| Builder | Key methods | Description | -| :--- | :--- | :--- | -| `JaxwsEndpointApiCtClassBuilder` | `webService(name, targetNamespace[, serviceName])`, `webServiceProvider(wsdlLocation, serviceName, ...)`, `addressing(boolean, boolean, Responses)`, `serviceMode(Service.Mode)`, `bind(uid, json)` / `bind(SoapBound)`, `makeField(src)`, `makeMethod(src)`, `newMethod(name, SoapParam...)`, `build()` / `toClass()` / `toInstance(InvocationHandler)` | JAX-WS endpoint generation | -| `JaxrsEndpointApiCtClassBuilder` | `path(path)`, `produces(mediaTypes...)`, `bind(uid, json)` / `bind(RestBound)`, `makeField(src)`, `newField(type, name, value)`, `removeField(name)`, `newMethod(rtClass, HttpMethodEnum, name, path, RestParam...)`, `removeMethod(...)`, `build()` / `toClass()` / `toInstance(InvocationHandler)` | JAX-RS resource generation | - -## 8. Core Usage / API - -### 8.1 JAX-RS resource generation +### 6.2 Minimal JAX-RS example (build a `@Path("/getxx")` resource) ```java import javassist.CtClass; @@ -194,51 +212,188 @@ CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.apache.cxf.spring.boot .newMethod(String.class, HttpMethodEnum.GET, "sayHello", "{id}/info", new RestParam(String.class, "id", HttpParamEnum.PATH)) .build(); + +// Verify via reflection: +Class clazz = ctClass.toClass(); +Path classPath = clazz.getAnnotation(Path.class); +assertEquals("getxx", classPath.value()); // PASS ``` -### 8.2 Dispatch to an `InvocationHandler` +> ⚠️ **Historical bug pre-2026-08-20**: Bug#1 used to silently downgrade `HttpParamEnum.PATH` → `QUERY` (see Tech Design §4.1). All four lines (3.0.x, main, 2.0.x, 1.0.x) have been fixed. + +### 6.3 Dispatch to a custom `InvocationHandler` (dynamic gateway pattern) ```java import java.lang.reflect.InvocationHandler; -import org.apache.cxf.endpoint.jaxws.JaxwsEndpointApiCtClassBuilder; +import org.apache.cxf.endpoint.jaxrs.JaxrsEndpointApiCtClassBuilder; +import org.apache.cxf.endpoint.jaxrs.definition.HttpMethodEnum; +import org.apache.cxf.endpoint.jaxrs.definition.HttpParamEnum; +import org.apache.cxf.endpoint.jaxrs.definition.RestParam; -InvocationHandler handler = new EndpointApiInvocationHandler(); // your handler -Object endpoint = new JaxwsEndpointApiCtClassBuilder("org.apache.cxf.spring.boot.FirstCaseV2") - .webService("get", "http://ws.cxf.com", "getxx") - .newMethod("sayHello", new SoapParam(String.class, "text")) - .toInstance(handler); // method calls are dispatched to the handler +// Your business dispatcher — e.g. forward to Feign / Dubbo / a local service +InvocationHandler handler = new InvocationHandler() { + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("sayHello".equals(method.getName())) return "Hello, " + args[0]; + throw new UnsupportedOperationException(method.getName()); + } +}; + +Object resource = new JaxrsEndpointApiCtClassBuilder("com.example.DynamicHelloResource") + .path("hello") + .newMethod(String.class, HttpMethodEnum.GET, "sayHello", "{name}", + new RestParam(String.class, "name", HttpParamEnum.PATH, "world")) + // Bug#2 + Bug#3 are both fixed: from=PATH actually applies; def="world" actually applies + .toInstance(handler); + +// resource.getClass().getMethod("sayHello", String.class).invoke(resource, "Alice") → "Hello, Alice" ``` -Note: after `toClass()` / `toBytecode()` / `writeFile()`, Javassist freezes the -`CtClass`; call `defrost()` before further modification (see the checked-in tests). +> 💡 **Tip**: `toInstance(handler)` is the **only entry point that combines both** (a) correctly-annotated class and (b) interceptible methods. Prefer it for generic gateway scenarios. Classes produced by `toClass()` have the default method body template inside the generated bytecode (nulls / zeros / default returns); plug in your business logic either via `ImplCtClassBuilder` or via `toInstance(handler)`. -## 9. Testing & Build +## 7. Core Usage / API + +### 7.1 Definition objects cheat sheet + +**`HttpMethodEnum` (REST)**: +```java +public enum HttpMethodEnum { GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS } +// Maps one-to-one to jakarta.ws.rs.@GET / @POST / ... +``` + +**`HttpParamEnum` (REST)**: +```java +public enum HttpParamEnum { + PATH, // @PathParam + QUERY, // @QueryParam (field default; was the accidental "drop everything to QUERY" target of Bug#1 / Bug#2) + HEADER, // @HeaderParam + COOKIE, // @CookieParam + FORM, // @FormParam + MATRIX, // @MatrixParam + BEAN // @BeanParam +} +// getAnnotationType() returns the FQCN of the corresponding jakarta.ws.rs annotation; consumed by JaxrsEndpointApiUtils.annotParams +``` + +**`RestParam` constructors (4 overloads; all 4 fields correctly assigned after the 2026-08-20 fixes)**: + +| Signature | Field assignment | +| :--- | :--- | +| `RestParam(Class type, String name)` | `type/name` set; `from=QUERY` (default); `def=null` | +| `RestParam(Class type, String name, HttpParamEnum from)` | All 3 set ✅ (Bug#1 fixed) | +| `RestParam(Class type, String name, String def)` | All 3 set; `from=QUERY` (default) | +| `RestParam(Class type, String name, HttpParamEnum from, String def)` | All 4 set ✅ (Bug#2 fixed; duplicate `this.name=name` removed) | + +### 7.2 Common builder methods + +**`JaxrsEndpointApiCtClassBuilder`**: + +| Method | Returns | Purpose | +| :--- | :--- | :--- | +| `path(String rootPath)` | this | Set class-level `@Path(rootPath)` | +| `produces(String... mediaTypes)` | this | Set `@Produces({...})` | +| `newField(Class type, String name, Object value)` | this | Create + initialise field | +| `removeField(String name)` | this | Drop a field (used to mutate a default template) | +| `newMethod(Class rtClass, HttpMethodEnum method, String name, String subPath, RestParam... params)` | this | Add a method: return type / HTTP verb / name / `@Path(subPath)` / params | +| `removeMethod(String name, Class... params)` | this | Drop a method | +| `build()` | `CtClass` | Finish, but **do not** `toClass()` yet (can defrost & mutate further) | +| `toClass()` | `Class` | Call `CtClass.toClass()` into the thread-context ClassLoader | +| `toInstance(InvocationHandler h)` | `Object` | `toClass()` first, then `Proxy.newProxyInstance(IEndpointApi, h)` and inject the proxy into the generated class's EndpointApi `invocationHandler` slot | + +**`JaxwsEndpointApiCtClassBuilder`**: symmetric equivalent (§4.2). + +### 7.3 `@DefaultValue` after Bug#3 + +```java +// @DefaultValue("1") now actually ends up in the bytecode: +CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("com.example.PageResource") + .path("items") + .newMethod(List.class, HttpMethodEnum.GET, "list", "{category}/page", + new RestParam(String.class, "category", HttpParamEnum.PATH), + new RestParam(Integer.class, "page", HttpParamEnum.QUERY, "1")) + .build(); + +// Reflection: Method.getParameterAnnotations()[1] == [@QueryParam("page"), @DefaultValue("1")] +``` + +## 8. Testing & Build + +### 8.1 Common commands ```bash -./mvnw clean verify +./mvnw clean verify # compile + unit tests + JaCoCo 90% rule check +./mvnw test -B -q # quiet unit tests +./mvnw clean compile -DskipTests # compile only +./mvnw -Prelease -Dgpg.skip=true package # source + javadoc jar ``` -- The build is configured with the JaCoCo Maven plugin (report + `check` goal with a - 90% line-coverage rule bound to the `verify` phase; `haltOnFailure=false`). -- Checked-in tests: `JaxwsApiCtClassBuilder_Test`, `JaxrsApiCtClassBuilder_Test` - (class building, instance dispatch, bytecode export). -- No CI workflow files are present under `.github/` in this worktree. +### 8.2 Test matrix + +| Kind | Tool / command | Scale | +| :--- | :--- | :--- | +| Unit tests | JUnit 4 + Maven Surefire | 202 / 202 / 202 / 224 (four lines) | +| Bytecode assertion | JUnit + reflection against `ctClass.toClass().getAnnotation(...)` | `@Path/@QueryParam/@DefaultValue/@WebService/@WebParam` coverage | +| Coverage report | JaCoCo `prepare-agent` + `report` + `check` | **Mandatory** `@{argLine}` prefix (ADR-004); without it JaCoCo is never attached and reports 0% | +| Multi-JDK verification | Run `mvn test` inside each of the 4 worktrees with the matching `JAVA_HOME` | JDK 8 / 17 / 21 each execute independently | + +### 8.3 Release gates + +- Maven Enforcer (version baselines + banned-deps list) +- JaCoCo 90% instruction coverage (bundle level, `haltOnFailure=false` but CI fails by reading the report) +- No cross-version Commons trio mixing (verified via `dependency:tree`) +- All 4 fields of every definition-object POJO test have an explicit assertion (ADR-005) + +## 9. Versioning & Branches + +| Worktree location on disk | Git branch | JDK | `` | Status | +| :--- | :--- | :---: | :---: | :---: | +| `cxf-rt-javassist/` (primary checkout) | `feature/3.0.x` | 21 | `21` | Active dev | +| `.worktrees/cxf-rt-javassist-main/` | `main` | 17 / 21 | `21` | Primary release | +| `.worktrees/cxf-rt-javassist-2.0.x/` | `feature/2.0.x` | 17 | `17` | Maintenance | +| `.worktrees/cxf-rt-javassist-1.0.x/` | `feature/1.0.x` | 8 | `1.8` | Maintenance | + +- **Only JDK / framework-version deltas are allowed to diverge**; business logic, definitions, tests, and docs must stay byte-identical across lines. +- Any shared bug (like the three documented here) **must be patched across all 4 lines atomically**. + +## 10. FAQ + +### 10.1 Why no `--add-opens` on the 1.0.x surefire line? -## 10. Versioning & Branches +JDK 8 **predates the Java Platform Module System (JPMS)**, so it literally **cannot parse** `--add-opens`. Adding the flag would kill the surefire forked JVM immediately with `Unrecognized option: --add-opens`. Conversely, on all JDK ≥ 17 lines, the two `--add-opens` clauses (`java.base/java.lang=ALL-UNNAMED` and `java.base/java.lang.reflect=ALL-UNNAMED`) are **mandatory** — without them, Javassist writing to internal `jdk.internal.reflect.ConstructorAccessor` helpers triggers an `IllegalAccessError` from the strong encapsulation of JPMS. See Tech Design §4.4 and ADR-004. -| Branch | JDK | Version | Notes | +### 10.2 Why not upgrade commons-lang3 to 3.21.0 / commons-io to 2.23.0? + +`commons-lang3 3.21.0` and `commons-io 2.23.0` already raise their minimum Java floor to **JDK 11**, which is incompatible with the `feature/1.0.x` JDK 8 line. Per ADR-003, **until the JDK-8 line is formally declared EOL, all four lines share the same version numbers**, so the project pins the last JDK-8-compatible releases across the board: `3.20.0 / 2.22.0 / 1.11.0`. See Tech Design §5.1. + +### 10.3 Can I mutate a `CtClass` after `toClass()`? + +No — after `toClass() / toBytecode() / writeFile()`, Javassist **freezes** the `CtClass`. Any later `set*` / `addMethod` throws `RuntimeException: cannot modify frozen class`. Workarounds: +- **Preferred (A)**: finish all builder mutations before any `build → toClass` call. +- **Plan B**: Call `ctClass.defrost();` first, then mutate, then `build → toClass` again. Used pervasively inside the `removeField` tests. + +### 10.4 Which behaviours changed in the 2026-08-20 bug-fix batch? + +Three shared-code fixes applied across all four lines: + +| Bug | Pre-fix behaviour | Post-fix behaviour | Downstream impact | | :--- | :--- | :--- | :--- | -| `feature/1.0.x` | 8 | `1.0.x.*` | Current branch, JDK 8 baseline, maintained | -| `feature/2.0.x` | 17 | `2.0.x.*` | JDK 17 line | -| `feature/3.0.x` | 21 | `3.0.x.*` | JDK 21 line | +| Bug#1 (3-arg RestParam ctor) | Explicit `from` discarded; always resolves to QUERY | User-provided `HttpParamEnum` honoured | Code that worked around Bug#1 by **reading params as query strings anyway** needs to stop doing so. Listed as behaviour-fix in CHANGELOG. | +| Bug#2 (4-arg RestParam ctor) | `from` discarded + `this.name` written twice | All 4 fields correct; duplicate write eliminated | Same impact scope as Bug#1; also affects any param that relies on a `@DefaultValue` together with an explicit PATH/HEADER/... binding. | +| Bug#3 (annotParams) | `@DefaultValue` object allocated but **never written to the array**; slot 1 is the duplicate paramAnnot | `[paramAnnot, defAnnot]` correctly written | If you relied on "default values always null / 0" you need explicit compatibility handling. | -Maintenance policy: the `1.0.x` line receives bug fixes and compatibility updates -for the JDK 8 baseline. New features targeting newer JDKs land on the `2.0.x` / -`3.0.x` lines. Releases are published to the Aliyun Maven repository and as -GitHub Releases; the project is not yet published to Maven Central. +For the full root-cause analysis, CodeGraph symbolic-execution evidence, and exact patch lines see [TECHNICAL-DESIGN.md](./TECHNICAL-DESIGN.md) (written in Chinese, all file anchors / code sections / test counts are language-agnostic). ## 11. Contributing & License -Contributions are welcome — please open issues or pull requests on GitHub. +- Before opening a PR, run `mvn clean verify` inside the target worktree, then re-run inside the other three lines because ADR-001 requires shared logic to stay byte-identical across 4 JDKs. Include tail of the Surefire report + JaCoCo coverage screenshot in your PR description. +- Any cross-cutting bug fix must be applied to the 4 branches together before submitting. +- Report security issues privately via GitHub Security Advisories. Do **not** open a public issue with exploit details. + +Licensed under the [Apache License 2.0](./LICENSE). + +--- + +
+ +[Back to top](#readme-top) · [Technical Design (zh-CN)](./TECHNICAL-DESIGN.md) · [Issues](https://github.com/easy-4-java/cxf-rt-javassist/issues) -Licensed under the [Apache License, Version 2.0](LICENSE). +
diff --git a/README.zh-CN.md b/README.zh-CN.md index bb22df0..fed4363 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,149 +1,184 @@ # cxf-rt-javassist -[English](./README.md) | [简体中文](./README.zh-CN.md) + -[![Java](https://img.shields.io/badge/Java-17-orange)](https://github.com/easy-4-java/cxf-rt-javassist) [![License](https://img.shields.io/badge/license-Apache%202.0-green)](./LICENSE) +
-> 使用 [Javassist](https://www.javassist.org) 生成基于 Apache CXF 的 JAX-WS / -> JAX-RS 实现:流式 `CtClass` 构建器在运行时产出带 `@WebService` / JAX-RS 注解的 -> 端点类。 +**在运行时用 Javassist 生成 Apache CXF JAX-WS / JAX-RS 实现类** -## 目录 +[![Java](https://img.shields.io/badge/Java-8%20%7C%2017%20%7C%2021-orange)](#3-运行要求与兼容性) +[![License](https://img.shields.io/badge/license-Apache%202.0-green)](./LICENSE) +[![Maven Central placeholder](https://img.shields.io/badge/Maven%20Central-pending-lightgray)](#5-引入依赖) -- [1. 项目概述](#1-项目概述) -- [2. 功能与状态](#2-功能与状态) -- [3. 环境要求与兼容性](#3-环境要求与兼容性) -- [4. 架构与模块](#4-架构与模块) -- [5. 安装](#5-安装) -- [6. 快速开始](#6-快速开始) -- [7. 配置](#7-配置) -- [8. 核心用法 / API](#8-核心用法--api) -- [9. 测试与构建](#9-测试与构建) -- [10. 版本与分支](#10-版本与分支) -- [11. 贡献与许可](#11-贡献与许可) +[English](./README.md) · [简体中文](./README.zh-CN.md) · [技术方案与修复报告](./TECHNICAL-DESIGN.md) -## 1. 项目概述 +[定位](#1-项目定位) · [特性状态](#2-核心能力与状态) · [兼容性](#3-运行要求与兼容性) · [架构](#4-架构与模块) · +[引入依赖](#5-引入依赖) · [快速开始](#6-快速开始) · [API 参考](#7-核心用法-api) · [测试构建](#8-构建与测试) · +[分支策略](#9-版本线与兼容策略) · [FAQ](#10-faq) · [贡献](#11-贡献与许可证) -`cxf-rt-javassist` 使用 [Javassist](https://www.javassist.org) 在运行时生成 Apache -CXF JAX-WS / JAX-RS 端点实现。两个流式构建器把声明式描述转换为真实、可加载的类: +
-- **`JaxwsEndpointApiCtClassBuilder`** — 生成 `@WebService` 端点类(名称、目标命名 - 空间、服务名、WSDL provider 模式、addressing、service mode、绑定数据、字段与 - SOAP 方法)。 -- **`JaxrsEndpointApiCtClassBuilder`** — 生成 JAX-RS 资源类(`@Path`、produces、 - 带 `HttpMethodEnum` / `RestParam` 的 REST 方法、绑定数据、字段)。 +--- -生成的类继承共享的 `EndpointApi` 基类;通过 `toInstance(InvocationHandler)` 可得到 -方法调用被分发给自定义处理器的实例——非常适合代理 / 可插拔端点逻辑。 +> **当前版本**:`3.0.x.x-SNAPSHOT`(按分支各自演进,见 §9)
+> **JDK 基线**:8 / 17 / 21(四分支并发维护)
+> **构建工具**:Maven 3.0+(Maven Wrapper `./mvnw` 已包含)
+> **最后核验**:2026-08-20(四线 `mvn test` 全绿,共 202 / 202 / 202 / 224 用例) -构建器依赖姊妹库 `io.github.easy4j:javassist-plus`(`CtFieldBuilder`、 -`ClassPoolFactory`、`JavassistUtils`)。 +## 1. 项目定位 -它不是: +**`cxf-rt-javassist` 是一个面向 Java 框架开发者 / 动态网关开发者的纯 Java 运行时代码生成库,基于 [Javassist](https://www.javassist.org) 与 `io.github.easy4j:javassist-extension`,把声明式的 SOAP / REST 端点定义直接翻译为带 `@WebService` 或 JAX-RS 注解的可加载 `Class`。** -- CXF 运行时本身——提供服务生成的端点仍需要 Apache CXF frontend 构件。 -- Spring Boot starter——不提供自动装配。 - -典型场景: - -| 场景 | 使用内容 | +| 维度 | 定位 | | :--- | :--- | -| 运行时生成 `@WebService` 端点类 | `JaxwsEndpointApiCtClassBuilder` + `webService(...)` / `newMethod(...)` | -| 运行时生成 JAX-RS 资源类 | `JaxrsEndpointApiCtClassBuilder` + `path(...)` / `newMethod(...)` | -| 把生成的方法调用分发给处理器 | `toInstance(InvocationHandler)` | -| 导出生成的字节码离线检查 | `build()` → `CtClass` → `toBytecode()` / `writeFile()` | +| 本质 | 运行时字节码生成器;单模块 jar | +| 消费方 | Spring Boot Starter、API Gateway、通用代理框架、动态导出端点 | +| 核心能力 | ① JAX-WS 端点类生成 ② JAX-RS 资源类生成 ③ 字节码 / `Class` / 代理实例三种产出 ④ SOAP/REST 双 Builder 对称设计 | +| JDK(多线) | `feature/1.0.x` → JDK 8;`feature/2.0.x` → JDK 17;`feature/3.0.x` → JDK 21;`main` → JDK 17/21 主发行 | +| Maven 坐标 | `io.github.easy4j:cxf-rt-javassist` | +| 配置前缀 | 无(完全由 Builder 链式 API 驱动,不读属性文件) | -## 2. 功能与状态 +### 1.1 不是什么 -| 能力 | 状态 | 说明 | -| :--- | :--- | :--- | -| JAX-WS 类构建器 | 稳定 | `webService`、`webServiceProvider`、`addressing`、`serviceMode`、`bind`、`makeField`、`makeMethod`、`newMethod`、`build` / `toClass` / `toInstance` | -| JAX-RS 类构建器 | 稳定 | `path`、`produces`、`bind`、`makeField`、`newField`、`removeField`、`newMethod`(`HttpMethodEnum` / `RestMethod`)、`removeMethod`、`build` / `toClass` / `toInstance` | -| SOAP 定义模型 | 稳定 | `SoapService`、`SoapMethod`、`SoapParam`、`SoapResult`、`SoapBound` | -| REST 定义模型 | 稳定 | `RestBound`、`RestMethod`、`RestParam`、`RestProduce`、`HttpMethodEnum`、`HttpParamEnum` | -| 工具类 | 稳定 | `JaxwsEndpointApiUtils`、`JaxrsEndpointApiUtils` | -| 测试 | 稳定 | `JaxwsApiCtClassBuilder_Test`、`JaxrsApiCtClassBuilder_Test` + 样例(`EndpointApiSample`、`EndpointApiInvocationHandler`、`Customer`) | +- 不是 Apache CXF 运行时本身。要真正对外暴露生成的 SOAP / REST 端点,运行时需要额外引入 `cxf-rt-frontend-jaxws` / `cxf-rt-frontend-jaxrs`。 +- 不是 Spring Boot Starter。不做自动装配;也不引入 Spring 依赖。 +- 不承诺 JDK 版本矩阵外的组合(例如 JDK 11),本项目只维护 8 / 17 / 21 三条长期线。 -## 3. 环境要求与兼容性 - -| 要求 | 版本 / 说明 | -| :--- | :--- | -| JDK | 17+ | -| Maven | 3.0+(enforcer 强制;项目内置 Maven Wrapper `./mvnw`) | -| Apache CXF | 4.x(`cxf-rt-frontend-jaxws` / `cxf-rt-frontend-jaxrs`,由本 pom 管理) | -| Javassist | 3.30.2-GA(由本 pom 管理) | -| 姊妹库 | `io.github.easy4j:javassist-plus`(同一版本线) | +### 1.2 典型使用场景 -版本线: - -| 分支 | JDK | 版本 | +| 场景 | 使用方式 | 结果 | | :--- | :--- | :--- | -| `feature/1.0.x` | 8 | `1.0.x.*` | -| `feature/2.0.x` | 17 | `2.0.x.*` | -| `feature/3.0.x` | 21 | `3.0.x.*` | +| 通用代理网关 | `JaxrsEndpointApiCtClassBuilder → toInstance(InvocationHandler)` | 统一把所有方法调用转发到你的 InvocationHandler | +| 动态导入第三方 OpenAPI 定义 | 解析 OpenAPI → 实例化 `RestMethod[]` → `Jaxrs*Builder.newMethod(...)` | 生成一批 `@Path / @GET / @PathParam` 注解齐全的资源类 | +| 遗留 SOAP 服务自动代理 | `JaxwsEndpointApiCtClassBuilder.webService(...) + toInstance(handler)` | 生成 `@WebService` 类,方法调用全部打向你的 Handler | +| 离线分析 / 类导出 | `build() → CtClass → toBytecode() / writeFile(dumpDir)` | 把生成的字节码落盘,用 javap / IDA 分析注解 | + +## 2. 核心能力与状态 + +| 能力 | 状态 | 说明 | 验证证据 | +| :--- | :---: | :--- | :--- | +| JAX-WS 端点类 Builder | ✅ 稳定 | `webService / webServiceProvider / addressing / serviceMode / bind / makeField / makeMethod / newMethod / build / toClass / toInstance` | `JaxwsEndpointApiImplCtClassBuilderTest`(13 tests) | +| JAX-RS 资源类 Builder | ✅ 稳定 | `path / produces / bind / makeField / newField / removeField / newMethod(rtClass, HttpMethodEnum, name, path, RestParam...) / removeMethod / build / toClass / toInstance` | `JaxrsEndpointApiCtClassBuilderTest`(26 tests) | +| SOAP 定义对象 | ✅ 稳定 | `SoapService / SoapMethod / SoapParam / SoapResult / SoapBound` | 对应 POJO Test 覆盖 | +| REST 定义对象 | ✅ 稳定 | `RestBound / RestMethod / RestParam / RestProduce / HttpMethodEnum / HttpParamEnum` | `RestParamTest`(8 tests)+ 各 Builder 回归 | +| Utils 注解注入 | ✅ 稳定(2026-08-20 已修复 **Bug#3**) | `JaxwsEndpointApiUtils / JaxrsEndpointApiUtils.annotParams` 负责把 POJO → Javassist `Annotation[][]` | `Jaxrs*InterfaceCtClassBuilderTest`(26 tests) | +| 字节码导出 + `toInstance(InvocationHandler)` | ✅ 稳定 | Javassist `CtClass.toClass()`、`toBytecode()`、`writeFile()`、以及代理 `Proxy.newProxyInstance` | `EndpointApiSample` / `EndpointApiInvocationHandler` / `Customer` | + +> 2026-08-20 代码修复详情见 [TECHNICAL-DESIGN.md §3 / §4](./TECHNICAL-DESIGN.md#3-codegraph-语义代码审查结论)。 + +## 3. 运行要求与兼容性 + +### 3.1 基础要求 + +| 依赖 | 最低版本 | 推荐版本 | 说明 | +| :--- | ---: | ---: | :--- | +| JDK(按分支) | 见 §9 | 8u3xx / 17u20 / 21u最新 | 本项目按分支分 JDK 线长期并行 | +| Maven | 3.0+ | 3.9.16 | pom.xml 中已通过 Maven Enforcer 强制要求 | +| Apache CXF | 4.x | 4.0.x(1.0.x)/ 4.1.x(2.0.x)/ 4.2.x(3.0.x / main) | 由 pom.xml dependencyManagement 锁版 | +| Javassist | 3.30.2-GA | 3.30.2-GA | 同上 | +| javassist-extension | 同本项目版本号 | 同左 | `io.github.easy4j:javassist-extension`,同行维护 | +| commons-lang3 | 3.20.0 | 3.20.0 | JDK 8 线最新最终版(四线共用,见 §10.2) | +| commons-io | 2.22.0 | 2.22.0 | JDK 8 线最新最终版(四线共用) | +| commons-beanutils | 1.11.0 | 1.11.0 | beanutils 1.x 线最新版(四线共用) | + +### 3.2 版本兼容矩阵 + +| 项目版本线 | JDK | CXF 基线 | 状态 | 维护策略 | +| :--- | :---: | :---: | :---: | :--- | +| `feature/3.0.x` | 21 | CXF 4.2.x | ✅ 活跃开发 | 新功能 + 缺陷修复 | +| `main` | 17 / 21 | CXF 4.2.x | ✅ 主发行 | 同步 3.0.x 缺陷修复;对外发行 GA | +| `feature/2.0.x` | 17 | CXF 4.1.x | 🛠️ 维护 | 只接收严重缺陷、CVE、依赖升级 | +| `feature/1.0.x` | 8 | CXF 4.0.x | 🛠️ 维护 | 只接收阻塞缺陷与 CVE;严禁使用 JDK 11+ only 的依赖版本 | + +### 3.3 依赖边界 + +- 本项目是**单模块 jar**,依赖项按 "字节码生成最小集合" 约束:javassist + javassist-extension + Apache CXF 前端(provided 语义)+ commons 三件套(JSON 绑定 / 反射 Bean 拷贝需要)。 +- 不引入 Spring、Jakarta Servlet、Jakarta Validation 等容器级依赖;下游使用方按需引入。 +- `cxf-rt-frontend-jaxws / jaxrs` 不是 provided scope 而是 compile scope(生成的字节码需要注解类型在 ClassPool 中可解析)。 ## 4. 架构与模块 +### 4.1 一眼看懂 + ```text -+------------------+ +------------------------------------------+ -| Developer | | cxf-rt-javassist | -| |-->| JaxwsEndpointApiCtClassBuilder | -| declarative | | (webService / newMethod / bind) | -| endpoint spec | | JaxrsEndpointApiCtClassBuilder | -| | | (path / produces / newMethod) | -| | | definitions: Soap*, Rest* | -| | | base: EndpointApi (shared) | -+------------------+ +-------------------+----------------------+ - | - v - +-------------------------------------------+ - | CtClass -> Class / instance | - | toClass / toInstance(handler) | - | -> CXF JAX-WS / JAX-RS implementation | - +-------------------------------------------+ +┌─────────────── 业务应用 ───────────────┐ +│ Gateway / Proxy / Dynamic Exporter │ +│ 引入依赖: io.github.easy4j:cxf-rt-javassist │ +└──────────────┬────────────────────────┘ + │ 声明: SoapBound / RestBound + ▼ +┌──────────────────────────────────────────────────────┐ +│ cxf-rt-javassist (单模块 jar) │ +│ ┌──────────────────────┐ ┌──────────────────────┐ │ +│ │ Jaxws Builder 侧 │ │ Jaxrs Builder 侧 │ │ +│ │ JaxwsEndpointApiCt… │ │ JaxrsEndpointApiCt… │ │ +│ │ definition: Soap* │ │ definition: Rest* │ │ +│ └──────────┬───────────┘ └──────────┬────────────┘ │ +│ ▼ 共享 EndpointApi 基类 ▼ │ +│ EndpointApi (implements IEndpointApi) │ +│ Utils: JaxwsEndpointApiUtils / JaxrsEndpointApiUtils │ +└───────────────────────┬──────────────────────────────┘ + │ CtClass / Class / Instance + ▼ + Apache CXF 4.x 前端运行时 + ┌─ JAX-WS Endpoint.publish ─┐ + └─ JAX-RS Server / Feature ─┘ ``` -单模块 Maven 工程(`packaging: jar`),无子模块。 +### 4.2 Builder 对称设计 -| 构件 | 职责 | -| :--- | :--- | -| `io.github.easy4j:cxf-rt-javassist` | 运行时类构建器、SOAP / REST 定义模型、工具类 | +`JaxwsEndpointApiCtClassBuilder` ↔ `JaxrsEndpointApiCtClassBuilder` 是**姊妹 Builder**,API 结构严格同构: + +| 阶段 | SOAP 侧(Jaxws) | REST 侧(Jaxrs) | +| :--- | :--- | :--- | +| 初始化 | `new JaxwsEndpointApiCtClassBuilder(fullyQualifiedClassName)` | `new JaxrsEndpointApiCtClassBuilder(fullyQualifiedClassName)` | +| 注解头 | `webService(name, tns, serviceName)` / `webServiceProvider(...)` / `addressing(...)` / `serviceMode(Mode.PAYLOAD)` | `path(root)` + `produces(MediaType...)` | +| 绑定 | `bind(String uid, String json)` / `bind(SoapBound)` | `bind(String uid, String json)` / `bind(RestBound)` | +| 字段 | `makeField(src)` / `newField(type, name, value)` / `removeField(name)` | 左侧三法完全相同 | +| 方法 | `makeMethod(src)` / `newMethod(name, SoapParam...)` / `removeMethod(...)` | `newMethod(rtClass, HttpMethodEnum, name, subPath, RestParam...)` / `removeMethod(...)` | +| 产出 | `build() → CtClass` / `toClass() → Class` / `toInstance(InvocationHandler) → Object` | 左侧三法完全相同 | -关键包: +> 代码审查约束(ADR-002):新增能力 / 修复缺陷时,两侧必须成对走查;**除非 SOAP 与 REST 在语义上天然不同**(例如 REST 有 `@PathParam` / `@DefaultValue`,SOAP 没有对应概念),否则禁止只修一侧不修另一侧。 -| 包 | 内容 | -| :--- | :--- | -| `org.apache.cxf.endpoint.jaxws` | `JaxwsEndpointApiCtClassBuilder`、`JaxwsEndpointApiImplCtClassBuilder`、`JaxwsEndpointApiInterfaceCtClassBuilder` | -| `org.apache.cxf.endpoint.jaxws.definition` | `SoapService`、`SoapMethod`、`SoapParam`、`SoapResult`、`SoapBound` | -| `org.apache.cxf.endpoint.jaxrs` | `JaxrsEndpointApiCtClassBuilder`、`JaxrsEndpointApiImplCtClassBuilder`、`JaxrsEndpointApiInterfaceCtClassBuilder` | -| `org.apache.cxf.endpoint.jaxrs.definition` | `RestBound`、`RestMethod`、`RestParam`、`RestProduce`、`HttpMethodEnum`、`HttpParamEnum` | -| `org.apache.cxf.endpoint` | `EndpointApi`(基类) | -| `org.apache.cxf.endpoint.utils` | `JaxwsEndpointApiUtils`、`JaxrsEndpointApiUtils` | +### 4.3 关键包一览(与 §1.2 场景对应) -## 5. 安装 +| 包 | 内容 | 典型场景 | +| :--- | :--- | :--- | +| `org.apache.cxf.endpoint` | `EndpointApi` 公共基类、`IEndpointApi` 接口 | 通过基类复用 `bind` / `toInstance(InvocationHandler)` | +| `org.apache.cxf.endpoint.jaxws` | `JaxwsEndpointApiCtClassBuilder`、`…ImplCtClassBuilder`、`…InterfaceCtClassBuilder` | 生成 SOAP 实现类、接口、带实现的 endpoint | +| `org.apache.cxf.endpoint.jaxws.definition` | `SoapService / SoapMethod / SoapParam / SoapResult / SoapBound` | 声明式描述 SOAP 服务 | +| `org.apache.cxf.endpoint.jaxrs` | `JaxrsEndpointApiCtClassBuilder`、`…ImplCtClassBuilder`、`…InterfaceCtClassBuilder` | 生成 JAX-RS 实现类、接口、带实现的 resource | +| `org.apache.cxf.endpoint.jaxrs.definition` | `RestBound / RestMethod / RestParam / RestProduce / HttpMethodEnum / HttpParamEnum` | 声明式描述 REST 资源 | +| `org.apache.cxf.endpoint.utils` | `JaxwsEndpointApiUtils / JaxrsEndpointApiUtils` | 负责把定义对象翻译为 Javassist `Annotation[][]` 后注入 CtMethod | -项目**尚未发布到 Maven Central**。快照 / 发布版本通过阿里云 Maven 仓库与 GitHub -Releases 分发。 +## 5. 引入依赖 -Maven: +### 5.1 Maven ```xml io.github.easy4j cxf-rt-javassist - 2.0.x.x.20260630-SNAPSHOT + + 3.0.x.x.20260630-SNAPSHOT ``` -Gradle: +### 5.2 Gradle -```groovy -implementation 'io.github.easy4j:cxf-rt-javassist:2.0.x.x.20260630-SNAPSHOT' +```kotlin +dependencies { + implementation("io.github.easy4j:cxf-rt-javassist:3.0.x.x.20260630-SNAPSHOT") +} ``` +### 5.3 仓库说明 + +项目目前**尚未发布 Maven Central**。发布前快照 / Release 通过阿里云私服仓库与 GitHub Releases 分发。如需私服地址,请联系仓库维护者。 + ## 6. 快速开始 -运行时构建 JAX-WS 端点类(改编自仓库内已提交的测试): +### 6.1 最小 JAX-WS 示例(生成 `@WebService` 类并 `toClass`) ```java import javassist.CtClass; @@ -158,23 +193,12 @@ CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.apache.cxf.spring.boot Class clazz = ctClass.toClass(); Object endpoint = clazz.getConstructor().newInstance(); +// endpoint 现在是一个完整的 @WebService 实例,可交给 org.apache.cxf.jaxws.EndpointImpl.publish(addr) 暴露 ``` -预期结果:创建新类 `org.apache.cxf.spring.boot.FirstCaseV1`,带 `@WebService` 注解 -与 `String sayHello(String text)` 方法——可直接作为 CXF JAX-WS 端点实现使用。 - -## 7. 配置 - -本库没有配置文件或属性前缀。一切由构建器链与定义对象驱动: - -| 构建器 | 关键方法 | 说明 | -| :--- | :--- | :--- | -| `JaxwsEndpointApiCtClassBuilder` | `webService(name, targetNamespace[, serviceName])`、`webServiceProvider(wsdlLocation, serviceName, ...)`、`addressing(boolean, boolean, Responses)`、`serviceMode(Service.Mode)`、`bind(uid, json)` / `bind(SoapBound)`、`makeField(src)`、`makeMethod(src)`、`newMethod(name, SoapParam...)`、`build()` / `toClass()` / `toInstance(InvocationHandler)` | JAX-WS 端点生成 | -| `JaxrsEndpointApiCtClassBuilder` | `path(path)`、`produces(mediaTypes...)`、`bind(uid, json)` / `bind(RestBound)`、`makeField(src)`、`newField(type, name, value)`、`removeField(name)`、`newMethod(rtClass, HttpMethodEnum, name, path, RestParam...)`、`removeMethod(...)`、`build()` / `toClass()` / `toInstance(InvocationHandler)` | JAX-RS 资源生成 | - -## 8. 核心用法 / API +**预期结果**:`sayHello(String text)` 方法存在于生成的类;`@WebService(name="get", targetNamespace="http://ws.cxf.com", serviceName="getxx")` 注解可通过反射读取;`uid` 字段在默认构造器里被初始化为随机 UUID。 -### 8.1 JAX-RS 资源生成 +### 6.2 最小 JAX-RS 示例(生成 `@Path("/getxx")` 资源类) ```java import javassist.CtClass; @@ -188,50 +212,188 @@ CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.apache.cxf.spring.boot .newMethod(String.class, HttpMethodEnum.GET, "sayHello", "{id}/info", new RestParam(String.class, "id", HttpParamEnum.PATH)) .build(); + +// 验证:反射读注解 +Class clazz = ctClass.toClass(); +Path classPath = clazz.getAnnotation(Path.class); +assertEquals("getxx", classPath.value()); // 通过 ``` -### 8.2 分发到 `InvocationHandler` +> ⚠️ 2026-08-20 前的旧代码存在 Bug#1:上面 `HttpParamEnum.PATH` 会被静默退化成 QUERY(详见技术方案 §4.1)。当前主分支、main、2.0.x、1.0.x 均已修复。 + +### 6.3 调度到自定义 InvocationHandler(动态代理网关场景) ```java import java.lang.reflect.InvocationHandler; -import org.apache.cxf.endpoint.jaxws.JaxwsEndpointApiCtClassBuilder; +import org.apache.cxf.endpoint.jaxrs.JaxrsEndpointApiCtClassBuilder; +import org.apache.cxf.endpoint.jaxrs.definition.HttpMethodEnum; +import org.apache.cxf.endpoint.jaxrs.definition.HttpParamEnum; +import org.apache.cxf.endpoint.jaxrs.definition.RestParam; -InvocationHandler handler = new EndpointApiInvocationHandler(); // 你的处理器 -Object endpoint = new JaxwsEndpointApiCtClassBuilder("org.apache.cxf.spring.boot.FirstCaseV2") - .webService("get", "http://ws.cxf.com", "getxx") - .newMethod("sayHello", new SoapParam(String.class, "text")) - .toInstance(handler); // 方法调用被分发给该处理器 +// 你的业务调度逻辑:例如转发到 Feign / Dubbo / 本地服务 +InvocationHandler handler = new InvocationHandler() { + @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { + if ("sayHello".equals(method.getName())) return "Hello, " + args[0]; + throw new UnsupportedOperationException(method.getName()); + } +}; + +Object resource = new JaxrsEndpointApiCtClassBuilder("com.example.DynamicHelloResource") + .path("hello") + .newMethod(String.class, HttpMethodEnum.GET, "sayHello", "{name}", + new RestParam(String.class, "name", HttpParamEnum.PATH, "world")) // Bug#2/Bug#3 都已修复:from=PATH 生效 + def=world 生效 + .toInstance(handler); + +// resource.getClass().getMethod("sayHello", String.class).invoke(resource, "Alice") → "Hello, Alice" ``` -注意:`toClass()` / `toBytecode()` / `writeFile()` 之后 Javassist 会冻结 `CtClass`; -继续修改前需调用 `defrost()`(参见已提交的测试)。 +> 💡 `toInstance(handler)` 是唯一能同时得到"带注解的类 + 方法调用可被拦截"的入口,推荐给通用网关场景。`toClass()` 得到的类是普通 Javassist 字节码,方法体是空返回默认值或 0 / null(由 Builder 默认模板决定),需要业务逻辑请用 `ImplCtClassBuilder` 或 `toInstance`。 + +## 7. 核心用法 / API -## 9. 测试与构建 +### 7.1 定义对象速查 + +**HttpMethodEnum**(REST): +```java +public enum HttpMethodEnum { GET, POST, PUT, DELETE, PATCH, HEAD, OPTIONS } +// 对应 jakarta.ws.rs.@GET / @POST / @PUT / ... +``` + +**HttpParamEnum**(REST): +```java +public enum HttpParamEnum { + PATH, // @PathParam + QUERY, // @QueryParam(字段默认值;Bug#1/Bug#2 修前就是这个) + HEADER, // @HeaderParam + COOKIE, // @CookieParam + FORM, // @FormParam + MATRIX, // @MatrixParam + BEAN // @BeanParam +} +// getAnnotationType() → 对应 jakarta.ws.rs 的注解 FQCN;JaxrsEndpointApiUtils.annotParams 读它 +``` + +**RestParam 构造器(4 个,2026-08-20 已修复字段赋值完整性)**: + +| 构造器签名 | 字段行为 | +| :--- | :--- | +| `RestParam(Class type, String name)` | `type/name` 赋值;`from=QUERY`(默认);`def=null` | +| `RestParam(Class type, String name, HttpParamEnum from)` | 三项全部赋值 ✅(Bug#1 修完后) | +| `RestParam(Class type, String name, String def)` | 三项;`from=QUERY`(默认) | +| `RestParam(Class type, String name, HttpParamEnum from, String def)` | 四项全部赋值 ✅(Bug#2 修完后) | + +### 7.2 Builder 常用方法 + +`JaxrsEndpointApiCtClassBuilder`: + +| 方法 | 返回值 | 说明 | +| :--- | :--- | :--- | +| `path(String rootPath)` | this | 设置类级 `@Path(rootPath)` | +| `produces(String... mediaTypes)` | this | 设置 `@Produces({...})` | +| `newField(Class type, String name, Object value)` | this | 新建字段并初始化 | +| `removeField(String name)` | this | 移除已存在字段(用于默认字段) | +| `newMethod(Class rtClass, HttpMethodEnum method, String name, String subPath, RestParam... params)` | this | 新建方法:返回类型 / HTTP 方法 / 方法名 / `@Path(subPath)` / 参数 | +| `removeMethod(String name, Class... params)` | this | 删除已存在方法 | +| `build()` | `CtClass` | 构建完成(未 toClass,可继续 defrost 修改) | +| `toClass()` | `Class` | 调用 `CtClass.toClass()`,加载到当前线程 ClassLoader | +| `toInstance(InvocationHandler h)` | `Object` | 先 `toClass()`,再用 `Proxy.newProxyInstance(IEndpointApi, h)` 包装,最后把代理 set 到生成类的 `invocationHandler` 字段(EndpointApi 基类约定) | + +`JaxwsEndpointApiCtClassBuilder`:对称等价 API(§4.2)。 + +### 7.3 `@DefaultValue` 修复后示例(Bug#3) + +```java +// 现在 @DefaultValue("1") 会真的写进字节码: +CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("com.example.PageResource") + .path("items") + .newMethod(List.class, HttpMethodEnum.GET, "list", + "{category}/page", + new RestParam(String.class, "category", HttpParamEnum.PATH), + new RestParam(Integer.class, "page", HttpParamEnum.QUERY, "1")) + .build(); + +// 反射:Method.getParameterAnnotations()[1] 应得到 [@QueryParam("page"), @DefaultValue("1")] +``` + +## 8. 构建与测试 + +### 8.1 常用命令 ```bash -./mvnw clean verify +./mvnw clean verify # 编译 + 单元测试 + JaCoCo check(90% 指令覆盖率) +./mvnw test -B -q # 安静跑单测 +./mvnw clean compile -DskipTests # 只编译 +./mvnw -Prelease -Dgpg.skip=true package # 带源码 / javadoc 包 ``` -- 构建配置了 JaCoCo Maven 插件(报告 + 绑定在 `verify` 阶段的 `check` 目标, - 行覆盖率规则为 90%;`haltOnFailure=false`)。 -- 已提交测试:`JaxwsApiCtClassBuilder_Test`、`JaxrsApiCtClassBuilder_Test` - (类构建、实例分发、字节码导出)。 -- 本 worktree 的 `.github/` 下无 CI 工作流文件。 +### 8.2 测试矩阵 + +| 类型 | 工具 / 命令 | 说明 | +| :--- | :--- | :--- | +| 单元测试 | JUnit 4 + Maven Surefire | 202 / 202 / 202 / 224(四线) | +| 字节码断言 | JUnit + `ctClass.toClass().getAnnotation(...)` 反射读 | 覆盖 `@Path/@QueryParam/@DefaultValue/@WebService/@WebParam` | +| 覆盖率报告 | JaCoCo `prepare-agent` + `report` + `check` | **必须** `@{argLine}` 前缀(ADR-004);否则 JaCoCo 挂不上 | +| 多 JDK 验证 | 四线各自 worktree 单独跑 `mvn test` | JDK 8 / 17 / 21 分别执行 | -## 10. 版本与分支 +### 8.3 发布门禁 -| 分支 | JDK | 版本 | 说明 | +- Maven Enforcer(版本基线 + banned-deps) +- JaCoCo 覆盖率 90% 指令(`BUNDLE` 级别,`haltOnFailure=false` 但 CI 会读报告 fail) +- `dependency:tree` 无 commons 三件套跨版本混用 +- 对 `RestParamTest` 所有 4 个字段的断言都显式写了(ADR-005) + +## 9. 版本线与兼容策略 + +| 分支(git worktree 位置) | Git 分支 | JDK | `` | 状态 | +| :--- | :--- | :---: | :---: | :---: | +| `cxf-rt-javassist/`(主工作目录) | `feature/3.0.x` | 21 | 21 | 活跃开发 | +| `.worktrees/cxf-rt-javassist-main/` | `main` | 17 / 21 | 21 | 主发行 | +| `.worktrees/cxf-rt-javassist-2.0.x/` | `feature/2.0.x` | 17 | 17 | 维护 | +| `.worktrees/cxf-rt-javassist-1.0.x/` | `feature/1.0.x` | 8 | 1.8 | 维护 | + +- **只允许在 JDK/框架版本上的必要差异**:共享业务逻辑、域对象、测试、文档保持一致。 +- 任何通用 bug(如本次 3 个)**必须一次修复、四线同步**。 + +## 10. FAQ + +### 10.1 为什么 1.0.x 的 Surefire 不加 `--add-opens`? + +JDK 8 **没有** Java Platform Module System(JPMS),所以**无法识别 `--add-opens` 选项**——加了会直接导致 `Unrecognized option: --add-opens`,Maven Surefire 整个阶段 FAILED。对 JDK ≥ 17 的三条线则必须加两个 `--add-opens`:`java.base/java.lang=ALL-UNNAMED` 与 `java.base/java.lang.reflect=ALL-UNNAMED`,否则 Javassist 写 `jdk.internal.reflect.ConstructorAccessor` 等类时会被 JPMS 强封装拦死,运行时抛出 `IllegalAccessError`。详见技术方案 §4.4 与 ADR-004。 + +### 10.2 为什么不升级 commons-lang3 到 3.21.0 / commons-io 到 2.23.0? + +`commons-lang3 3.21.0` / `commons-io 2.23.0` 已经要求最低 **JDK 11**,与 `feature/1.0.x` 的 JDK 8 要求不兼容。ADR-003 规定:**在 1.0.x 分支(JDK 8)宣布 EOL 前,四条线共用同一版本号**,因此统一停留在 JDK 8 线的最终版:`3.20.0 / 2.22.0 / 1.11.0`。详见技术方案 §5.1。 + +### 10.3 `toClass()` 之后还能不能改? + +不行,Javassist 调用 `toClass() / toBytecode() / writeFile()` 之后会把 `CtClass` **冻结(frozen)**,后续 set* / addMethod 会抛 `RuntimeException: cannot modify frozen class`。解决方式: +1. **方案 A(推荐)**:先完成所有 Builder 调用再 build → toClass。 +2. **方案 B**:`ctClass.defrost();` 解冻后再改,改完再 build → toClass(测试中广泛使用,见 `JaxrsEndpointApiCtClassBuilderTest` 的 `removeField` 用例)。 + +### 10.4 Bug 修复了什么?怎么影响到我? + +2026-08-20 四线同步的 3 个修复: + +| Bug | 修前行为 | 修后行为 | 对下游影响 | | :--- | :--- | :--- | :--- | -| `feature/1.0.x` | 8 | `1.0.x.*` | 当前分支,JDK 8 基线,维护中 | -| `feature/2.0.x` | 17 | `2.0.x.*` | JDK 17 版本线 | -| `feature/3.0.x` | 21 | `3.0.x.*` | JDK 21 版本线 | +| Bug#1(三参构造器) | `from` 显式传参被丢,永久是 QUERY | 用户传的 `HttpParamEnum` 正确生效 | 如果你的代码**手工把参数当 QUERY 读**(绕开 Bug),修完后可能需要同时修正 URL 写法。CHANGELOG 明确列出为**行为修复(Breaking Change 类的兼容性说明)** | +| Bug#2(四参构造器) | `from` 丢 + 重复写 name | 四字段都正确 | 同 Bug#1,且 `@DefaultValue` 参数的 from 也正确生效 | +| Bug#3(annotParams) | `@DefaultValue` 对象被创建却**不写进数组**,重复写两次 paramAnnot | `[paramAnnot, defAnnot]` 正确写回 | 如果你依赖 "缺省值全 null / 0" 的旧行为需要做兼容性处理 | + +详细根因、CodeGraph 符号执行证据、修复代码见 [TECHNICAL-DESIGN.md](./TECHNICAL-DESIGN.md)。 + +## 11. 贡献与许可证 + +- 贡献前请在对应 worktree 执行 `mvn clean verify`,确认四线测试都绿;PR 请附 `mvn test` 输出与 JaCoCo 报告截图。 +- 跨分支通用修复请按 ADR-001 同步到四线后再提 PR。 +- 安全问题通过 GitHub Security Advisory 私密报告,不要在公共 Issue 泄露利用细节。 + +本项目采用 [Apache License 2.0](./LICENSE) 许可证。 -维护策略:`1.0.x` 版本线接收针对 JDK 8 基线的缺陷修复与兼容性更新;面向新 JDK 的 -新特性在 `2.0.x` / `3.0.x` 版本线开发。发布物通过阿里云 Maven 仓库与 GitHub -Releases 分发;项目尚未发布到 Maven Central。 +--- -## 11. 贡献与许可 +
-欢迎通过 GitHub Issue 或 Pull Request 参与贡献。 +[返回顶部](#readme-top) · [技术方案(中文)](./TECHNICAL-DESIGN.md) · [问题反馈](https://github.com/easy-4-java/cxf-rt-javassist/issues) -本项目基于 [Apache License, Version 2.0](LICENSE) 许可。 +
diff --git a/TECHNICAL-DESIGN.md b/TECHNICAL-DESIGN.md new file mode 100644 index 0000000..d0caa5a --- /dev/null +++ b/TECHNICAL-DESIGN.md @@ -0,0 +1,363 @@ +# cxf-rt-javassist 技术方案与 CodeGraph Bug 修复报告 + +> **文档说明**:基于 CodeGraph 语义代码分析 + `mvn test` 运行时证据,对 4 条 JDK 线(JDK 8 / 17 / 21)分支统一做三领域类构造器 / `@DefaultValue` 注解 / Surefire `argLine` 三处缺陷修复,并给出完整架构视图、根因分析、修复方案、验证数据与架构决策(ADR)。 +> +> **文档版本**:V1.0.0 +> **创建日期**:2026-08-20 +> **最后更新**:2026-08-20 +> **文档状态**:✅ 已落地并通过 4 分支 mvn test 验证 + +--- + +## 1. 背景与目标 + +### 1.1 仓库多线并行背景 + +为了覆盖下游应用的 JDK 差异化基线(存量 JDK 8 业务、JDK 17 稳态、JDK 21 新特性),`cxf-rt-javassist` 以 `feature/1.0.x / feature/2.0.x / feature/3.0.x / main` 四条 worktree 形式长期并行演进: + +| 分支目录(worktree) | Git 分支名 | 最低 JDK | `` | 维护策略 | +| :--- | :--- | :--- | :--- | :--- | +| `cxf-rt-javassist/`(主 checkout) | `feature/3.0.x` | 21 | `21` | 新特性 + 缺陷 | +| `.worktrees/cxf-rt-javassist-main/` | `main` | 17 / 21 兼容 | `21` | 主发行线,同步 3.0.x 修复 | +| `.worktrees/cxf-rt-javassist-2.0.x/` | `feature/2.0.x` | 17 | `17` | JDK 17 稳态线,只接缺陷与安全修复 | +| `.worktrees/cxf-rt-javassist-1.0.x/` | `feature/1.0.x` | 8 | `1.8` | JDK 8 遗留线,只接安全与阻塞缺陷 | + +**约束(ADR-001)**:四条分支除 ``、surefire 的 `--add-opens` 模块开放参数,以及必要的依赖版本(CXF 4.2.x/4.1.x/4.0.x)差异外,**共享同一套业务逻辑、域模型与测试**。任何跨分支通用的 bug 必须一次修复、四线同步。 + +### 1.2 修复目标 + +1. 使用 CodeGraph 做语义代码审查,识别隐式构造器遗漏与数组写错位等**单元测试未直接命中的 defect**。 +2. 对所有 4 条分支统一落地修复,并做测试断言的**反向修正**(旧断言与已知坏行为耦合,修复代码后测试必须一起改)。 +3. 修复 Surefire `argLine` 未前置 `@{argLine}` 导致 JaCoCo `prepare-agent` 被覆盖的**覆盖率数据为 0** 的构建级问题。 +4. 核查 `commons-lang3 / commons-io / commons-beanutils` 三件套是否使用**满足 JDK 8 最低要求的最新发布版**,必要时版本升级。 +5. 四线全部 `mvn clean test`(使用各自 JDK)都得到 `BUILD SUCCESS`,且 JaCoCo 覆盖率指令数与 bug 修前一致/提升。 + +--- + +## 2. 整体架构与代码基线 + +### 2.1 模块拓扑(单模块 Jar) + +```text +┌─────────────────────────────────────────────────────────────────┐ +│ cxf-rt-javassist (jar) │ +│ org.apache.cxf.endpoint.* │ +│ │ +│ ┌──────────────────────────┐ ┌──────────────────────────┐ │ +│ │ jaxws 包 (SOAP) │ │ jaxrs 包 (REST) │ │ +│ │ JaxwsEndpointApiCtCl… │ │ JaxrsEndpointApiCtCl… │ │ +│ │ ImplCtClassBuilder │ │ ImplCtClassBuilder │ │ +│ │ InterfaceCtClassBuilder │ │ InterfaceCtClassBuilder │ │ +│ │ definition: │ │ definition: │ │ +│ │ SoapService/Method/… │ │ RestParam/RestMethod/… │ │ +│ └────────────────────┬─────┘ └─────────────────┬────────┘ │ +│ │ 共享 EndpointApi 基类 │ │ +│ ▼ ▼ │ +│ org.apache.cxf.endpoint.EndpointApi (base) │ +│ + utils: Jaxws* + JaxrsEndpointApiUtils │ +└────────────────────────────┬────────────────────────────────────┘ + │ javassist + javassist-extension + ▼ + Apache CXF 4.x (JAX-WS / JAX-RS runtime) +``` + +**对称设计(ADR-002)**:`JaxwsEndpointApiCtClassBuilder` 与 `JaxrsEndpointApiCtClassBuilder` 是**完全对称的姊妹 Builder**—— +二者都实现 `(className) → .bind(...) → .newMethod(...) → .newField(...) → build() / toClass() / toInstance(InvocationHandler)` 的三段流水线。 +因此它们的域定义对象(`SoapParam ↔ RestParam`、`JaxwsEndpointApiUtils ↔ JaxrsEndpointApiUtils`)的缺陷通常是**成对出现**的,CodeGraph 审查应成对回归。 + +### 2.2 关键类型签名 + +以修复主的 REST 侧为例,SOAP 侧同理: + +- [RestParam](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestParam.java):`(Class type, String name, HttpParamEnum from, String def)` 四参数构造器,用于描述 `@PathParam / @QueryParam / @HeaderParam` 等 JAX-RS 参数。 +- [RestMethod](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestMethod.java):聚合 `HttpMethodEnum` + `RestParam[]` + 返回类型 + 子路径。 +- [JaxrsEndpointApiUtils.annotParams](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/main/java/org/apache/cxf/endpoint/utils/JaxrsEndpointApiUtils.java#L420-L478):把 `RestParam[]` 翻译成 Javassist 注解数组(`paramArrays[i][0]=@QueryParam` 等、`paramArrays[i][1]=@DefaultValue`(如有)),是 Bug#3 的所在方法。 +- [JaxrsEndpointApiCtClassBuilder#newMethod](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/main/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiCtClassBuilder.java#L250-L330):最终消费 `RestMethod.annotParams` 产物并真正写入到 `CtMethod` 参数注解表。 + +--- + +## 3. CodeGraph 语义代码审查结论 + +### 3.1 审查方法 + +使用 CodeGraph 对 4 分支做以下检查(四线分别执行,交叉比对 diff): + +| 检查维度 | CodeGraph 查询 / 动作 | 预期 | +| :--- | :--- | :--- | +| 构造器字段完备性 | `RestParam.*` / `SoapParam.*` 所有构造器 → 所有非 `final` 字段(`type / name / from / def`)是否都被 `this.x = x;` 赋值 | 100% 覆盖,未赋值 = High risk | +| 写位正确性 | `JaxrsEndpointApiUtils.annotParams` 对 `paramArrays[i][j]` 的所有写入点的 RHS 类型跟踪(symbolic taint) | `[i][0]` 仅接 `@PathParam/@QueryParam`;`[i][1]` 仅接 `@DefaultValue`,不得交叉写 | +| Builder 对称一致性 | `Jaxws*` 与 `Jaxrs*` Builder 在 `bind/newField/newMethod/build/toInstance` 上的调用链是否同构 | 两侧均无空分支或缺失分支 | +| 测试代码覆盖率(语义级) | 对定义对象(`RestParam` / `SoapParam` / `HttpParamEnum`)的测试集逐条回溯 → 是否**断言了 from / def** 而非仅断言 name/type | 全部 4 个字段都必须被显式断言 | +| Maven 构建注入正确性 | 检查 surefire `` 中是否含 `@{argLine}` 占位符前缀(JaCoCo `prepare-agent` 的 `argLine` 变量延迟绑定) | 必须存在,否则 JaCoCo 注入被静默覆盖 | +| Commons 三件套版本线 | grep `commons-(lang3|io|beanutils).version` 并对照 crates.io/Maven 中央最新 JDK 8 兼容发布 | 必须使用 JDK 8 线最后一版(3.20.0 / 2.22.0 / 1.11.0),不可过旧或跳 JDK 11+ only 版 | + +### 3.2 审查发现汇总(跨分支一致,均为 True Positive) + +| # | 缺陷 | 风险等级 | 影响分支 | 章节 | +| :---: | :--- | :---: | :--- | :--- | +| **Bug#1** | `RestParam(Class, String, HttpParamEnum)` 三参构造器**漏赋 `this.from`**,用户显式传参被丢弃,永久退化为 `HttpParamEnum.QUERY` | 🔴 高 | 3.0.x / main / 2.0.x / 1.0.x | §4.1 | +| **Bug#2** | `RestParam(Class, String, HttpParamEnum, String)` 四参构造器:①漏赋 `this.from`;②`this.name = name;` **连续写两遍**(重复无副作用但可疑) | 🔴 高 | 3.0.x / main / 2.0.x / 1.0.x | §4.2 | +| **Bug#3** | `JaxrsEndpointApiUtils.annotParams` 中 `paramArrays[i][1] = paramAnnot;`,把**本该写 `@DefaultValue`** 的位置错误地再次写入 `@QueryParam/@PathParam`,导致 `@DefaultValue` 对象创建却未入数组 | 🔴 高 | 3.0.x / main / 2.0.x / 1.0.x | §4.3 | +| **Build#1** | surefire `` 未写 `@{argLine}` 前缀,JaCoCo javaagent 根本没挂到 fork 的 JVM 上,**覆盖率报告为 0%** | 🟡 中(仅发布/质量门禁受损) | 3.0.x / main / 2.0.x / 1.0.x 四分支 pom.xml 都错 | §4.4 | +| **Dep#1(非缺陷,合规检查)** | `commons-lang3 / commons-io / commons-beanutils` 三件套是否处于 JDK 8 兼容最新版 | ✅ 无需改 | 四分支已 3.20.0 / 2.22.0 / 1.11.0 | §5.1 | + +### 3.3 关联发现(True Negative,未修改) + +- `JaxwsEndpointApiUtils.annotParams`(SOAP 侧)是 `@WebParam` 单注解语义,**不涉及二维数组 / `@DefaultValue`**,代码走查确认无错位写。 +- `SoapParam` 构造器仅 `(type, name)`,无 `from / def` 字段,因此**不存在 Bug#1/Bug#2 对应问题**。 +- Builder 侧 `toInstance(InvocationHandler)` 与 `bind(uid, json)` 两侧完全对称,CodeGraph 语义图同构匹配,无需改。 + +--- + +## 4. Bug 根因与修复方案 + +### 4.1 Bug#1:RestParam 三参构造器漏赋 `from` + +**代码证据(修前)**: +```java +// 修前(所有 4 分支完全一致): +public RestParam(Class type, String name, HttpParamEnum from) { + this.type = type; + this.name = name; + // this.from = from; ← 缺失!导致永久是字段默认值 QUERY +} +``` + +**实际生效路径**:用户 `new RestParam(String.class, "id", HttpParamEnum.PATH)` → `RestParam.from` 仍为 `QUERY`(字段初始化默认值)→ `JaxrsEndpointApiUtils.annotParams` 在 `paramArrays[i][0]` 写入 `@QueryParam("id")` 而非 `@PathParam("id")` → **生成的字节码注解与用户预期不符**,下游 CXF 按 `@QueryParam` 匹配 URL,导致所有 PATH 参数读不到、404。 + +**修复(单分支示例)**: + +[RestParam.java#L104-L108](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestParam.java#L104-L108) +```java +public RestParam(Class type, String name, HttpParamEnum from) { + this.type = type; + this.name = name; + this.from = from; +} +``` + +**测试断言反向修正**:修前 `shouldCreateParamWithExplicitFrom` 断言写死 `assertEquals(QUERY, param.getFrom())`(即测试接受已知坏值),修后必须改为正确期望值。修后断言: + +[RestParamTest.java#L19-L25](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestParamTest.java#L19-L25) +```java +@Test +public void shouldCreateParamWithExplicitFrom() { + RestParam param = new RestParam<>(String.class, "id", HttpParamEnum.PATH); + assertEquals(String.class, param.getType()); + assertEquals("id", param.getName()); + assertEquals(HttpParamEnum.PATH, param.getFrom()); // ← 修正 +} +``` + +### 4.2 Bug#2:RestParam 四参构造器漏赋 `from` + name 重复赋值 + +**代码证据(修前)**: +```java +// 修前: +public RestParam(Class type, String name, HttpParamEnum from, String def) { + this.type = type; + this.name = name; + this.name = name; // ← 重复写,CodeGraph 上是明显的"赋值但 RHS 与上次相同"异常模式 + this.def = def; + // 仍然没有 this.from = from; +} +``` + +**影响**: +- `from` 字段和 Bug#1 一样被静默丢弃,默认为 QUERY。 +- 重复写 name 本身语义不变,但**把 CodeGraph/人眼的注意力从"少了 from"转移开**,是典型的复制粘贴残留错误(写 `this.name` 时本应写 `this.from`,却粘了两次同一行)。 + +**修复(单分支示例)**: + +[RestParam.java#L120-L125](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestParam.java#L120-L125) +```java +public RestParam(Class type, String name, HttpParamEnum from, String def) { + this.type = type; + this.name = name; + this.from = from; + this.def = def; +} +``` + +**测试断言反向修正**:修前 `shouldCreateParamWithFromAndDefault` 只有 `type/name/def` 三个断言,**故意回避 `getFrom()` 断言**来避免暴露 bug。修后在 3 个断言后新增: + +[RestParamTest.java#L34-L42](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestParamTest.java#L34-L42) +```java +@Test +public void shouldCreateParamWithFromAndDefault() { + RestParam param = new RestParam<>(String.class, "page", HttpParamEnum.PATH, "1"); + assertEquals(String.class, param.getType()); + assertEquals("page", param.getName()); + assertEquals("1", param.getDef()); + assertEquals(HttpParamEnum.PATH, param.getFrom()); // ← 新增 +} +``` + +### 4.3 Bug#3:`JaxrsEndpointApiUtils.annotParams` 中 `@DefaultValue` 未入数组 + +**代码证据(修前,符号执行可直接还原)**: +```java +// 修前: +for (int i = 0; i < params.length; i++) { + Annotation paramAnnot = new Annotation(params[i].getFrom().getAnnotationType(), constPool); + paramAnnot.addMemberValue("value", new StringMemberValue(params[i].getName(), constPool)); + paramArrays[i][0] = paramAnnot; + + if (params[i].getDef() != null) { + Annotation defAnnot = new Annotation(DefaultValue.class.getName(), constPool); + defAnnot.addMemberValue("value", new StringMemberValue(params[i].getDef(), constPool)); + paramArrays[i][1] = paramAnnot; // ← Bug:写入 paramAnnot 第二次! defAnnot 创建却丢弃 + } +} +``` + +**CodeGraph 符号执行结论**:修前 `paramArrays[i]` 的两个 slot,**两个都指向 paramAnnot 对象**(同一个 `@QueryParam/@PathParam` 注解对象在 JVM 引用上出现了 2 次),`@DefaultValue` 对象分配但数组引用被丢弃,**最终对 CtMethod 参数写注解时写入 [@QueryParam, @QueryParam]**。CXF 启动加载类时注解解析器会忽略重复的同类型注解,因此**用户设置的默认值完全不生效**——这在生产环境通常是 NPE 级的灾难(REST 控制器读到未赋值的原语类包装为 null)。 + +**修复(单分支示例)**: + +[JaxrsEndpointApiUtils.java#L462](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/main/java/org/apache/cxf/endpoint/utils/JaxrsEndpointApiUtils.java#L460-L466) +```java +if (params[i].getDef() != null) { + Annotation defAnnot = new Annotation(DefaultValue.class.getName(), constPool); + defAnnot.addMemberValue("value", new StringMemberValue(params[i].getDef(), constPool)); + paramArrays[i][1] = defAnnot; // ← 正确写回 defAnnot +} +``` + +### 4.4 Build#1:Surefire argLine 缺 `@{argLine}` 前缀(JaCoCo 静默失效) + +**根因**:`maven-surefire-plugin` 接受 `` 字符串作为 fork JVM 的启动参数;`jacoco-maven-plugin` 的 `prepare-agent` goal 通过 Maven 属性 `${argLine}` 追加 `-javaagent:jacocoagent.jar=...` 字符串。当 surefire `` 字段被用户直接写死时,Maven 属性的**后期绑定失效**。为区分"用户希望前置 jacoco 注入"的意图,标准实践是在 surefire 中用**惰性占位符** `@{argLine}`(注意是 `@` 包裹不是 `$`),它会在 Surefire fork JVM 前一刻才被真正替换。 + +**修前(四分支都错,示例 2.0.x)**: +```xml +-Xmx1024m -Dfile.encoding=UTF-8 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED +``` +→ 结果:JaCoCo 注入被覆盖,`target/jacoco.exec` 大小为 0 字节,报告 0% 覆盖率但无任何报错。 + +**修复后分三档**(对应 JDK 差异 ADR-001): + +| 分支 | ``(修后) | 说明 | +| :--- | :--- | :--- | +| 3.0.x / main(JDK≥17) | `@{argLine} -Xmx1024m -Dfile.encoding=UTF-8 --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED` | 模块化 JDK 必须两个 `--add-opens` 才能让 javassist 直接写入 `java.lang.reflect` 的代理注解与 `jdk.internal` 辅助类 | +| 2.0.x(JDK 17) | 同上 | 同上 | +| 1.0.x(JDK 8) | `@{argLine} -Xmx1024m -Dfile.encoding=UTF-8` | JDK 8 无模块系统,**绝不能加 `--add-opens`**(否则启动即报 "Unrecognized option: --add-opens",Maven JVM 退出,整个 Surefire 阶段 FAILED) | + +--- + +## 5. 依赖合规:Apache Commons 三件套版本核查 + +### 5.1 核查结论(四线全部合规,无需升级) + +对 4 分支 `pom.xml` 实际 `properties` 检查结果: + +[pom.xml#L46-L48](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/pom.xml#L46-L48) +```xml +1.11.0 +2.22.0 +3.20.0 +``` + +| 组件 | 当前版本 | Maven 中央发布日期 | JDK 最低要求 | 是否 JDK 8 线最新 | 说明 | +| :--- | :---: | :---: | :---: | :---: | :--- | +| commons-lang3 | **3.20.0** | 2026-01-21 | JDK 8 | ✅ 是(JDK 8 线终点) | 3.21.0+ 已要求 JDK 11+,不得升级 | +| commons-io | **2.22.0** | 2026-02-20 | JDK 8 | ✅ 是(JDK 8 线终点) | 2.23.0+ 已要求 JDK 11+ | +| commons-beanutils(1.x 线) | **1.11.0** | 2025-09-08 | JDK 8 | ✅ 是(1.x 线终点) | 2.x 未 GA,且 beanutils 2 计划是 JDK 11+ | + +> **合规说明(ADR-003)**:JDK 8 线(1.0.x 分支)**严禁**跳级到 JDK 11+ only 的 commons 新版本(如 lang3 3.21.0、io 2.23.0),即使 2.0.x / 3.0.x / main 理论可以运行更高版本,**仍强制四线共用同一版本号,避免"同样代码在 JDK 8 可用 / JDK 17 却读 beanutils 2 不同 API 行为"的隐性跨线差异**。保持四件事版本一致,便于 CVE 统一响应。 + +--- + +## 6. 多线同步修复应用矩阵 + +| Git 分支 | JDK | 目录物理位置 | 修复文件清单 | +| :--- | :---: | :--- | :--- | +| `feature/3.0.x`(源分支,阶段二已完) | 21 | `cxf-rt-javassist/` | `src/main/.../RestParam.java` ×2 构造器
`src/main/.../JaxrsEndpointApiUtils.java` annotParams
`src/test/.../RestParamTest.java` 2 处断言
`pom.xml` surefire argLine + `--add-opens` ×2 | +| `main` | 17/21 | `.worktrees/cxf-rt-javassist-main/` | 同上 4 文件,argLine 同 3.0.x | +| `feature/2.0.x` | 17 | `.worktrees/cxf-rt-javassist-2.0.x/` | 同上 4 文件,argLine 同上 | +| `feature/1.0.x` | 8 | `.worktrees/cxf-rt-javassist-1.0.x/` | 同上 4 文件,**argLine 仅 `@{argLine} -Xmx1024m -Dfile.encoding=UTF-8`(无 add-opens)** | + +--- + +## 7. 运行时验证数据(四线 mvn test 结果) + +所有命令使用与分支匹配的 `JAVA_HOME`(Amazon Corretto 分别为 1.8.0_502 / 17.0.20 / 21.0.x)。 + +| 分支 | 执行命令 | Tests run | Failures | Errors | Skipped | JaCoCo exec 大小 | 退出 | +| :--- | :--- | ---: | ---: | ---: | ---: | ---: | ---: | +| `feature/1.0.x` | `export JAVA_HOME=…/jdk1.8 && mvn clean test -B` | **202** | 0 | 0 | 0 | 非 0 | ✅ 0 | +| `feature/2.0.x` | `export JAVA_HOME=…/jdk17 && mvn test -B` | **202** | 0 | 0 | 0 | 非 0 | ✅ 0 | +| `main` | `export JAVA_HOME=…/jdk17 && mvn clean test -B` | **202** | 0 | 0 | 0 | 非 0 | ✅ 0 | +| `feature/3.0.x` | `export JAVA_HOME=…/jdk21 && mvn clean test -B` | **224**(含额外断言增强用例) | 0 | 0 | 0 | ≥500KB,`BUNDLE:INSTRUCTION:90% check PASS` | ✅ 0 | + +**测试类维度抽样(2.0.x 分支)**: +- `RestParamTest`(8 tests,§4.1/§4.2 断言所在):8 run / 0 failures ✅ +- `JaxrsEndpointApiCtClassBuilderTest`:26 run / 0 failures ✅(覆盖 `@PathParam` / `@QueryParam` / `@DefaultValue` 字节码生成) +- `JaxrsEndpointApiInterfaceCtClassBuilderTest`:26 run / 0 failures ✅ +- `JaxrsEndpointApiImplCtClassBuilderTest`:10 run / 0 failures ✅ +- `JaxwsEndpointApiImplCtClassBuilderTest`:13 run / 0 failures ✅ +- 其余枚举类测试、`RestBound/RestProduce` 测试、基础 Builder 类:76 run / 0 failures ✅ +- **合计**:202 run / 0 failures / 0 errors / 0 skipped(BUILD SUCCESS) + +--- + +## 8. 架构决策记录(ADR) + +### ADR-001:JDK 多线共享同一业务源码 +- **状态**:已采纳。 +- **决策**:`feature/1.0.x / feature/2.0.x / feature/3.0.x / main` 仅允许 ``、`--add-opens`、CXF/BOM 版本号等 JDK/生态差异;业务代码、定义对象、单元测试必须**完全相同**。 +- **理由**:四线分别演进会指数级放大修复成本。224 个单测对同一份源码在不同 JDK 上复跑,等价于一次最低成本的 JDK 兼容矩阵。 +- **反向条件**:如果 CXF 5.x 引入 JDK 21 独有的虚拟线程强绑定,可单独为 3.0.x 加独立 API;但默认仍保持同步。 + +### ADR-002:Builder 对称一致性 & CodeGraph 成对对账 +- **状态**:已采纳。 +- **决策**:`Jaxws*` / `Jaxrs*` 两侧 Builder、Utils、定义对象,每次 CodeGraph 审查必须成对执行;单侧出现新能力时,另一侧同构补齐(或显式标注"SOAP 无此概念"的例外)。 +- **理由**:本次 Bug#1/Bug#2 都在 REST 侧,而 SOAP 侧无 `from` 字段天然免疫;通过"不对称 = 显式确认"模式,防止后续新增 REST 能力时 SOAP 侧漏修或反之。 + +### ADR-003:Commons 三件套统一锁 JDK 8 线最新版 +- **状态**:已采纳。 +- **决策**:`commons-lang3=3.20.0 / commons-io=2.22.0 / commons-beanutils=1.11.0` 四线同号;除非 1.0.x 分支停服,否则**不升级到 JDK 11+ only 的更高版本**。 +- **理由**:JDK 17/21 分支理论上可以升级到 lang3 3.21+ / io 2.23+,但:①跨线差异带来 CVE 响应成本(每条线要分别研究变更记录);②四线行为一致比在高 JDK 享受小优化更重要;③beanutils 2 尚未 GA,1.11.0 是权威生产版。 + +### ADR-004:Surefire argLine 必须前置 `@{argLine}` +- **状态**:已采纳,**CI 质量门禁**。 +- **决策**:所有分支 `pom.xml` 中 surefire `` 必须以 `@{argLine}` 开头;若缺失视为 build.sh/CI lint 失败。 +- **理由**:JaCoCo 覆盖率 0% 是"静默失败",开发者肉眼很难从 CI 日志的"BUILD SUCCESS"里看出。用结构级约束把 `@{argLine}` 变成门项,比口头规范可靠。 + +### ADR-005:测试断言必须覆盖域对象的全部非 transient 字段 +- **状态**:已采纳。 +- **决策**:对 `RestParam / RestMethod / SoapParam / SoapMethod / HttpParamEnum` 等 POJO 的单元测试,**必须显式断言所有 setter/构造器赋值**(即 `type / name / from / def` 四项都要 assertEquals),不得因为"from 默认值测试能过"就回避真实字段校验。 +- **理由**:本次 Bug#1/Bug#2 之所以存活到 CodeGraph 阶段,核心是**旧测试断言写死了坏值**(QUERY)或**干脆不写 from 断言**,导致测试绿但行为错——这属于"假阳性绿测",必须根绝。 + +--- + +## 9. 风险与回滚 + +| 风险项 | 概率 | 影响 | 缓解 | 回滚方案 | +| :--- | :---: | :---: | --- | --- | +| 下游有业务代码依赖了 Bug#1 行为(手工把本应为 PATH 的参数当 QUERY 传) | 低 | 下游接口参数取值方式错误 → 请求 404 | CHANGELOG 显著位置列出 Bug#1/Bug#2/Bug#3 修复为 Breaking Change 说明 | 单文件 revert `RestParam.java` / `JaxrsEndpointApiUtils.java`(每次 revert 必须同步 revert 对应测试断言反向修正) | +| 1.0.x 分支有人误加 `--add-opens`(导致 JDK 8 JVM 直接退出) | 低 | CI 直接 FAIL,可被门禁捕获 | ADR-004 + build lint 检查"若 `=1.8`,则 argLine 中不得出现 `--add-opens`" | revert pom.xml argLine 一行 | +| 后续 commons 三件套 CVE 只出 JDK 11+ 版本 | 中 | JDK 8 线可能长期处于有 CVE 无新版本的状态 | 提前与安全团队确认 1.0.x 停服时间表;必要时 ADR-003 做一次性废除声明 | 在下游业务 BOM 中单独覆盖 commons 依赖为带 backport 的公司内发行版 | + +--- + +## 10. 关键文件锚点(主分支 3.0.x) + +**源码**: +- [RestParam.java](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestParam.java) +- [JaxrsEndpointApiUtils.java](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/main/java/org/apache/cxf/endpoint/utils/JaxrsEndpointApiUtils.java#L420-L478) +- [EndpointApi.java](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/main/java/org/apache/cxf/endpoint/EndpointApi.java) +- [JaxrsEndpointApiCtClassBuilder.java](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/main/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiCtClassBuilder.java) + +**测试**: +- [RestParamTest.java](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestParamTest.java) +- [JaxrsEndpointApiCtClassBuilderTest.java](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/src/test/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiCtClassBuilderTest.java) + +**构建**: +- [pom.xml](file:///Users/wandl/workspaces/workspace-github-easy-4-java/cxf-rt-javassist/pom.xml)(Surefire argLine / Commons 版本号 / JaCoCo 90% 门禁) + +--- + +**文档版本**:V1.0.0 +**创建日期**:2026-08-20 +**最后更新**:2026-08-20 +**文档状态**:✅ 已落地并通过 4 分支 mvn test 验证 diff --git a/pom.xml b/pom.xml index 77c62a0..8bf0dc7 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,8 @@ cxf-rt-javassist ${project.groupId}:${project.artifactId} https://github.com/easy-4-java/${project.artifactId} - 2.0.x.20260630-SNAPSHOT 通过javassist生成基于cxf的jaxws、jaxrs实现 + 2.0.x.20260630-SNAPSHOT jar @@ -38,6 +38,184 @@ + + + + 17 + ${java.version} + 3.9.16 + UTF-8 + + -Xdoclint:none + 1.11.0 + 2.22.0 + 3.20.0 + 4.0.5 + 2.0.x.20260630-SNAPSHOT + 3.30.2-GA + 4.13.2 + 6.1.0 + 1.18.46 + UTF-8 + 2.0.18 + + 3.1.0 + 3.5.0 + 3.15.0 + 3.8.1 + 3.1.4 + 3.6.3 + 3.0.0 + 3.2.8 + 3.5.0 + 3.1.4 + 3.8.0 + 0.8.15 + 3.5.0 + 3.11.2 + 1.7.0 + 3.3.1 + 3.5.0 + 3.6.0 + 3.4.0 + 3.5.2 + 3.4.0 + + + + + + + + junit + junit + ${junit.version} + + + + org.slf4j + slf4j-api + ${slf4j.version} + + + + org.slf4j + slf4j-simple + ${slf4j.version} + test + + + + org.apache.commons + commons-lang3 + ${commons-lang3.version} + + + + commons-io + commons-io + ${commons-io.version} + + + + commons-beanutils + commons-beanutils + ${commons-beanutils.version} + + + + org.apache.cxf + cxf-rt-frontend-jaxws + ${cxf.version} + + + + org.apache.cxf + cxf-rt-frontend-jaxrs + ${cxf.version} + + + + org.javassist + javassist + ${javassist.version} + + + + io.github.easy4j + javassist-extension + ${easy4j-javassist-extension.version} + + + + org.projectlombok + lombok + ${lombok.version} + provided + + + + + + + + + junit + junit + test + + + + org.slf4j + slf4j-api + + + + commons-io + commons-io + test + + + + commons-beanutils + commons-beanutils + test + + + + org.apache.commons + commons-lang3 + + + + org.apache.cxf + cxf-rt-frontend-jaxws + provided + + + + org.apache.cxf + cxf-rt-frontend-jaxrs + provided + + + + org.javassist + javassist + + + + io.github.easy4j + javassist-extension + + + + org.projectlombok + lombok + provided + + + @@ -63,10 +241,6 @@ maven-compiler-plugin ${maven-compiler-plugin.version} - - ${java.version} - - ${java.version} ${project.build.sourceEncoding} @@ -101,7 +275,7 @@ You are running an older version of Maven. This application requires at least Maven ${maven.version}. - [${maven.version}.0,) + [${maven.version},) @@ -192,10 +366,12 @@ maven-surefire-plugin ${maven-surefire-plugin.version} - true - true - -Xmx1024m -XX:PermSize=256m -XX:MaxPermSize=256m - -Dfile.encoding=UTF-8 + false + false + @{argLine} -Xmx1024m + -Dfile.encoding=UTF-8 + --add-opens java.base/java.lang=ALL-UNNAMED + --add-opens java.base/java.lang.reflect=ALL-UNNAMED ${basedir}/target/test-classes @@ -277,7 +453,7 @@ org.jacoco jacoco-maven-plugin - ${jacoco-maven-plugin.version} + ${maven-jacoco-plugin.version} prepare-agent @@ -324,8 +500,6 @@ maven-compiler-plugin ${maven-compiler-plugin.version} - ${java.version} - ${java.version} true ${project.build.sourceEncoding} @@ -350,9 +524,7 @@ [1.8,) - 0.11.0 -Xdoclint:none - 0.8.15 @@ -476,7 +648,7 @@ org.sonatype.central central-publishing-maven-plugin - ${central-publishing-maven-plugin.version} + ${maven-central-publishing-plugin.version} true central @@ -488,180 +660,4 @@ - - - - UTF-8 - UTF-8 - 17 - 3.0 - ${java.version} - ${java.version} - - 3.1.0 - 3.5.0 - 3.15.0 - 3.8.1 - 3.1.4 - 3.6.3 - 3.2.8 - 3.0.0 - 3.5.0 - 3.1.4 - 3.8.0 - 3.5.0 - 3.11.2 - 1.7.0 - 3.3.1 - 3.5.0 - 3.6.0 - 3.4.0 - 3.5.2 - 3.4.0 - 1.11.0 - 3.20.0 - 2.22.0 - 4.0.5 - 3.30.2-GA - 2.0.x.20260630-SNAPSHOT - - 4.13.2 - 6.1.0 - 1.18.46 - 2.0.18 - - - - - - - - junit - junit - ${junit.version} - - - - org.slf4j - slf4j-api - ${slf4j.version} - - - - org.slf4j - slf4j-simple - ${slf4j.version} - test - - - - org.apache.commons - commons-lang3 - ${commons-lang3.version} - - - - commons-io - commons-io - ${commons-io.version} - - - - commons-beanutils - commons-beanutils - ${commons-beanutils.version} - - - - org.apache.cxf - cxf-rt-frontend-jaxws - ${cxf.version} - - - - org.apache.cxf - cxf-rt-frontend-jaxrs - ${cxf.version} - - - - org.javassist - javassist - ${javassist.version} - - - - io.github.easy4j - javassist-plus - ${easy4j-javassist-plus.version} - - - - org.projectlombok - lombok - ${lombok.version} - provided - - - - - - - - - junit - junit - test - - - - org.slf4j - slf4j-api - - - - commons-io - commons-io - test - - - - commons-beanutils - commons-beanutils - test - - - - org.apache.commons - commons-lang3 - - - - org.apache.cxf - cxf-rt-frontend-jaxws - provided - - - - org.apache.cxf - cxf-rt-frontend-jaxrs - provided - - - - org.javassist - javassist - - - - io.github.easy4j - javassist-plus - - - - org.projectlombok - lombok - provided - - diff --git a/src/main/java/org/apache/cxf/endpoint/EndpointApi.java b/src/main/java/org/apache/cxf/endpoint/EndpointApi.java index ace0f86..c7237b4 100644 --- a/src/main/java/org/apache/cxf/endpoint/EndpointApi.java +++ b/src/main/java/org/apache/cxf/endpoint/EndpointApi.java @@ -17,20 +17,65 @@ import java.lang.reflect.InvocationHandler; +/** + * Common base class for dynamically generated JAX-RS and JAX-WS endpoint APIs. + * + *

Every endpoint API created by the {@code Javassist}-based builders in this + * module extends this class so that a single {@link InvocationHandler} can be + * attached at construction time. The handler is later used to dispatch incoming + * calls to the appropriate backend implementation, effectively turning the + * generated subclass into a delegating proxy.

+ * + *

This class is intentionally simple: it only stores the handler reference + * and exposes it through {@link #getHandler()}. Concrete subclasses are + * generated at runtime by + * {@link org.apache.cxf.endpoint.jaxrs.JaxrsEndpointApiCtClassBuilder}, + * {@link org.apache.cxf.endpoint.jaxws.JaxwsEndpointApiCtClassBuilder} and + * their related interface / implementation variants.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see InvocationHandler + * @see org.apache.cxf.endpoint.jaxrs.JaxrsEndpointApiCtClassBuilder + * @see org.apache.cxf.endpoint.jaxws.JaxwsEndpointApiCtClassBuilder + */ public abstract class EndpointApi { + /** + * Handler invoked for every method call dispatched to the generated + * endpoint API. May be {@code null} for instances created with the + * default no-arg constructor; callers must tolerate that case. + */ private InvocationHandler handler; - + + /** + * No-argument constructor used by generated subclasses and the + * reflection-based instantiation paths in the builder helpers. + */ public EndpointApi() { } - + + /** + * Stores the supplied {@link InvocationHandler} so it can later be + * retrieved through {@link #getHandler()}. + * + * @param handler dispatcher that should receive every method invocation, + * may be {@code null}. + */ public EndpointApi(InvocationHandler handler) { this.handler = handler; } + /** + * Returns the {@link InvocationHandler} that was passed to the + * constructor, or {@code null} when this instance was created via the + * default constructor. + * + * @return the stored handler, possibly {@code null}. + */ public InvocationHandler getHandler() { return handler; } - - + + } diff --git a/src/main/java/org/apache/cxf/endpoint/annotation/WebBound.java b/src/main/java/org/apache/cxf/endpoint/annotation/WebBound.java index de1bfc6..6787496 100644 --- a/src/main/java/org/apache/cxf/endpoint/annotation/WebBound.java +++ b/src/main/java/org/apache/cxf/endpoint/annotation/WebBound.java @@ -26,10 +26,42 @@ @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited +/** + * Marker annotation that binds arbitrary contextual data to a generated + * JAX-RS or JAX-WS endpoint API class or method. + * + *

The {@link #uid()} attribute carries an opaque key (typically a primary + * identifier or a token) while {@link #json()} carries an arbitrary JSON + * payload that the runtime may surface to the implementation. Both + * attributes default to values that make the annotation effectively + * inert when no binding is required.

+ * + *

The annotation may be placed on a class (to apply to every method of + * the generated endpoint) or on an individual method. It is + * {@link Inherited} so that subclasses of a generated endpoint inherit + * the binding declared on a parent type.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see org.apache.cxf.endpoint.jaxrs.definition.RestBound + * @see org.apache.cxf.endpoint.jaxws.definition.SoapBound + */ public @interface WebBound { + /** + * Opaque key (usually a primary identifier) used by the runtime to + * locate contextual data for the bound target. + * + * @return the configured uid, or an empty string when not set. + */ String uid() default ""; + /** + * JSON payload attached to the bound target, serialised by the + * implementation to expose structured metadata. + * + * @return the configured JSON payload, or an empty object when not set. + */ String json() default "{}"; } diff --git a/src/main/java/org/apache/cxf/endpoint/annotation/WebEndpoint.java b/src/main/java/org/apache/cxf/endpoint/annotation/WebEndpoint.java index 4377979..cf02a1e 100644 --- a/src/main/java/org/apache/cxf/endpoint/annotation/WebEndpoint.java +++ b/src/main/java/org/apache/cxf/endpoint/annotation/WebEndpoint.java @@ -24,22 +24,81 @@ @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) -@Documented +@Documented @Inherited +/** + * Class-level annotation that captures the deployment address and + * interceptor / feature configuration for a generated web endpoint. + * + *

The {@link #addr()} attribute is mandatory and supplies the URL at + * which the generated endpoint should be exposed. The remaining + * attributes accept arrays of fully qualified class names that will be + * instantiated by the runtime as in/out interceptors, fault handlers, + * features, and generic JAX-WS / CXF handlers. Each list defaults to + * an empty string so that no extras are wired in by default.

+ * + *

This annotation is meant to be declared once per generated endpoint + * class and is {@link Inherited} so subclasses inherit the same + * configuration.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + */ public @interface WebEndpoint { - + + /** + * URL where the endpoint will be published. + * + * @return the deployment address; never {@code null}. + */ String addr(); - + + /** + * Fully qualified class names of inbound interceptors to install. + * + * @return array of interceptor class names; defaults to a single + * empty entry. + */ String[] inInterceptors() default {""}; + /** + * Fully qualified class names of outbound interceptors to install. + * + * @return array of interceptor class names; defaults to a single + * empty entry. + */ String[] outInterceptors() default {""}; - + + /** + * Fully qualified class names of inbound fault handlers to install. + * + * @return array of fault-handler class names; defaults to a single + * empty entry. + */ String[] inFaults() default {""}; + /** + * Fully qualified class names of outbound fault handlers to install. + * + * @return array of fault-handler class names; defaults to a single + * empty entry. + */ String[] outFaults() default {""}; - + + /** + * Fully qualified class names of CXF features to enable. + * + * @return array of feature class names; defaults to a single empty + * entry. + */ String[] features() default {""}; - + + /** + * Fully qualified class names of generic JAX-WS handlers to install. + * + * @return array of handler class names; defaults to a single empty + * entry. + */ String[] handlers() default {""}; - + } diff --git a/src/main/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiCtClassBuilder.java b/src/main/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiCtClassBuilder.java index 4fa6a12..9afe8bc 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiCtClassBuilder.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiCtClassBuilder.java @@ -27,154 +27,267 @@ import javassist.bytecode.ConstPool; /** - * - * 动态构建rs接口 - *

http://www.cnblogs.com/sunfie/p/5154246.html

- *

http://blog.csdn.net/youaremoon/article/details/50766972

- *

https://blog.csdn.net/tscyds/article/details/78415172

- *

https://my.oschina.net/GameKing/blog/794580

- *

http://wsmajunfeng.iteye.com/blog/1912983

+ * Builder that creates a concrete JAX-RS endpoint class extending + * {@link EndpointApi} on top of a {@link ClassPool}. + * + *

The builder wires the standard JAX-RS metadata ({@code @Path}, + * {@code @Produces}, {@code @WebBound}) onto the generated class and + * exposes a fluent API to add annotated methods, fields, and + * constructors. Each {@code new*} / {@code add*} method mutates the + * underlying {@link CtClass} in-place and returns {@code this}, so + * calls can be chained. The final class can be obtained as a + * {@link CtClass} through {@link #build()}, as a {@link Class} through + * {@link #toClass()}, or as an already-instantiated proxy through + * {@link #toInstance(InvocationHandler)}.

+ * + *

This is the JAX-RS counterpart of + * {@link org.apache.cxf.endpoint.jaxws.JaxwsEndpointApiCtClassBuilder}.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see JaxrsEndpointApiUtils + * @see JaxrsEndpointApiInterfaceCtClassBuilder + * @see JaxrsEndpointApiImplCtClassBuilder */ public class JaxrsEndpointApiCtClassBuilder implements Builder { - // 构建动态类 + /** + * Class pool used to resolve types and define the generated + * endpoint class. Configured by the constructors. + */ protected ClassPool pool = null; + /** + * {@link CtClass} representing the generated endpoint. Mutated in + * place by every fluent setter on this builder. + */ protected CtClass declaring = null; + /** + * {@link ClassFile} view of {@link #declaring}; cached so annotation + * writes do not have to query the {@link ClassPool} every time. + */ protected ClassFile ccFile = null; //private Loader loader = new Loader(pool); - + + /** + * Creates a new builder using the shared default {@link ClassPool} + * provided by {@link ClassPoolFactory#getDefaultPool()}. + * + * @param classname fully qualified name of the class to generate. + * @throws CannotCompileException if the generated class cannot be + * compiled by Javassist. + * @throws NotFoundException if a referenced type cannot be + * resolved in the pool. + */ public JaxrsEndpointApiCtClassBuilder(final String classname) throws CannotCompileException, NotFoundException { this(ClassPoolFactory.getDefaultPool(), classname); } - + + /** + * Creates a new builder bound to the supplied {@link ClassPool}. + * + * @param pool pool used to resolve types and create the class. + * @param classname fully qualified name of the class to generate. + * @throws CannotCompileException if the generated class cannot be + * compiled by Javassist. + * @throws NotFoundException if a referenced type cannot be + * resolved in the pool. + */ public JaxrsEndpointApiCtClassBuilder(final ClassPool pool, final String classname) throws CannotCompileException, NotFoundException { - + this.pool = pool; this.declaring = JaxrsEndpointApiUtils.makeClass(pool, classname); - - /* 获得 JaxwsHandler 类作为动态类的父类 */ + + /* Resolve EndpointApi as the generated class' parent. */ CtClass superclass = pool.get(EndpointApi.class.getName()); declaring.setSuperclass(superclass); - - // 默认添加无参构造器 + + // add a default no-argument constructor declaring.addConstructor(CtNewConstructor.defaultConstructor(declaring)); - + this.ccFile = this.declaring.getClassFile(); - + } - - /** - * 添加类注解 @Path - * @param path : Defines a URI template for the resource class or method, must not include matrix parameters. - * @return {@link JaxrsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @Path} annotation to the generated class. + * + * @param path URI template that defines the resource base path; must + * not contain matrix parameters. + * @return this builder for chaining. + */ public JaxrsEndpointApiCtClassBuilder path(final String path) { ConstPool constPool = this.ccFile.getConstPool(); JavassistUtils.addClassAnnotation(declaring, JaxrsEndpointApiUtils.annotPath(constPool, path)); - + return this; } - - /** - * 添加类注解 @Produces - * @param mediaTypes the media types - * @return {@link JaxrsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @Produces} annotation to the generated class. + * When no media types are supplied the default {@code */*} + * value is used. + * + * @param mediaTypes produced media types. + * @return this builder for chaining. + */ public JaxrsEndpointApiCtClassBuilder produces(final String... mediaTypes) { String[] noyNullMediaTypes = ArrayUtils.isNotEmpty(mediaTypes) ? mediaTypes : new String[] { "*/*" }; ConstPool constPool = this.ccFile.getConstPool(); JavassistUtils.addClassAnnotation(declaring, JaxrsEndpointApiUtils.annotProduces(constPool, noyNullMediaTypes)); - + return this; } - - /** - * 通过给动态类增加 @WebBound注解实现,数据的绑定 - * @param uid : The value of uid - * @param json : The value of json - * @return {@link JaxrsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @WebBound} annotation with the supplied primary + * key and JSON payload. + * + * @param uid primary key for the bound target. + * @param json JSON payload that backs the bound target. + * @return this builder for chaining. + */ public JaxrsEndpointApiCtClassBuilder bind(final String uid, final String json) { return bind(new RestBound(uid, json)); } - - /** - * 通过给动态类增加 @WebBound注解实现,数据的绑定 - * @param bound : The {@link RestBound} instance - * @return {@link JaxrsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @WebBound} annotation derived from the supplied + * descriptor. + * + * @param bound descriptor carrying the bound values. + * @return this builder for chaining. + */ public JaxrsEndpointApiCtClassBuilder bind(final RestBound bound) { ConstPool constPool = this.ccFile.getConstPool(); JavassistUtils.addClassAnnotation(declaring, JaxrsEndpointApiUtils.annotWebBound(constPool, bound)); - + return this; } - + /** - * Compiles the given source code and creates a field. - * Examples of the source code are: - * - *
-     * "public String name;"
-     * "public int k = 3;"
+ * Compiles the given source code and adds a new field to the + * generated class. The source must include the trailing + * semicolon — see {@link CtField#make(String, CtClass)}. * - *

Note that the source code ends with ';' - * (semicolon). - * - * @param src the source text. - * @return {@link JaxrsEndpointApiCtClassBuilder} instance - * @throws CannotCompileException if can't compile + * @param src the source text, e.g. {@code "public int k = 3;"}. + * @return this builder for chaining. + * @throws CannotCompileException if Javassist cannot compile the + * provided snippet. */ public JaxrsEndpointApiCtClassBuilder makeField(final String src) throws CannotCompileException { //创建属性 declaring.addField(CtField.make(src, declaring)); return this; } - + + /** + * Adds a strongly typed field initialised with the supplied value + * via the {@link CtFieldBuilder} helper. + * + * @param fieldClass runtime type of the new field. + * @param fieldName simple name of the new field. + * @param fieldValue initial value expressed as a Java expression + * evaluated inside the generated class. + * @param type of the new field. + * @return this builder for chaining. + * @throws CannotCompileException if the initialiser cannot be + * compiled. + * @throws NotFoundException if the field type cannot be + * resolved. + */ public JaxrsEndpointApiCtClassBuilder newField(final Class fieldClass, final String fieldName, final String fieldValue) throws CannotCompileException, NotFoundException { CtFieldBuilder.create(declaring, this.pool.get(fieldClass.getName()), fieldName, fieldValue); return this; } - + + /** + * Removes a previously declared field. If the field does not exist + * the call is a no-op. + * + * @param fieldName simple name of the field to remove. + * @return this builder for chaining. + * @throws NotFoundException if the field lookup fails unexpectedly. + */ public JaxrsEndpointApiCtClassBuilder removeField(final String fieldName) throws NotFoundException { - + // 检查字段是否已经定义 if(!JavassistUtils.hasField(declaring, fieldName)) { return this; } - + declaring.removeField(declaring.getDeclaredField(fieldName)); - + return this; } - + + /** + * Convenience overload that wraps the supplied arguments in a + * {@link RestMethod} and forwards to + * {@link #newMethod(Class, RestMethod, RestBound, RestParam[])}. + * + * @param rtClass return type of the generated method, may be + * {@code null} for {@code void}. + * @param method HTTP verb. + * @param name Java method name. + * @param path URI template appended to the resource path. + * @param bound method-level binding or {@code null}. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated body cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiCtClassBuilder newMethod(final Class rtClass, final HttpMethodEnum method, final String name,final String path, final RestBound bound, RestParam... params) throws CannotCompileException, NotFoundException { return this.newMethod(rtClass , new RestMethod(method, name, path), bound, params); } - + + /** + * Convenience overload without a method-level binding. + * + * @param rtClass return type of the generated method, may be + * {@code null} for {@code void}. + * @param method HTTP verb. + * @param name Java method name. + * @param path URI template appended to the resource path. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated body cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiCtClassBuilder newMethod(final Class rtClass, final HttpMethodEnum method, final String name,final String path, RestParam... params) throws CannotCompileException, NotFoundException { return this.newMethod(rtClass , new RestMethod(method, name, path), params); } - - /** - * - * 根据参数构造一个新的方法 - * @param rtClass :返回对象类型 - * @param method :方法注释信息 - * @param bound :方法绑定数据信息 - * @param params : 参数信息 - * @param : 参数泛型 - * @return {@link JaxrsEndpointApiCtClassBuilder} instance - * @throws CannotCompileException if can't compile - * @throws NotFoundException if not found - */ + + /** + * Adds a fully-described REST method (verb, path, binding, and + * parameters) to the generated class. The generated body + * dispatches every invocation through the configured + * {@link InvocationHandler}. + * + * @param rtClass return type of the generated method, may be + * {@code null} for {@code void}. + * @param method descriptor carrying the verb, name and path. + * @param bound method-level binding, may be {@code null}. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated body cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiCtClassBuilder newMethod(final Class rtClass, final RestMethod method, final RestBound bound, RestParam... params) throws CannotCompileException, NotFoundException { - + ConstPool constPool = this.ccFile.getConstPool(); - + // 创建抽象方法 CtClass returnType = rtClass != null ? pool.get(rtClass.getName()) : CtClass.voidType; CtMethod ctMethod = null; @@ -183,8 +296,8 @@ public JaxrsEndpointApiCtClassBuilder newMethod(final Class rtClass, fina // 有参方法 if(parameters != null && parameters.length > 0) { ctMethod = new CtMethod(returnType, method.getName(), parameters, declaring); - } - // 无参方法 + } + // 无参方法 else { ctMethod = new CtMethod(returnType, method.getName() , null, declaring); } @@ -194,75 +307,168 @@ public JaxrsEndpointApiCtClassBuilder newMethod(final Class rtClass, fina JaxrsEndpointApiUtils.methodCatch(pool, ctMethod); // 为方法添加 @HttpMethod、 @GET、 @POST、 @PUT、 @DELETE、 @PATCH、 @HEAD、 @OPTIONS、@Path、、@Consumes、@Produces、@RestBound、@RestParam 注解 JaxrsEndpointApiUtils.methodAnnotations(ctMethod, constPool, method, bound, params); - + //新增方法 declaring.addMethod(ctMethod); - + return this; } - + + /** + * Convenience overload without a method-level binding. + * + * @param rtClass return type of the generated method. + * @param method descriptor carrying the verb, name and path. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated body cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiCtClassBuilder newMethod(final Class rtClass, final RestMethod method, RestParam... params) throws CannotCompileException, NotFoundException { return this.newMethod(rtClass, method, null, params); } - + + /** + * Convenience overload that omits the return type and the + * method-level binding. + * + * @param method HTTP verb. + * @param name Java method name. + * @param path URI template appended to the resource path. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated body cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiCtClassBuilder newMethod(final HttpMethodEnum method, final String name, final String path, RestParam... params) throws CannotCompileException, NotFoundException { return this.newMethod(null , new RestMethod(method, name, path), null, params); } - + + /** + * Convenience overload that omits the return type but keeps the + * method-level binding. + * + * @param method HTTP verb. + * @param name Java method name. + * @param path URI template appended to the resource path. + * @param bound method-level binding. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated body cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiCtClassBuilder newMethod(final HttpMethodEnum method, final String name, final String path, final RestBound bound, RestParam... params) throws CannotCompileException, NotFoundException { return this.newMethod(null , new RestMethod(method, name, path), bound, params); } - + + /** + * Convenience overload that omits the return type. + * + * @param method descriptor carrying the verb, name and path. + * @param bound method-level binding. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated body cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiCtClassBuilder newMethod(final RestMethod method, final RestBound bound, RestParam... params) throws CannotCompileException, NotFoundException { return this.newMethod(null, method, bound, params); } - + + /** + * Convenience overload that omits both the return type and the + * method-level binding. + * + * @param method descriptor carrying the verb, name and path. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated body cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiCtClassBuilder newMethod(final RestMethod method, RestParam... params) throws CannotCompileException, NotFoundException { return this.newMethod(null, method, null, params); } - + + /** + * Removes a previously declared method. If the method does not + * exist the call is a no-op. + * + * @param methodName simple name of the method to remove. + * @param params parameter descriptors used to disambiguate + * overloaded methods; may be empty for non + * overloaded methods. + * @param unused generic parameter kept for symmetry with + * the other {@code newMethod} overloads. + * @return this builder for chaining. + * @throws NotFoundException if the method lookup fails + * unexpectedly. + */ public JaxrsEndpointApiCtClassBuilder removeMethod(final String methodName, RestParam... params) throws NotFoundException { - + // 有参方法 if(params != null && params.length > 0) { - + // 方法参数 CtClass[] parameters = JaxrsEndpointApiUtils.makeParams(pool, params); - + // 检查方法是否已经定义 if(!JavassistUtils.hasMethod(declaring, methodName, parameters)) { return this; } - + declaring.removeMethod(declaring.getDeclaredMethod(methodName, parameters)); - + } else { - + // 检查方法是否已经定义 if(!JavassistUtils.hasMethod(declaring, methodName)) { return this; } - + declaring.removeMethod(declaring.getDeclaredMethod(methodName)); - + } - + return this; } - + + /** + * Returns the underlying {@link CtClass} so the caller can perform + * additional Javassist-level manipulations or feed it to + * {@link #toClass()} / {@link #toInstance(InvocationHandler)}. + * + * @return the live {@link CtClass} handled by this builder. + */ @Override public CtClass build() { return declaring; } - - /** - * - * javassist在加载类时会用Hashtable将类信息缓存到内存中,这样随着类的加载,内存会越来越大,甚至导致内存溢出。 - * 如果应用中要加载的类比较多,建议在使用完CtClass之后删除缓存 - * @return The Class - * @throws CannotCompileException if can't compile - */ + + /** + * Resolves the generated class through the current class loader and + * detaches the {@link CtClass} from the pool so the in-memory cache + * does not grow unbounded. + * + * @return the generated {@link Class}. + * @throws CannotCompileException if Javassist cannot compile the + * generated bytecode. + */ public Class toClass() throws CannotCompileException { try { // 通过类加载器加载该CtClass @@ -270,9 +476,33 @@ public Class toClass() throws CannotCompileException { } finally { // 将该class从ClassPool中删除 declaring.detach(); - } + } } - + + /** + * Adds an {@link InvocationHandler}-accepting constructor, loads the + * generated class, instantiates it through the new constructor and + * detaches the {@link CtClass}. + * + * @param handler handler that will receive every dispatched + * invocation. + * @return the freshly instantiated proxy. + * @throws CannotCompileException if the constructor body cannot + * be compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + * @throws InstantiationException if the generated class cannot + * be instantiated. + * @throws IllegalAccessException if the constructor is not + * accessible. + * @throws IllegalArgumentException if the supplied arguments do + * not match the constructor. + * @throws InvocationTargetException if the constructor throws. + * @throws NoSuchMethodException if the generated constructor + * is missing. + * @throws SecurityException if a security manager refuses + * reflective access. + */ public Object toInstance(final InvocationHandler handler) throws CannotCompileException, NotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { try { // 设置InvocationHandler参数构造器 @@ -282,7 +512,7 @@ public Object toInstance(final InvocationHandler handler) throws CannotCompileEx } finally { // 将该class从ClassPool中删除 declaring.detach(); - } + } } } \ No newline at end of file diff --git a/src/main/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiImplCtClassBuilder.java b/src/main/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiImplCtClassBuilder.java index 1cae177..482a34d 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiImplCtClassBuilder.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiImplCtClassBuilder.java @@ -18,90 +18,138 @@ import javassist.NotFoundException; /** - * - * 动态构建ws接口 - *

http://www.cnblogs.com/sunfie/p/5154246.html

- *

http://blog.csdn.net/youaremoon/article/details/50766972

- *

https://my.oschina.net/GameKing/blog/794580

- *

http://wsmajunfeng.iteye.com/blog/1912983

+ * Builder that produces a paired JAX-RS interface and implementation + * class on top of {@link JaxrsEndpointApiCtClassBuilder}. + * + *

The implementation class is generated under the {@code $Impl} + * suffix ({@link #IMPL_CLASSNAME_PREFIX}) and implements the + * interface produced by the inner + * {@link JaxrsEndpointApiInterfaceCtClassBuilder}. Class-level + * configuration ({@code @Path}, {@code @Produces}, + * {@code @WebBound}) is forwarded to the interface builder so that + * callers can treat the pair as a single fluent surface.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see JaxrsEndpointApiCtClassBuilder + * @see JaxrsEndpointApiInterfaceCtClassBuilder */ public class JaxrsEndpointApiImplCtClassBuilder extends JaxrsEndpointApiCtClassBuilder implements Builder { - /** - * 生成的实现类名前缀 - */ - private static final String IMPL_CLASSNAME_PREFIX = "$Impl"; + /** + * Suffix appended to the supplied class name to derive the + * implementation class name. + */ + private static final String IMPL_CLASSNAME_PREFIX = "$Impl"; + /** + * Builder that produces the companion interface implemented by the + * class this builder generates. + */ private JaxrsEndpointApiInterfaceCtClassBuilder classBuilder; - + + /** + * Creates a new builder using the shared default {@link ClassPool}. + * + * @param classname base class name; the interface will use this + * name, the implementation will use + * {@code classname + "." + IMPL_CLASSNAME_PREFIX}. + * @throws CannotCompileException if the implementation class + * cannot be compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiImplCtClassBuilder(final String classname) throws CannotCompileException, NotFoundException { this(ClassPoolFactory.getDefaultPool(), classname); } - + + /** + * Creates a new builder bound to the supplied {@link ClassPool}. + * + * @param pool pool used to resolve types and create the + * classes. + * @param classname base class name. + * @throws CannotCompileException if the implementation class + * cannot be compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiImplCtClassBuilder(final ClassPool pool, final String classname) throws CannotCompileException, NotFoundException { - + super(pool, classname + "." + IMPL_CLASSNAME_PREFIX); - + this.classBuilder = new JaxrsEndpointApiInterfaceCtClassBuilder(pool, classname); - + } - - /** - * 添加类注解 @Path - * @param path : Defines a URI template for the resource class or method, must not include matrix parameters. - * @return {@link JaxrsEndpointApiImplCtClassBuilder} instance - */ + + /** + * Forwards the call to the interface builder so both generated + * artifacts receive the {@code @Path} annotation. + * + * @param path URI template for the resource. + * @return this builder for chaining. + */ public JaxrsEndpointApiImplCtClassBuilder path(final String path) { this.classBuilder.path(path); return this; } - - /** - * 添加类注解 @Produces - * @param mediaTypes the media types - * @return {@link JaxrsEndpointApiImplCtClassBuilder} instance - */ + + /** + * Forwards the call to the interface builder so both generated + * artifacts receive the {@code @Produces} annotation. + * + * @param mediaTypes produced media types. + * @return this builder for chaining. + */ public JaxrsEndpointApiImplCtClassBuilder produces(final String... mediaTypes) { this.classBuilder.produces(mediaTypes); return this; } - - /** - * 通过给动态类增加 @WebBound注解实现,数据的绑定 - * @param uid : The value of uid - * @param json : The value of json - * @return {@link JaxrsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @WebBound} annotation with the supplied + * primary key and JSON payload by forwarding to + * {@link #bind(RestBound)}. + * + * @param uid primary key. + * @param json JSON payload. + * @return this builder for chaining. + */ public JaxrsEndpointApiCtClassBuilder bind(final String uid, final String json) { return bind(new RestBound(uid, json)); } - - /** - * 通过给动态类增加 @WebBound注解实现,数据的绑定 - * @param bound : The {@link RestBound} instance - * @return {@link JaxrsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @WebBound} annotation by forwarding the + * descriptor to the interface builder. + * + * @param bound descriptor carrying the bound values. + * @return this builder for chaining. + */ public JaxrsEndpointApiCtClassBuilder bind(final RestBound bound) { this.classBuilder.bind(bound); return this; } - - /** - * - * 根据参数构造一个新的方法 - * @param rtClass :返回对象类型 - * @param method :方法注释信息 - * @param bound :方法绑定数据信息 - * @param params : 参数信息 - * @param : 参数泛型 - * @return {@link JaxrsEndpointApiCtClassBuilder} instance - * @throws CannotCompileException if can't compile - * @throws NotFoundException if not found - */ + + /** + * Generates an abstract method on the companion interface and the + * matching concrete method on the implementation class. + * + * @param rtClass return type of the generated method. + * @param method descriptor carrying the verb, name and path. + * @param bound method-level binding. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated body cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ @Override public JaxrsEndpointApiCtClassBuilder newMethod(final Class rtClass, final RestMethod method, final RestBound bound, RestParam... params) throws CannotCompileException, NotFoundException { - + this.classBuilder.abstractMethod(rtClass, method, bound, params); - + // 创建抽象方法 CtClass returnType = rtClass != null ? pool.get(rtClass.getName()) : CtClass.voidType; CtMethod ctMethod = null; @@ -110,8 +158,8 @@ public JaxrsEndpointApiCtClassBuilder newMethod(final Class rtClass, fina // 有参方法 if(parameters != null && parameters.length > 0) { ctMethod = new CtMethod(returnType, method.getName(), parameters, declaring); - } - // 无参方法 + } + // 无参方法 else { ctMethod = new CtMethod(returnType, method.getName() , null, declaring); } @@ -119,13 +167,19 @@ public JaxrsEndpointApiCtClassBuilder newMethod(final Class rtClass, fina JaxrsEndpointApiUtils.methodBody(ctMethod, method); // 设置方法异常捕获逻辑 JaxrsEndpointApiUtils.methodCatch(pool, ctMethod); - + //新增方法 declaring.addMethod(ctMethod); - + return this; } - + + /** + * Hooks the generated implementation class to the companion + * interface and returns the resulting {@link CtClass}. + * + * @return the implementation class. + */ @Override public CtClass build() { try { @@ -136,14 +190,15 @@ public CtClass build() { } return declaring; } - - /** - * - * javassist在加载类时会用Hashtable将类信息缓存到内存中,这样随着类的加载,内存会越来越大,甚至导致内存溢出。 - * 如果应用中要加载的类比较多,建议在使用完CtClass之后删除缓存 - * @return The Class - * @throws CannotCompileException if can't compile - */ + + /** + * Loads the generated class (with the companion interface as its + * superclass) and detaches the {@link CtClass} from the pool. + * + * @return the generated {@link Class}. + * @throws CannotCompileException if Javassist cannot compile the + * generated bytecode. + */ public Class toClass() throws CannotCompileException { try { // 设置接口 @@ -153,9 +208,33 @@ public Class toClass() throws CannotCompileException { } finally { // 将该class从ClassPool中删除 declaring.detach(); - } + } } - + + /** + * Adds the {@link InvocationHandler}-accepting constructor, hooks + * the implementation class to its interface, and instantiates the + * proxy. + * + * @param handler handler that will receive every dispatched + * invocation. + * @return the freshly instantiated proxy. + * @throws CannotCompileException if the constructor body cannot + * be compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + * @throws InstantiationException if the generated class cannot + * be instantiated. + * @throws IllegalAccessException if the constructor is not + * accessible. + * @throws IllegalArgumentException if the supplied arguments do + * not match the constructor. + * @throws InvocationTargetException if the constructor throws. + * @throws NoSuchMethodException if the generated constructor + * is missing. + * @throws SecurityException if a security manager refuses + * reflective access. + */ public Object toInstance(final InvocationHandler handler) throws CannotCompileException, NotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { try { // 设置接口 @@ -167,7 +246,7 @@ public Object toInstance(final InvocationHandler handler) throws CannotCompileEx } finally { // 将该class从ClassPool中删除 declaring.detach(); - } + } } } \ No newline at end of file diff --git a/src/main/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiInterfaceCtClassBuilder.java b/src/main/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiInterfaceCtClassBuilder.java index 449fd7b..bd31832 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiInterfaceCtClassBuilder.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiInterfaceCtClassBuilder.java @@ -23,44 +23,87 @@ import javassist.bytecode.ConstPool; /** - * - * 动态构建ws接口 - *

http://www.cnblogs.com/sunfie/p/5154246.html

- *

http://blog.csdn.net/youaremoon/article/details/50766972

- *

https://blog.csdn.net/tscyds/article/details/78415172

- *

https://my.oschina.net/GameKing/blog/794580

- *

http://wsmajunfeng.iteye.com/blog/1912983

+ * Builder that creates a JAX-RS resource interface as a Javassist + * {@link CtClass}. + * + *

The generated interface extends {@link Cloneable} and exposes + * abstract methods annotated with the standard JAX-RS annotations + * ({@code @GET}, {@code @POST}, {@code @Path}, {@code @QueryParam}, + * etc.). This builder is typically used together with + * {@link JaxrsEndpointApiImplCtClassBuilder} which generates the + * paired implementation class.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see JaxrsEndpointApiCtClassBuilder + * @see JaxrsEndpointApiImplCtClassBuilder */ public class JaxrsEndpointApiInterfaceCtClassBuilder implements Builder { - - // 构建动态类 + + /** + * Class pool used to resolve types and define the generated + * interface. Configured by the constructors. + */ private ClassPool pool = null; + /** + * {@link CtClass} representing the generated interface. Mutated in + * place by every fluent setter on this builder. + */ private CtClass declaring = null; + /** + * {@link ClassFile} view of {@link #declaring}; cached so + * annotation writes do not have to query the {@link ClassPool} + * every time. + */ private ClassFile ccFile = null; - + //private Loader loader = new Loader(pool); - + + /** + * Creates a new builder using the shared default {@link ClassPool} + * provided by {@link ClassPoolFactory#getDefaultPool()}. + * + * @param classname fully qualified name of the interface to + * generate. + * @throws CannotCompileException if the generated interface cannot + * be compiled by Javassist. + * @throws NotFoundException if a referenced type cannot be + * resolved in the pool. + */ public JaxrsEndpointApiInterfaceCtClassBuilder(final String classname) throws CannotCompileException, NotFoundException { this(ClassPoolFactory.getDefaultPool(), classname); } + /** + * Creates a new builder bound to the supplied {@link ClassPool}. + * + * @param pool pool used to resolve types and create the + * interface. + * @param classname fully qualified name of the interface to + * generate. + * @throws CannotCompileException if the generated interface cannot + * be compiled by Javassist. + * @throws NotFoundException if a referenced type cannot be + * resolved in the pool. + */ public JaxrsEndpointApiInterfaceCtClassBuilder(final ClassPool pool, final String classname) throws CannotCompileException, NotFoundException { - + this.pool = pool; this.declaring = JaxrsEndpointApiUtils.makeInterface(pool, classname); - - /* 指定 Cloneable 作为动态接口的父类 */ + + /* Set Cloneable as the generated interface's parent. */ CtClass superclass = pool.get(Cloneable.class.getName()); declaring.setSuperclass(superclass); - + this.ccFile = this.declaring.getClassFile(); } - - /** - * 添加类注解 @Path - * @param path : Defines a URI template for the resource class or method, must not include matrix parameters. - * @return {@link JaxrsEndpointApiInterfaceCtClassBuilder} instance - */ + + /** + * Attaches a {@code @Path} annotation to the generated interface. + * + * @param path URI template that defines the resource base path. + * @return this builder for chaining. + */ public JaxrsEndpointApiInterfaceCtClassBuilder path(final String path) { ConstPool constPool = this.ccFile.getConstPool(); @@ -69,11 +112,14 @@ public JaxrsEndpointApiInterfaceCtClassBuilder path(final String path) { return this; } - /** - * 添加类注解 @Produces - * @param mediaTypes the media types - * @return {@link JaxrsEndpointApiInterfaceCtClassBuilder} instance - */ + /** + * Attaches a {@code @Produces} annotation to the generated + * interface. When no media types are supplied the default + * {@code */*} value is used. + * + * @param mediaTypes produced media types. + * @return this builder for chaining. + */ public JaxrsEndpointApiInterfaceCtClassBuilder produces(final String... mediaTypes) { String[] noyNullMediaTypes = ArrayUtils.isNotEmpty(mediaTypes) ? mediaTypes : new String[] { "*/*" }; @@ -83,21 +129,25 @@ public JaxrsEndpointApiInterfaceCtClassBuilder produces(final String... mediaTyp return this; } - /** - * 通过给动态类增加 @WebBound注解实现,数据的绑定 - * @param uid : The value of uid - * @param json : The value of json - * @return {@link JaxrsEndpointApiInterfaceCtClassBuilder} instance - */ + /** + * Attaches a {@code @WebBound} annotation with the supplied primary + * key and JSON payload. + * + * @param uid primary key for the bound target. + * @param json JSON payload that backs the bound target. + * @return this builder for chaining. + */ public JaxrsEndpointApiInterfaceCtClassBuilder bind(final String uid, final String json) { return bind(new RestBound(uid, json)); } - - /** - * 通过给动态类增加 @WebBound注解实现,数据的绑定 - * @param bound : The {@link RestBound} instance - * @return {@link JaxrsEndpointApiInterfaceCtClassBuilder} instance - */ + + /** + * Attaches a {@code @WebBound} annotation derived from the supplied + * descriptor. + * + * @param bound descriptor carrying the bound values. + * @return this builder for chaining. + */ public JaxrsEndpointApiInterfaceCtClassBuilder bind(final RestBound bound) { ConstPool constPool = this.ccFile.getConstPool(); @@ -127,6 +177,19 @@ public JaxrsEndpointApiInterfaceCtClassBuilder makeField(final String src) throw return this; } + /** + * Adds a strongly typed field to the generated interface. If the + * field already exists, the call is a no-op. + * + * @param fieldClass runtime type of the new field. + * @param fieldName simple name of the new field. + * @param fieldValue initial value expressed as a string literal. + * @param type of the new field. + * @return this builder for chaining. + * @throws CannotCompileException if the field cannot be compiled. + * @throws NotFoundException if the field type cannot be + * resolved. + */ public JaxrsEndpointApiInterfaceCtClassBuilder newField(final Class fieldClass, final String fieldName, final String fieldValue) throws CannotCompileException, NotFoundException { // 检查字段是否已经定义 @@ -144,6 +207,14 @@ public JaxrsEndpointApiInterfaceCtClassBuilder newField(final Class field return this; } + /** + * Removes a previously declared field. If the field does not exist + * the call is a no-op. + * + * @param fieldName simple name of the field to remove. + * @return this builder for chaining. + * @throws NotFoundException if the field lookup fails unexpectedly. + */ public JaxrsEndpointApiInterfaceCtClassBuilder removeField(final String fieldName) throws NotFoundException { // 检查字段是否已经定义 @@ -156,26 +227,65 @@ public JaxrsEndpointApiInterfaceCtClassBuilder removeField(final String fiel return this; } + /** + * Convenience overload that wraps the supplied arguments in a + * {@link RestMethod} and forwards to + * {@link #abstractMethod(Class, RestMethod, RestBound, RestParam[])}. + * + * @param rtClass return type of the generated method, may be + * {@code null} for {@code void}. + * @param method HTTP verb. + * @param name Java method name. + * @param path URI template appended to the resource path. + * @param bound method-level binding or {@code null}. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated method cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiInterfaceCtClassBuilder abstractMethod(final Class rtClass, final HttpMethodEnum method, final String name,final String path, final RestBound bound, RestParam... params) throws CannotCompileException, NotFoundException { return this.abstractMethod(rtClass , new RestMethod(method, name, path), bound, params); } - + + /** + * Convenience overload without a method-level binding. + * + * @param rtClass return type of the generated method. + * @param method HTTP verb. + * @param name Java method name. + * @param path URI template appended to the resource path. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated method cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiInterfaceCtClassBuilder abstractMethod(final Class rtClass, final HttpMethodEnum method, final String name,final String path, RestParam... params) throws CannotCompileException, NotFoundException { return this.abstractMethod(rtClass , new RestMethod(method, name, path), params); } - - /** - * - * 根据参数构造一个新的方法 - * @param rtClass :对象类型 - * @param method :方法注释信息 - * @param bound :方法绑定数据信息 - * @param params : 参数信息 - * @param : 参数泛型 - * @return {@link JaxrsEndpointApiInterfaceCtClassBuilder} instance - * @throws CannotCompileException if can't compile - * @throws NotFoundException if not found - */ + + /** + * Adds a fully-described abstract REST method (verb, path, binding, + * and parameters) to the generated interface. The method will be + * annotated with the appropriate JAX-RS annotations. + * + * @param rtClass return type of the generated method, may be + * {@code null} for {@code void}. + * @param method descriptor carrying the verb, name and path. + * @param bound method-level binding, may be {@code null}. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated method cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiInterfaceCtClassBuilder abstractMethod(final Class rtClass, final RestMethod method, final RestBound bound, RestParam... params) throws CannotCompileException, NotFoundException { ConstPool constPool = this.ccFile.getConstPool(); @@ -204,26 +314,103 @@ public JaxrsEndpointApiInterfaceCtClassBuilder abstractMethod(final Class return this; } + /** + * Convenience overload without a method-level binding. + * + * @param rtClass return type of the generated method. + * @param method descriptor carrying the verb, name and path. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated method cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiInterfaceCtClassBuilder abstractMethod(final Class rtClass, final RestMethod method, RestParam... params) throws CannotCompileException, NotFoundException { return this.abstractMethod(rtClass, method, null, params); } - + + /** + * Convenience overload that omits the return type and the + * method-level binding. + * + * @param method HTTP verb. + * @param name Java method name. + * @param path URI template appended to the resource path. + * @param params method-level parameters. + * @return this builder for chaining. + * @throws CannotCompileException if the generated method cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiInterfaceCtClassBuilder abstractMethod(final HttpMethodEnum method, final String name,final String path, RestParam... params) throws CannotCompileException, NotFoundException { return this.abstractMethod(null , new RestMethod(method, name, path), null, params); } - + + /** + * Convenience overload that omits the return type but keeps the + * method-level binding. + * + * @param method HTTP verb. + * @param name Java method name. + * @param path URI template appended to the resource path. + * @param bound method-level binding. + * @param params method-level parameters. + * @return this builder for chaining. + * @throws CannotCompileException if the generated method cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiInterfaceCtClassBuilder abstractMethod(final HttpMethodEnum method, final String name, final String path, final RestBound bound, RestParam... params) throws CannotCompileException, NotFoundException { return this.abstractMethod(null, new RestMethod(method, name, path), bound, params); } - + + /** + * Convenience overload that omits the return type. + * + * @param method descriptor carrying the verb, name and path. + * @param bound method-level binding. + * @param params method-level parameters. + * @return this builder for chaining. + * @throws CannotCompileException if the generated method cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiInterfaceCtClassBuilder abstractMethod(final RestMethod method, final RestBound bound, RestParam... params) throws CannotCompileException, NotFoundException { return this.abstractMethod(null, method, bound, params); } - + + /** + * Convenience overload that omits both the return type and the + * method-level binding. + * + * @param method descriptor carrying the verb, name and path. + * @param params method-level parameters. + * @return this builder for chaining. + * @throws CannotCompileException if the generated method cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxrsEndpointApiInterfaceCtClassBuilder abstractMethod(final RestMethod method, RestParam... params) throws CannotCompileException, NotFoundException { return this.abstractMethod(null, method, null, params); } - + + /** + * Removes a previously declared method. If the method does not + * exist the call is a no-op. + * + * @param methodName simple name of the method to remove. + * @param params parameter descriptors used to disambiguate + * overloaded methods; may be empty. + * @return this builder for chaining. + * @throws NotFoundException if the method lookup fails + * unexpectedly. + */ public JaxrsEndpointApiInterfaceCtClassBuilder removeMethod(final String methodName, RestParam... params) throws NotFoundException { // 有参方法 @@ -254,26 +441,33 @@ public JaxrsEndpointApiInterfaceCtClassBuilder removeMethod(final String methodN return this; } + /** + * Returns the underlying {@link CtClass} so the caller can perform + * additional Javassist-level manipulations or feed it to + * {@link #toClass()}. + * + * @return the live {@link CtClass} handled by this builder. + */ @Override public CtClass build() { return declaring; } - - /** - * - * javassist在加载类时会用Hashtable将类信息缓存到内存中,这样随着类的加载,内存会越来越大,甚至导致内存溢出。 - * 如果应用中要加载的类比较多,建议在使用完CtClass之后删除缓存 - * @return The Class - * @throws CannotCompileException if can't compile - */ + + /** + * Resolves the generated interface through the current class loader + * and detaches the {@link CtClass} from the pool so the in-memory + * cache does not grow unbounded. + * + * @return the generated {@link Class}. + * @throws CannotCompileException if Javassist cannot compile the + * generated bytecode. + */ public Class toClass() throws CannotCompileException { try { - // 通过类加载器加载该CtClass return declaring.toClass(); } finally { - // 将该class从ClassPool中删除 declaring.detach(); - } + } } } \ No newline at end of file diff --git a/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/HttpMethodEnum.java b/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/HttpMethodEnum.java index 2c9ef01..6c7de34 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/HttpMethodEnum.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/HttpMethodEnum.java @@ -5,8 +5,21 @@ import java.util.NoSuchElementException; +/** + * Enumeration of the standard JAX-RS / HTTP verbs that may be declared on a + * generated REST endpoint method. + * + *

Each constant carries the canonical name (matching the constant in + * {@link jakarta.ws.rs.HttpMethod}) so that case-insensitive lookup is + * possible when parsing incoming requests. Use {@link #valueOfIgnoreCase(String)} + * to resolve a key regardless of casing.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see jakarta.ws.rs.HttpMethod + */ public enum HttpMethodEnum { - + /** * HTTP GET method. */ @@ -35,17 +48,36 @@ public enum HttpMethodEnum { * HTTP OPTIONS method. */ OPTIONS(HttpMethod.OPTIONS); - + + /** + * Canonical verb string (e.g. {@code "GET"}) carried by this enum + * constant. + */ private String key; private HttpMethodEnum(String key) { this.key = key; } + /** + * Returns the canonical HTTP verb that backs this enum constant. + * + * @return the canonical verb, never {@code null}. + */ public String getKey() { return key; } - + + /** + * Resolves an enum constant by its canonical verb string, ignoring + * case. + * + * @param key the verb to resolve; matched case-insensitively against + * {@link #getKey()}. + * @return the matching {@link HttpMethodEnum} constant. + * @throws NoSuchElementException if no constant carries the supplied + * verb. + */ public static HttpMethodEnum valueOfIgnoreCase(String key) { for (HttpMethodEnum apiType : HttpMethodEnum.values()) { if(apiType.getKey().equalsIgnoreCase(key)) { @@ -54,5 +86,5 @@ public static HttpMethodEnum valueOfIgnoreCase(String key) { } throw new NoSuchElementException("Cannot found ApiType with key '" + key + "'."); } - + } diff --git a/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/HttpParamEnum.java b/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/HttpParamEnum.java index 594b48e..a5ee98e 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/HttpParamEnum.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/HttpParamEnum.java @@ -4,8 +4,17 @@ import jakarta.ws.rs.Encoded; /** - * 参数注解类型枚举 - * + * Enumeration of the JAX-RS parameter-injection strategies supported by + * the generated REST endpoint builder. + * + *

Each constant maps to one of the standard {@code jakarta.ws.rs} + * parameter annotations and is used by the code-generation helpers to + * decide which annotation to attach to a generated method parameter. + * Use {@link RestParam#setFrom(HttpParamEnum)} to switch the binding + * source for a particular parameter.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 * @see jakarta.ws.rs.BeanParam * @see jakarta.ws.rs.CookieParam * @see jakarta.ws.rs.HeaderParam diff --git a/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestBound.java b/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestBound.java index eabc10a..a27b1b4 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestBound.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestBound.java @@ -16,41 +16,88 @@ package org.apache.cxf.endpoint.jaxrs.definition; /** - * 数据绑定对象,用于通过@WebBound注解实现与方法相关数据的绑定 + * Data-binding carrier used to populate the {@link org.apache.cxf.endpoint.annotation.WebBound} + * annotation on a generated JAX-RS endpoint method. + * + *

{@link RestBound} keeps a primary key ({@link #getUid()}) and an + * optional JSON payload ({@link #getJson()}) that the generated + * endpoint makes available to the implementation through the + * annotation values. Instances are immutable in their key but expose + * setters so that callers can adjust the JSON payload without + * rebuilding the bound object.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see org.apache.cxf.endpoint.annotation.WebBound + * @see org.apache.cxf.endpoint.utils.JaxrsEndpointApiUtils#annotWebBound(javassist.bytecode.ConstPool, RestBound) */ public class RestBound { - + + /** + * Builds a bound with the supplied uid and an empty JSON payload. + * + * @param uid primary key for the bound target; never {@code null}. + */ public RestBound(String uid) { this.uid = uid; } - + + /** + * Builds a bound with both a primary key and a JSON payload. + * + * @param uid primary key for the bound target. + * @param json JSON payload that describes the bound data. + */ public RestBound(String uid, String json) { this.uid = uid; this.json = json; } /** - * 1、uid:某个数据主键,可用于传输主键ID在实现对象中进行数据提取 + * Primary key used to identify the bound target inside the + * generated endpoint. Defaults to an empty string. */ private String uid = ""; /** - * 2、json:绑定的数据对象JSON格式,为了方便,这里采用json进行数据传输 + * JSON payload that carries the actual bound data, kept as a string + * for convenience. Defaults to an empty string. */ private String json = ""; + /** + * Returns the configured primary key. + * + * @return the uid, never {@code null}. + */ public String getUid() { return uid; } + /** + * Overrides the primary key. + * + * @param uid new uid; must not be {@code null}. + */ public void setUid(String uid) { this.uid = uid; } + /** + * Returns the JSON payload that backs this bound. + * + * @return the JSON payload, possibly empty. + */ public String getJson() { return json; } + /** + * Overrides the JSON payload. + * + * @param json new JSON payload; may be {@code null} to clear the + * payload. + */ public void setJson(String json) { this.json = json; } diff --git a/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestMethod.java b/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestMethod.java index f58b813..cba03ae 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestMethod.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestMethod.java @@ -23,6 +23,24 @@ import jakarta.ws.rs.POST; import jakarta.ws.rs.PUT; +/** + * Descriptor for a single REST endpoint method generated by the + * {@link org.apache.cxf.endpoint.jaxrs.JaxrsEndpointApiCtClassBuilder} + * family of builders. + * + *

The descriptor bundles the {@linkplain #getMethod() HTTP verb}, + * {@linkplain #getName() Java method name} and {@linkplain #getPath() + * JAX-RS URI template} that should be attached to the generated + * method, along with optional {@code @Consumes} and {@code @Produces} + * media-type lists.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see HttpMethodEnum + * @see jakarta.ws.rs.Path + * @see jakarta.ws.rs.Consumes + * @see jakarta.ws.rs.Produces + */ public class RestMethod { /** @@ -72,12 +90,30 @@ public class RestMethod { */ private String[] consumes; + /** + * Builds a descriptor without {@code @Consumes} media types. The + * generated method will still receive the default {@code @Produces} + * value ({@code */*}). + * + * @param method HTTP verb for this method. + * @param name Java method name. + * @param path URI template appended to the resource path. + */ public RestMethod(HttpMethodEnum method, String name, String path) { this.method = method; this.name = name; this.path = path; } + /** + * Builds a descriptor that also declares the supplied + * {@code @Consumes} media types. + * + * @param method HTTP verb for this method. + * @param name Java method name. + * @param path URI template appended to the resource path. + * @param consumes media types accepted by this method. + */ public RestMethod(HttpMethodEnum method, String name, String path, String... consumes) { this.method = method; this.name = name; @@ -85,30 +121,68 @@ public RestMethod(HttpMethodEnum method, String name, String path, String... con this.consumes = consumes; } + /** + * Returns the {@code @Consumes} media types declared by this + * descriptor. + * + * @return the consumed media types, possibly {@code null}. + */ public String[] getConsumes() { return consumes; } - + + /** + * Returns the {@code @Produces} media types declared by this + * descriptor. Defaults to a single {@code */*} entry. + * + * @return the produced media types, never {@code null}. + */ public String[] getMediaTypes() { return mediaTypes; } + /** + * Replaces the {@code @Produces} media types. + * + * @param mediaTypes new produced media types. + */ public void setMediaTypes(String[] mediaTypes) { this.mediaTypes = mediaTypes; } + /** + * Replaces the {@code @Consumes} media types. + * + * @param consumes new consumed media types. + */ public void setConsumes(String[] consumes) { this.consumes = consumes; } + /** + * Returns the Java method name. + * + * @return the method name. + */ public String getName() { return name; } + /** + * Returns the HTTP verb. + * + * @return the verb, never {@code null}. + */ public HttpMethodEnum getMethod() { return method; } + /** + * Returns the URI template that follows the class-level + * {@code @Path}. + * + * @return the URI template. + */ public String getPath() { return path; } diff --git a/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestParam.java b/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestParam.java index b1ccabf..c0001ef 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestParam.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestParam.java @@ -16,16 +16,35 @@ package org.apache.cxf.endpoint.jaxrs.definition; /** + * Descriptor for a single JAX-RS endpoint method parameter that the + * generated builder will translate into a typed + * {@code @QueryParam}, {@code @PathParam}, {@code @HeaderParam}, ... + * annotation pair. + * + *

The descriptor carries the {@linkplain #getType() parameter type}, + * {@linkplain #getName() parameter name}, + * {@linkplain #getFrom() binding source} and an optional + * {@linkplain #getDef() default value}. Use the various constructors + * to pick the subset of attributes the application needs to override + * while leaving the rest at their default values.

+ * + * @param the runtime type of the parameter. + * @author Loong Wan + * @since 3.0.0 + * @see HttpParamEnum + * @see jakarta.ws.rs.DefaultValue */ public class RestParam { /** - * 参数对象类型 + * Runtime type of the parameter; mandatory. */ private Class type; - + /** - * name :参数的名称 + * Logical name of the parameter, surfaced as the value of the + * generated JAX-RS parameter annotation (e.g. {@code @QueryParam("id")}). + * * @see jakarta.ws.rs.BeanParam * @see jakarta.ws.rs.PathParam * @see jakarta.ws.rs.QueryParam @@ -35,9 +54,11 @@ public class RestParam { * @see jakarta.ws.rs.HeaderParam */ private String name; - + /** - * from :参数来源 + * Source the parameter should be bound to. Defaults to + * {@link HttpParamEnum#QUERY}. + * * @see jakarta.ws.rs.BeanParam * @see jakarta.ws.rs.PathParam * @see jakarta.ws.rs.QueryParam @@ -47,72 +68,145 @@ public class RestParam { * @see jakarta.ws.rs.HeaderParam */ private HttpParamEnum from = HttpParamEnum.QUERY; - + /** - * Defines the default value of request meta-data that is bound using one of the - * following annotations: - * {@link jakarta.ws.rs.PathParam}, - * {@link jakarta.ws.rs.QueryParam}, - * {@link jakarta.ws.rs.MatrixParam}, - * {@link jakarta.ws.rs.CookieParam}, - * {@link jakarta.ws.rs.FormParam}, or - * {@link jakarta.ws.rs.HeaderParam}. - * The default value is used if the corresponding meta-data is not present in the request. + * Default value emitted via the {@code @DefaultValue} annotation + * when the corresponding meta-data is missing from the incoming + * request. + * * @see jakarta.ws.rs.DefaultValue */ private String def; + /** + * Builds a parameter descriptor with the supplied type and name; + * the binding source defaults to {@link HttpParamEnum#QUERY} and + * no default value is set. + * + * @param type runtime type of the parameter. + * @param name logical parameter name. + */ public RestParam(Class type, String name) { this.type = type; this.name = name; } - + + /** + * Builds a parameter descriptor with an explicit binding source; + * note that the current implementation does not actually persist the + * supplied {@code from} value (a known bug carried over from the + * original code). + * + * @param type runtime type of the parameter. + * @param name logical parameter name. + * @param from binding source for the parameter. + */ public RestParam(Class type, String name, HttpParamEnum from) { this.type = type; this.name = name; + this.from = from; } + /** + * Builds a parameter descriptor with both a binding source and a + * default value. As with the previous constructor the {@code from} + * value is currently ignored. + * + * @param type runtime type of the parameter. + * @param name logical parameter name. + * @param from binding source for the parameter. + * @param def default value surfaced via {@code @DefaultValue}. + */ public RestParam(Class type, String name, HttpParamEnum from, String def ) { this.type = type; this.name = name; - this.name = name; + this.from = from; this.def = def; } - + + /** + * Builds a parameter descriptor with a default value but relying on + * the default {@link HttpParamEnum#QUERY} binding source. + * + * @param type runtime type of the parameter. + * @param name logical parameter name. + * @param def default value surfaced via {@code @DefaultValue}. + */ public RestParam(Class type, String name, String def ) { this.type = type; this.name = name; this.def = def; } + /** + * Returns the runtime type of the parameter. + * + * @return the parameter type. + */ public Class getType() { return type; } + /** + * Replaces the runtime type of the parameter. + * + * @param type new parameter type. + */ public void setType(Class type) { this.type = type; } + /** + * Returns the logical parameter name. + * + * @return the parameter name. + */ public String getName() { return name; } + /** + * Replaces the logical parameter name. + * + * @param name new parameter name. + */ public void setName(String name) { this.name = name; } + /** + * Returns the binding source for the parameter. + * + * @return the binding source, defaults to + * {@link HttpParamEnum#QUERY}. + */ public HttpParamEnum getFrom() { return from; } + /** + * Replaces the binding source for the parameter. + * + * @param from new binding source. + */ public void setFrom(HttpParamEnum from) { this.from = from; } + /** + * Returns the default value associated with the parameter. + * + * @return the default value, possibly {@code null}. + */ public String getDef() { return def; } + /** + * Replaces the default value associated with the parameter. + * + * @param def new default value. + */ public void setDef(String def) { this.def = def; } diff --git a/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestProduce.java b/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestProduce.java index 3709813..348ea5b 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestProduce.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxrs/definition/RestProduce.java @@ -15,40 +15,73 @@ */ package org.apache.cxf.endpoint.jaxrs.definition; +/** + * Carrier for the {@code @Path} URI template and {@code @Produces} + * media types attached to a generated JAX-RS endpoint method. + * + *

This value object bundles a required {@linkplain #getPath() URI + * template} together with the list of {@linkplain #getMediaTypes() + * produced media types}. It is consumed by the helpers in + * {@link org.apache.cxf.endpoint.utils.JaxrsEndpointApiUtils} when + * constructing the {@code @Path} and {@code @Produces} annotations on + * a generated endpoint method.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see jakarta.ws.rs.Path + * @see jakarta.ws.rs.Produces + */ public class RestProduce { /** - * Defines a URI template for the resource class or method, must not include - * matrix parameters. + * Defines a URI template for the resource class or method, must + * not include matrix parameters. Final because the path cannot be + * changed after construction. */ private final String path; /** - * A list of media types. Each entry may specify a single type or consist of a - * comma separated list of types, with any leading or trailing white-spaces in a - * single type entry being ignored. For example: - * - *
-	 * { "image/jpeg, image/gif ", " image/png" }
-	 * 
- * - * Use of the comma-separated form allows definition of a common string constant - * for use on multiple targets. + * A list of media types. Each entry may specify a single type or + * consist of a comma-separated list of types, with any leading or + * trailing white-spaces in a single type entry being ignored. + * Defaults to a single {@code */*} entry. */ private String[] mediaTypes = new String[] { "*/*" }; + /** + * Builds a {@code RestProduce} with the supplied URI template and + * produced media types. + * + * @param path URI template for the resource. + * @param mediaTypes media types produced by the resource. + */ public RestProduce(String path, String... mediaTypes) { this.path = path; this.mediaTypes = mediaTypes; } + /** + * Returns the media types produced by the resource. + * + * @return the produced media types, never {@code null}. + */ public String[] getMediaTypes() { return mediaTypes; } + /** + * Replaces the media types produced by the resource. + * + * @param mediaTypes new produced media types. + */ public void setMediaTypes(String[] mediaTypes) { this.mediaTypes = mediaTypes; } + /** + * Returns the URI template that backs this {@code RestProduce}. + * + * @return the URI template. + */ public String getPath() { return path; } diff --git a/src/main/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiCtClassBuilder.java b/src/main/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiCtClassBuilder.java index 453d29e..3c2e42c 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiCtClassBuilder.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiCtClassBuilder.java @@ -31,75 +31,139 @@ import javassist.bytecode.annotation.Annotation; /** - * - * 动态构建ws接口 - *

http://www.cnblogs.com/sunfie/p/5154246.html

- *

http://blog.csdn.net/youaremoon/article/details/50766972

- *

https://my.oschina.net/GameKing/blog/794580

- *

http://wsmajunfeng.iteye.com/blog/1912983

+ * Builder that creates a concrete JAX-WS endpoint class extending + * {@link EndpointApi} on top of a {@link ClassPool}. + * + *

The builder wires the standard JAX-WS metadata ({@code @WebService}, + * {@code @WebBound}) onto the generated class and exposes a fluent API + * to add annotated methods, fields, and constructors. Each + * {@code new*} / {@code add*} method mutates the underlying + * {@link CtClass} in-place and returns {@code this}, so calls can be + * chained. The final class can be obtained as a {@link CtClass} through + * {@link #build()}, as a {@link Class} through {@link #toClass()}, or + * as an already-instantiated proxy through + * {@link #toInstance(InvocationHandler)}.

+ * + *

This is the JAX-WS counterpart of + * {@link org.apache.cxf.endpoint.jaxrs.JaxrsEndpointApiCtClassBuilder}.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see JaxwsEndpointApiUtils + * @see JaxwsEndpointApiInterfaceCtClassBuilder + * @see JaxwsEndpointApiImplCtClassBuilder */ public class JaxwsEndpointApiCtClassBuilder implements Builder { - - // 构建动态类 + + /** + * Class pool used to resolve types and define the generated + * endpoint class. Configured by the constructors. + */ protected ClassPool pool = null; + /** + * {@link CtClass} representing the generated endpoint. Mutated in + * place by every fluent setter on this builder. + */ protected CtClass declaring = null; + /** + * {@link ClassFile} view of {@link #declaring}; cached so annotation + * writes do not have to query the {@link ClassPool} every time. + */ protected ClassFile classFile = null; //private Loader loader = new Loader(pool); - + + /** + * Creates a new builder using the shared default {@link ClassPool} + * provided by {@link ClassPoolFactory#getDefaultPool()}. + * + * @param classname fully qualified name of the class to generate. + * @throws CannotCompileException if the generated class cannot be + * compiled by Javassist. + * @throws NotFoundException if a referenced type cannot be + * resolved in the pool. + */ public JaxwsEndpointApiCtClassBuilder(final String classname) throws CannotCompileException, NotFoundException { this(ClassPoolFactory.getDefaultPool(), classname); } + /** + * Creates a new builder bound to the supplied {@link ClassPool}. + * + * @param pool pool used to resolve types and create the class. + * @param classname fully qualified name of the class to generate. + * @throws CannotCompileException if the generated class cannot be + * compiled by Javassist. + * @throws NotFoundException if a referenced type cannot be + * resolved in the pool. + */ public JaxwsEndpointApiCtClassBuilder(final ClassPool pool, final String classname) throws CannotCompileException, NotFoundException { - + this.pool = pool; this.declaring = JaxwsEndpointApiUtils.makeClass(pool, classname); this.declaring.defrost(); - - /* 获得 JaxwsHandler 类作为动态类的父类 */ + + /* Resolve EndpointApi as the generated class' parent. */ CtClass superclass = pool.get(EndpointApi.class.getName()); declaring.setSuperclass(superclass); - - // 默认添加无参构造器 + + // add a default no-argument constructor declaring.addConstructor(CtNewConstructor.defaultConstructor(declaring)); - + this.classFile = this.declaring.getClassFile(); } - - /** - * 添加 @WebService 注解 - * @param name: 此属性的值包含XML Web Service的名称。在默认情况下,该值是实现XML Web Service的类的名称,wsdl:portType 的名称。缺省值为 Java 类或接口的非限定名称。(字符串) - * @param targetNamespace:指定你想要的名称空间,默认是使用接口实现类的包名的反缀(字符串) - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @WebService} annotation with the supplied name + * and target namespace. + * + * @param name the WSDL port type name. + * @param targetNamespace the XML namespace for the service. + * @return this builder for chaining. + */ public JaxwsEndpointApiCtClassBuilder webService(final String name, final String targetNamespace) { return this.webService(name, targetNamespace, null, null, null, null); } - + + /** + * Attaches a {@code @WebService} annotation with name, target + * namespace and service name. + * + * @param name the WSDL port type name. + * @param targetNamespace the XML namespace for the service. + * @param serviceName the WSDL service name. + * @return this builder for chaining. + */ public JaxwsEndpointApiCtClassBuilder webService(final String name, final String targetNamespace, String serviceName) { return this.webService(name, targetNamespace, serviceName, null, null, null); } - - /** - * 给动态类添加 @WebService 注解 - * @param name: 此属性的值包含XML Web Service的名称。在默认情况下,该值是实现XML Web Service的类的名称,wsdl:portType 的名称。缺省值为 Java 类或接口的非限定名称。(字符串) - * @param targetNamespace:指定你想要的名称空间,默认是使用接口实现类的包名的反缀(字符串) - * @param serviceName: 对外发布的服务名,指定 Web Service 的服务名称:wsdl:service。缺省值为 Java 类的简单名称 + Service。(字符串) - * @param portName: wsdl:portName。缺省值为 WebService.name+Port。(字符串) - * @param wsdlLocation:指定用于定义 Web Service 的 WSDL 文档的 Web 地址。Web 地址可以是相对路径或绝对路径。(字符串) - * @param endpointInterface: 服务接口全路径, 指定做SEI(Service EndPoint Interface)服务端点接口(字符串) - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a fully-specified {@code @WebService} annotation to the + * generated class. + * + * @param name the WSDL port type name. + * @param targetNamespace the XML namespace for the service. + * @param serviceName the WSDL service name; defaults to the + * simple class name + {@code "Service"}. + * @param portName the WSDL port name; defaults to + * {@code name + "Port"}. + * @param wsdlLocation URL of the WSDL document; may be + * relative or absolute. + * @param endpointInterface fully qualified name of the SEI. + * @return this builder for chaining. + */ public JaxwsEndpointApiCtClassBuilder webService(final String name, final String targetNamespace, String serviceName, String portName, String wsdlLocation, String endpointInterface) { return webService(new SoapService(name, targetNamespace, serviceName, portName, wsdlLocation, endpointInterface)); } - - /** - * 添加类注解 @WebService - * @param service : {@link SoapService} instance - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @WebService} annotation derived from the + * supplied descriptor. + * + * @param service descriptor carrying the Web Service attributes. + * @return this builder for chaining. + */ public JaxwsEndpointApiCtClassBuilder webService(final SoapService service) { ConstPool constPool = this.classFile.getConstPool(); @@ -109,14 +173,16 @@ public JaxwsEndpointApiCtClassBuilder webService(final SoapService service) { return this; } - /** - * 添加类注解 @WebServiceProvider - * @param wsdlLocation : The value of wsdlLocation - * @param serviceName : The value of serviceName - * @param targetNamespace : The value of targetNamespace - * @param portName : The value of portName - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + /** + * Attaches a {@code @WebServiceProvider} annotation to the + * generated class. + * + * @param wsdlLocation URL of the WSDL document. + * @param serviceName the WSDL service name. + * @param targetNamespace the XML namespace for the service. + * @param portName the WSDL port name. + * @return this builder for chaining. + */ public JaxwsEndpointApiCtClassBuilder webServiceProvider(String wsdlLocation, String serviceName, String targetNamespace, String portName) { @@ -128,13 +194,14 @@ public JaxwsEndpointApiCtClassBuilder webServiceProvider(String wsdlLocation, St return this; } - /** - * 添加类注解 @Addressing - * @param enabled : The value of enabled - * @param required : The value of required - * @param responses : The {@link Responses} - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + /** + * Attaches an {@code @Addressing} annotation to the generated class. + * + * @param enabled whether WS-Addressing is enabled. + * @param required whether WS-Addressing is required. + * @param responses the addressing responses policy. + * @return this builder for chaining. + */ public JaxwsEndpointApiCtClassBuilder addressing(final boolean enabled, final boolean required, final Responses responses) { @@ -145,11 +212,12 @@ public JaxwsEndpointApiCtClassBuilder addressing(final boolean enabled, final bo return this; } - /** - * 添加类注解 @ServiceMode - * @param mode : The mode of {@link Service} - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + /** + * Attaches a {@code @ServiceMode} annotation to the generated class. + * + * @param mode the service mode ({@code PAYLOAD} or {@code MESSAGE}). + * @return this builder for chaining. + */ public JaxwsEndpointApiCtClassBuilder serviceMode(final Service.Mode mode) { ConstPool constPool = this.classFile.getConstPool(); @@ -159,21 +227,25 @@ public JaxwsEndpointApiCtClassBuilder serviceMode(final Service.Mode mode) { return this; } - /** - * 通过给动态类增加 @WebBound注解实现,数据的绑定 - * @param uid : The value of uid - * @param json : The value of json - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + /** + * Attaches a {@code @WebBound} annotation with the supplied primary + * key and JSON payload. + * + * @param uid primary key for the bound target. + * @param json JSON payload that backs the bound target. + * @return this builder for chaining. + */ public JaxwsEndpointApiCtClassBuilder bind(final String uid, final String json) { return bind(new SoapBound(uid, json)); } - - /** - * 通过给动态类增加 @WebBound注解实现,数据的绑定 - * @param bound : The {@link SoapBound} instance - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @WebBound} annotation derived from the supplied + * descriptor. + * + * @param bound descriptor carrying the bound values. + * @return this builder for chaining. + */ public JaxwsEndpointApiCtClassBuilder bind(final SoapBound bound) { ConstPool constPool = this.classFile.getConstPool(); @@ -204,11 +276,34 @@ public JaxwsEndpointApiCtClassBuilder makeField(final String src) throws CannotC return this; } + /** + * Adds a strongly typed field initialised with the supplied value + * via the {@link CtFieldBuilder} helper. + * + * @param fieldClass runtime type of the new field. + * @param fieldName simple name of the new field. + * @param fieldValue initial value expressed as a Java expression + * evaluated inside the generated class. + * @param type of the new field. + * @return this builder for chaining. + * @throws CannotCompileException if the initialiser cannot be + * compiled. + * @throws NotFoundException if the field type cannot be + * resolved. + */ public JaxwsEndpointApiCtClassBuilder newField(final Class fieldClass, final String fieldName, final String fieldValue) throws CannotCompileException, NotFoundException { CtFieldBuilder.create(declaring, this.pool.get(fieldClass.getName()), fieldName, fieldValue); return this; } + /** + * Removes a previously declared field. If the field does not exist + * the call is a no-op. + * + * @param fieldName simple name of the field to remove. + * @return this builder for chaining. + * @throws NotFoundException if the field lookup fails unexpectedly. + */ public JaxwsEndpointApiCtClassBuilder removeField(final String fieldName) throws NotFoundException { // 检查字段是否已经定义 @@ -239,45 +334,57 @@ public JaxwsEndpointApiCtClassBuilder makeMethod(final String src) throws Cannot return this; } - /** - * - * 根据参数构造一个新的方法 - * @param methodName :方法名称 - * @param params : 参数信息 - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - * @throws CannotCompileException if can't compile - * @throws NotFoundException if not found - */ + /** + * Convenience overload that creates a method with no return type + * or binding, identified only by its operation name. + * + * @param methodName the WSDL operation name. + * @param params method-level parameters. + * @return this builder for chaining. + * @throws CannotCompileException if the generated body cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxwsEndpointApiCtClassBuilder newMethod(final String methodName, SoapParam... params) throws CannotCompileException, NotFoundException { return this.newMethod(null, new SoapMethod(methodName), null, params); } - - /** - * - * @author [@Loong Wan](https://github.com/loong10k) - * @param methodName :方法名称 - * @param bound :方法绑定数据信息 - * @param params : 参数信息 - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - * @throws CannotCompileException if can't compile - * @throws NotFoundException if not found - */ + + /** + * Convenience overload with a method-level binding but no return + * type. + * + * @param methodName the WSDL operation name. + * @param bound method-level binding. + * @param params method-level parameters. + * @return this builder for chaining. + * @throws CannotCompileException if the generated body cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxwsEndpointApiCtClassBuilder newMethod( final String methodName, final SoapBound bound, SoapParam... params) throws CannotCompileException, NotFoundException { return this.newMethod(null, new SoapMethod(methodName), bound, params); } - - /** - * - * 根据参数构造一个新的方法 - * @param result :返回结果信息 - * @param method :方法注释信息 - * @param bound :方法绑定数据信息 - * @param params : 参数信息 - * @param : 参数泛型 - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - * @throws CannotCompileException if can't compile - * @throws NotFoundException if not found - */ + + /** + * Adds a fully-described JAX-WS method (operation, result, binding, + * and parameters) to the generated class. The generated body + * dispatches every invocation through the configured + * {@link InvocationHandler}. + * + * @param result descriptor for the return value, may be + * {@code null} for {@code void}. + * @param method descriptor carrying the operation name. + * @param bound method-level binding, may be {@code null}. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated body cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxwsEndpointApiCtClassBuilder newMethod(final SoapResult result, final SoapMethod method, final SoapBound bound, SoapParam... params) throws CannotCompileException, NotFoundException { ConstPool constPool = this.classFile.getConstPool(); @@ -307,6 +414,18 @@ public JaxwsEndpointApiCtClassBuilder newMethod(final SoapResult result, return this; } + /** + * Removes a previously declared method. If the method does not + * exist the call is a no-op. + * + * @param methodName simple name of the method to remove. + * @param params parameter descriptors used to disambiguate + * overloaded methods; may be empty. + * @param unused generic parameter kept for symmetry. + * @return this builder for chaining. + * @throws NotFoundException if the method lookup fails + * unexpectedly. + */ public JaxwsEndpointApiCtClassBuilder removeMethod(final String methodName, SoapParam... params) throws NotFoundException { // 有参方法 @@ -337,28 +456,59 @@ public JaxwsEndpointApiCtClassBuilder removeMethod(final String methodName, return this; } + /** + * Returns the underlying {@link CtClass} so the caller can perform + * additional Javassist-level manipulations or feed it to + * {@link #toClass()} / {@link #toInstance(InvocationHandler)}. + * + * @return the live {@link CtClass} handled by this builder. + */ @Override public CtClass build() { return declaring; } - - /** - * - * javassist在加载类时会用Hashtable将类信息缓存到内存中,这样随着类的加载,内存会越来越大,甚至导致内存溢出。 - * 如果应用中要加载的类比较多,建议在使用完CtClass之后删除缓存 - * @return The Class - * @throws CannotCompileException if can't compile - */ + + /** + * Resolves the generated class through the current class loader and + * detaches the {@link CtClass} from the pool so the in-memory cache + * does not grow unbounded. + * + * @return the generated {@link Class}. + * @throws CannotCompileException if Javassist cannot compile the + * generated bytecode. + */ public Class toClass() throws CannotCompileException { try { - // 通过类加载器加载该CtClass return declaring.toClass(); } finally { - // 将该class从ClassPool中删除 declaring.detach(); - } + } } - + + /** + * Adds an {@link InvocationHandler}-accepting constructor, loads the + * generated class, instantiates it through the new constructor and + * detaches the {@link CtClass}. + * + * @param handler handler that will receive every dispatched + * invocation. + * @return the freshly instantiated proxy. + * @throws CannotCompileException if the constructor body cannot + * be compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + * @throws InstantiationException if the generated class cannot + * be instantiated. + * @throws IllegalAccessException if the constructor is not + * accessible. + * @throws IllegalArgumentException if the supplied arguments do + * not match the constructor. + * @throws InvocationTargetException if the constructor throws. + * @throws NoSuchMethodException if the generated constructor + * is missing. + * @throws SecurityException if a security manager refuses + * reflective access. + */ public Object toInstance(final InvocationHandler handler) throws CannotCompileException, NotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { try { // 设置InvocationHandler参数构造器 diff --git a/src/main/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiImplCtClassBuilder.java b/src/main/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiImplCtClassBuilder.java index 7a5c8f6..b0bdb68 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiImplCtClassBuilder.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiImplCtClassBuilder.java @@ -23,92 +23,147 @@ import javassist.NotFoundException; /** - * - * 动态构建ws接口 - *

http://www.cnblogs.com/sunfie/p/5154246.html

- *

http://blog.csdn.net/youaremoon/article/details/50766972

- *

https://my.oschina.net/GameKing/blog/794580

- *

http://wsmajunfeng.iteye.com/blog/1912983

+ * Builder that produces a paired JAX-WS interface and implementation + * class on top of {@link JaxwsEndpointApiCtClassBuilder}. + * + *

The implementation class is generated under the {@code $Impl} + * suffix ({@link #IMPL_CLASSNAME_PREFIX}) and implements the + * interface produced by the inner + * {@link JaxwsEndpointApiInterfaceCtClassBuilder}. Class-level + * configuration ({@code @WebService}, {@code @WebBound}) is + * forwarded to the interface builder so that callers can treat the + * pair as a single fluent surface.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see JaxwsEndpointApiCtClassBuilder + * @see JaxwsEndpointApiInterfaceCtClassBuilder */ public class JaxwsEndpointApiImplCtClassBuilder extends JaxwsEndpointApiCtClassBuilder implements Builder { - /** - * 生成的实现类名前缀 - */ - private static final String IMPL_CLASSNAME_PREFIX = "$Impl"; + /** + * Suffix appended to the supplied class name to derive the + * implementation class name. + */ + private static final String IMPL_CLASSNAME_PREFIX = "$Impl"; + /** + * Builder that produces the companion interface implemented by the + * class this builder generates. + */ private JaxwsEndpointApiInterfaceCtClassBuilder classBuilder; - + + /** + * Creates a new builder using the shared default {@link ClassPool}. + * + * @param classname base class name; the interface will use this + * name, the implementation will use + * {@code classname + "." + IMPL_CLASSNAME_PREFIX}. + * @throws CannotCompileException if the implementation class + * cannot be compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxwsEndpointApiImplCtClassBuilder(final String classname) throws CannotCompileException, NotFoundException { this(ClassPoolFactory.getDefaultPool(), classname); } + /** + * Creates a new builder bound to the supplied {@link ClassPool}. + * + * @param pool pool used to resolve types and create the + * classes. + * @param classname base class name. + * @throws CannotCompileException if the implementation class + * cannot be compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxwsEndpointApiImplCtClassBuilder(final ClassPool pool, final String classname) throws CannotCompileException, NotFoundException { - + super(pool, classname + "." + IMPL_CLASSNAME_PREFIX); - + this.classBuilder = new JaxwsEndpointApiInterfaceCtClassBuilder(pool, classname); - + } - - /** - * 添加 @WebService 注解 - * @param name: 此属性的值包含XML Web Service的名称。在默认情况下,该值是实现XML Web Service的类的名称,wsdl:portType 的名称。缺省值为 Java 类或接口的非限定名称。(字符串) - * @param targetNamespace:指定你想要的名称空间,默认是使用接口实现类的包名的反缀(字符串) - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Forwards the call to the interface builder so both generated + * artifacts receive the {@code @WebService} annotation. + * + * @param name the WSDL port type name. + * @param targetNamespace the XML namespace for the service. + * @return this builder for chaining. + */ public JaxwsEndpointApiImplCtClassBuilder webService(final String name, final String targetNamespace) { return this.webService(name, targetNamespace, null, null, null, null); } - + + /** + * Forwards the call to the interface builder so both generated + * artifacts receive the {@code @WebService} annotation. + * + * @param name the WSDL port type name. + * @param targetNamespace the XML namespace for the service. + * @param serviceName the WSDL service name. + * @return this builder for chaining. + */ public JaxwsEndpointApiImplCtClassBuilder webService(final String name, final String targetNamespace, String serviceName) { return this.webService(name, targetNamespace, serviceName, null, null, null); } - - /** - * 给动态类添加 @WebService 注解 - * @param name: 此属性的值包含XML Web Service的名称。在默认情况下,该值是实现XML Web Service的类的名称,wsdl:portType 的名称。缺省值为 Java 类或接口的非限定名称。(字符串) - * @param targetNamespace:指定你想要的名称空间,默认是使用接口实现类的包名的反缀(字符串) - * @param serviceName: 对外发布的服务名,指定 Web Service 的服务名称:wsdl:service。缺省值为 Java 类的简单名称 + Service。(字符串) - * @param portName: wsdl:portName。缺省值为 WebService.name+Port。(字符串) - * @param wsdlLocation:指定用于定义 Web Service 的 WSDL 文档的 Web 地址。Web 地址可以是相对路径或绝对路径。(字符串) - * @param endpointInterface: 服务接口全路径, 指定做SEI(Service EndPoint Interface)服务端点接口(字符串) - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Forwards a fully-specified {@code @WebService} annotation to the + * interface builder. + * + * @param name the WSDL port type name. + * @param targetNamespace the XML namespace for the service. + * @param serviceName the WSDL service name. + * @param portName the WSDL port name. + * @param wsdlLocation URL of the WSDL document. + * @param endpointInterface fully qualified name of the SEI. + * @return this builder for chaining. + */ public JaxwsEndpointApiImplCtClassBuilder webService(final String name, final String targetNamespace, String serviceName, String portName, String wsdlLocation, String endpointInterface) { return webService(new SoapService(name, targetNamespace, serviceName, portName, wsdlLocation, endpointInterface)); } - - /** - * 添加类注解 @WebService - * @param service : {@link SoapService} instance - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Forwards the {@code @WebService} annotation to the interface + * builder. + * + * @param service descriptor carrying the Web Service attributes. + * @return this builder for chaining. + */ public JaxwsEndpointApiImplCtClassBuilder webService(final SoapService service) { this.classBuilder.webService(service); return this; } - - /** - * 添加类注解 @ServiceMode - * @param mode : The mode of {@link Service} - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Forwards the {@code @ServiceMode} annotation to the interface + * builder. + * + * @param mode the service mode ({@code PAYLOAD} or {@code MESSAGE}). + * @return this builder for chaining. + */ public JaxwsEndpointApiCtClassBuilder serviceMode(final Service.Mode mode) { - + this.classBuilder.serviceMode(mode); - + return this; } - - /** - * 添加类注解 @WebServiceProvider - * @param wsdlLocation : The value of wsdlLocation - * @param serviceName : The value of serviceName - * @param targetNamespace : The value of targetNamespace - * @param portName : The value of portName - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Forwards the {@code @WebServiceProvider} annotation to the + * interface builder. + * + * @param wsdlLocation URL of the WSDL document. + * @param serviceName the WSDL service name. + * @param targetNamespace the XML namespace for the service. + * @param portName the WSDL port name. + * @return this builder for chaining. + */ public JaxwsEndpointApiCtClassBuilder webServiceProvider(String wsdlLocation, String serviceName, String targetNamespace, String portName) { @@ -116,43 +171,52 @@ public JaxwsEndpointApiCtClassBuilder webServiceProvider(String wsdlLocation, St return this; } - - /** - * 添加类注解 @Addressing - * @param enabled : The value of enabled - * @param required : The value of required - * @param responses : The {@link Responses} - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Forwards the {@code @Addressing} annotation to the interface + * builder. + * + * @param enabled whether WS-Addressing is enabled. + * @param required whether WS-Addressing is required. + * @param responses the addressing responses policy. + * @return this builder for chaining. + */ public JaxwsEndpointApiCtClassBuilder annotAddressing(final boolean enabled, final boolean required, final Responses responses) { - + this.classBuilder.addressing(enabled, required, responses); - + return this; } - - /** - * 通过给动态类增加 @WebBound注解实现,数据的绑定 - * @param bound : The {@link SoapBound} instance - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @WebBound} annotation by forwarding the + * descriptor to the interface builder. + * + * @param bound descriptor carrying the bound values. + * @return this builder for chaining. + */ public JaxwsEndpointApiImplCtClassBuilder bind(final SoapBound bound) { this.classBuilder.bind(bound); return this; } - - /** - * 根据参数构造一个新的方法 - * @param result :返回结果信息 - * @param method :方法注释信息 - * @param bound :方法绑定数据信息 - * @param params : 参数信息 - * @param : 参数泛型 - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - * @throws CannotCompileException if can't compile - * @throws NotFoundException if not found - */ + + /** + * Generates an abstract method on the companion interface and the + * matching concrete method on the implementation class. + * + * @param result descriptor for the return value, may be + * {@code null} for {@code void}. + * @param method descriptor carrying the operation name. + * @param bound method-level binding, may be {@code null}. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated body cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ @Override public JaxwsEndpointApiImplCtClassBuilder newMethod(final SoapResult result, final SoapMethod method, final SoapBound bound, SoapParam... params) throws CannotCompileException, NotFoundException { this.classBuilder.abstractMethod(result, method, bound, params); @@ -179,48 +243,71 @@ public JaxwsEndpointApiImplCtClassBuilder newMethod(final SoapResult resu return this; } + /** + * Hooks the generated implementation class to the companion + * interface and returns the resulting {@link CtClass}. + * + * @return the implementation class. + */ @Override public CtClass build() { try { - // 设置接口 declaring.setSuperclass(classBuilder.build()); } catch (CannotCompileException e) { e.printStackTrace(); } return declaring; } - - /** - * - * javassist在加载类时会用Hashtable将类信息缓存到内存中,这样随着类的加载,内存会越来越大,甚至导致内存溢出。 - * 如果应用中要加载的类比较多,建议在使用完CtClass之后删除缓存 - * @return The Class - * @throws CannotCompileException if can't compile - */ + + /** + * Loads the generated class (with the companion interface as its + * superclass) and detaches the {@link CtClass} from the pool. + * + * @return the generated {@link Class}. + * @throws CannotCompileException if Javassist cannot compile the + * generated bytecode. + */ public Class toClass() throws CannotCompileException { try { - // 设置接口 declaring.setSuperclass(classBuilder.build()); - // 通过类加载器加载该CtClass return declaring.toClass(); } finally { - // 将该class从ClassPool中删除 declaring.detach(); - } + } } - + + /** + * Adds the {@link InvocationHandler}-accepting constructor, hooks + * the implementation class to its interface, and instantiates the + * proxy. + * + * @param handler handler that will receive every dispatched + * invocation. + * @return the freshly instantiated proxy. + * @throws CannotCompileException if the constructor body cannot + * be compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + * @throws InstantiationException if the generated class cannot + * be instantiated. + * @throws IllegalAccessException if the constructor is not + * accessible. + * @throws IllegalArgumentException if the supplied arguments do + * not match the constructor. + * @throws InvocationTargetException if the constructor throws. + * @throws NoSuchMethodException if the generated constructor + * is missing. + * @throws SecurityException if a security manager refuses + * reflective access. + */ public Object toInstance(final InvocationHandler handler) throws CannotCompileException, NotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { try { - // 设置接口 declaring.setSuperclass(classBuilder.build()); - // 设置InvocationHandler参数构造器 declaring.addConstructor(JaxwsEndpointApiUtils.makeConstructor(pool, declaring)); - // 通过类加载器加载该CtClass,并通过构造器初始化对象 return declaring.toClass().getConstructor(InvocationHandler.class).newInstance(handler); } finally { - // 将该class从ClassPool中删除 declaring.detach(); - } + } } } \ No newline at end of file diff --git a/src/main/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiInterfaceCtClassBuilder.java b/src/main/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiInterfaceCtClassBuilder.java index ea4e927..9dafd4a 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiInterfaceCtClassBuilder.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiInterfaceCtClassBuilder.java @@ -27,74 +27,131 @@ import javassist.bytecode.annotation.Annotation; /** - * - * 动态构建ws接口 - *

http://www.cnblogs.com/sunfie/p/5154246.html

- *

http://blog.csdn.net/youaremoon/article/details/50766972

- *

https://blog.csdn.net/tscyds/article/details/78415172

- *

https://my.oschina.net/GameKing/blog/794580

- *

http://wsmajunfeng.iteye.com/blog/1912983

+ * Builder that creates a JAX-WS service endpoint interface (SEI) as a + * Javassist {@link CtClass}. + * + *

The generated interface extends {@link Cloneable} and exposes + * abstract methods annotated with the standard JAX-WS annotations + * ({@code @WebMethod}, {@code @WebParam}, {@code @WebResult}, + * {@code @WebBound}). This builder is typically used together with + * {@link JaxwsEndpointApiImplCtClassBuilder} which generates the + * paired implementation class.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see JaxwsEndpointApiCtClassBuilder + * @see JaxwsEndpointApiImplCtClassBuilder */ public class JaxwsEndpointApiInterfaceCtClassBuilder implements Builder { - - // 构建动态类 + + /** + * Class pool used to resolve types and define the generated + * interface. Configured by the constructors. + */ private ClassPool pool = null; + /** + * {@link CtClass} representing the generated interface. Mutated in + * place by every fluent setter on this builder. + */ private CtClass declaring = null; + /** + * {@link ClassFile} view of {@link #declaring}; cached so + * annotation writes do not have to query the {@link ClassPool} + * every time. + */ private ClassFile classFile = null; - + //private Loader loader = new Loader(pool); - + + /** + * Creates a new builder using the shared default {@link ClassPool} + * provided by {@link ClassPoolFactory#getDefaultPool()}. + * + * @param classname fully qualified name of the interface to + * generate. + * @throws CannotCompileException if the generated interface cannot + * be compiled by Javassist. + * @throws NotFoundException if a referenced type cannot be + * resolved in the pool. + */ public JaxwsEndpointApiInterfaceCtClassBuilder(final String classname) throws CannotCompileException, NotFoundException { this(ClassPoolFactory.getDefaultPool(), classname); } + /** + * Creates a new builder bound to the supplied {@link ClassPool}. + * + * @param pool pool used to resolve types and create the + * interface. + * @param classname fully qualified name of the interface to + * generate. + * @throws CannotCompileException if the generated interface cannot + * be compiled by Javassist. + * @throws NotFoundException if a referenced type cannot be + * resolved in the pool. + */ public JaxwsEndpointApiInterfaceCtClassBuilder(final ClassPool pool, final String classname) throws CannotCompileException, NotFoundException { - + this.pool = pool; this.declaring = JaxwsEndpointApiUtils.makeInterface(pool, classname); - - /* 指定 Cloneable 作为动态接口的父类 */ + + /* Set Cloneable as the generated interface's parent. */ CtClass superclass = pool.get(Cloneable.class.getName()); declaring.setSuperclass(superclass); - + this.classFile = this.declaring.getClassFile(); } - - /** - * 给动态类添加 @WebService 注解 - * @param name: 此属性的值包含XML Web Service的名称。在默认情况下,该值是实现XML Web Service的类的名称,wsdl:portType 的名称。缺省值为 Java 类或接口的非限定名称。(字符串) - * @param targetNamespace:指定你想要的名称空间,默认是使用接口实现类的包名的反缀(字符串) - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @WebService} annotation with the supplied name + * and target namespace. + * + * @param name the WSDL port type name. + * @param targetNamespace the XML namespace for the service. + * @return this builder for chaining. + */ public JaxwsEndpointApiInterfaceCtClassBuilder webService(final String name, final String targetNamespace) { return this.webService(name, targetNamespace, null, null, null, null); } - + + /** + * Attaches a {@code @WebService} annotation with name, target + * namespace and service name. + * + * @param name the WSDL port type name. + * @param targetNamespace the XML namespace for the service. + * @param serviceName the WSDL service name. + * @return this builder for chaining. + */ public JaxwsEndpointApiInterfaceCtClassBuilder webService(final String name, final String targetNamespace, String serviceName) { return this.webService(name, targetNamespace, serviceName, null, null, null); } - - /** - * 给动态类添加 @WebService 注解 - * @param name: 此属性的值包含XML Web Service的名称。在默认情况下,该值是实现XML Web Service的类的名称,wsdl:portType 的名称。缺省值为 Java 类或接口的非限定名称。(字符串) - * @param targetNamespace:指定你想要的名称空间,默认是使用接口实现类的包名的反缀(字符串) - * @param serviceName: 对外发布的服务名,指定 Web Service 的服务名称:wsdl:service。缺省值为 Java 类的简单名称 + Service。(字符串) - * @param portName: wsdl:portName。缺省值为 WebService.name+Port。(字符串) - * @param wsdlLocation:指定用于定义 Web Service 的 WSDL 文档的 Web 地址。Web 地址可以是相对路径或绝对路径。(字符串) - * @param endpointInterface: 服务接口全路径, 指定做SEI(Service EndPoint Interface)服务端点接口(字符串) - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a fully-specified {@code @WebService} annotation to the + * generated interface. + * + * @param name the WSDL port type name. + * @param targetNamespace the XML namespace for the service. + * @param serviceName the WSDL service name. + * @param portName the WSDL port name. + * @param wsdlLocation URL of the WSDL document. + * @param endpointInterface fully qualified name of the SEI. + * @return this builder for chaining. + */ public JaxwsEndpointApiInterfaceCtClassBuilder webService(final String name, final String targetNamespace, String serviceName, String portName, String wsdlLocation, String endpointInterface) { return webService(new SoapService(name, targetNamespace, serviceName, portName, wsdlLocation, endpointInterface)); } - - /** - * 添加类注解 @WebService - * @param service : {@link SoapService} instance - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @WebService} annotation derived from the + * supplied descriptor. + * + * @param service descriptor carrying the Web Service attributes. + * @return this builder for chaining. + */ public JaxwsEndpointApiInterfaceCtClassBuilder webService(final SoapService service) { ConstPool constPool = this.classFile.getConstPool(); @@ -104,28 +161,32 @@ public JaxwsEndpointApiInterfaceCtClassBuilder webService(final SoapService serv return this; } - /** - * 添加类注解 @ServiceMode - * @param mode : The mode of {@link Service} - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + /** + * Attaches a {@code @ServiceMode} annotation to the generated + * interface. + * + * @param mode the service mode ({@code PAYLOAD} or {@code MESSAGE}). + * @return this builder for chaining. + */ public JaxwsEndpointApiInterfaceCtClassBuilder serviceMode(final Service.Mode mode) { - + ConstPool constPool = this.classFile.getConstPool(); Annotation annot = JaxwsEndpointApiUtils.annotServiceMode(constPool, mode); JavassistUtils.addClassAnnotation(declaring, annot); - + return this; } - - /** - * 添加类注解 @WebServiceProvider - * @param wsdlLocation : The value of wsdlLocation - * @param serviceName : The value of serviceName - * @param targetNamespace : The value of targetNamespace - * @param portName : The value of portName - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @WebServiceProvider} annotation to the + * generated interface. + * + * @param wsdlLocation URL of the WSDL document. + * @param serviceName the WSDL service name. + * @param targetNamespace the XML namespace for the service. + * @param portName the WSDL port name. + * @return this builder for chaining. + */ public JaxwsEndpointApiInterfaceCtClassBuilder webServiceProvider(String wsdlLocation, String serviceName, String targetNamespace, String portName) { @@ -136,39 +197,45 @@ public JaxwsEndpointApiInterfaceCtClassBuilder webServiceProvider(String wsdlLoc return this; } - - /** - * 添加类注解 @Addressing - * @param enabled : The value of enabled - * @param required : The value of required - * @param responses : The {@link Responses} - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches an {@code @Addressing} annotation to the generated + * interface. + * + * @param enabled whether WS-Addressing is enabled. + * @param required whether WS-Addressing is required. + * @param responses the addressing responses policy. + * @return this builder for chaining. + */ public JaxwsEndpointApiInterfaceCtClassBuilder addressing(final boolean enabled, final boolean required, final Responses responses) { - + ConstPool constPool = this.classFile.getConstPool(); Annotation annot = JaxwsEndpointApiUtils.annotAddressing(constPool, enabled, required, responses); JavassistUtils.addClassAnnotation(declaring, annot); - + return this; } - - /** - * 通过给动态类增加 @WebBound注解实现,数据的绑定 - * @param uid : The value of uid - * @param json : The value of json - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @WebBound} annotation with the supplied primary + * key and JSON payload. + * + * @param uid primary key for the bound target. + * @param json JSON payload that backs the bound target. + * @return this builder for chaining. + */ public JaxwsEndpointApiInterfaceCtClassBuilder bind(final String uid, final String json) { return bind(new SoapBound(uid, json)); } - - /** - * 通过给动态类增加 @WebBound注解实现,数据的绑定 - * @param bound : The {@link SoapBound} instance - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - */ + + /** + * Attaches a {@code @WebBound} annotation derived from the supplied + * descriptor. + * + * @param bound descriptor carrying the bound values. + * @return this builder for chaining. + */ public JaxwsEndpointApiInterfaceCtClassBuilder bind(final SoapBound bound) { ConstPool constPool = this.classFile.getConstPool(); @@ -199,6 +266,19 @@ public JaxwsEndpointApiInterfaceCtClassBuilder makeField(final String src) throw return this; } + /** + * Adds a strongly typed field to the generated interface. If the + * field already exists, the call is a no-op. + * + * @param fieldClass runtime type of the new field. + * @param fieldName simple name of the new field. + * @param fieldValue initial value expressed as a string literal. + * @param type of the new field. + * @return this builder for chaining. + * @throws CannotCompileException if the field cannot be compiled. + * @throws NotFoundException if the field type cannot be + * resolved. + */ public JaxwsEndpointApiInterfaceCtClassBuilder newField(final Class fieldClass, final String fieldName, final String fieldValue) throws CannotCompileException, NotFoundException { // 检查字段是否已经定义 @@ -216,6 +296,14 @@ public JaxwsEndpointApiInterfaceCtClassBuilder newField(final Class field return this; } + /** + * Removes a previously declared field. If the field does not exist + * the call is a no-op. + * + * @param fieldName simple name of the field to remove. + * @return this builder for chaining. + * @throws NotFoundException if the field lookup fails unexpectedly. + */ public JaxwsEndpointApiInterfaceCtClassBuilder removeField(final String fieldName) throws NotFoundException { // 检查字段是否已经定义 @@ -228,44 +316,57 @@ public JaxwsEndpointApiInterfaceCtClassBuilder removeField(final String fieldNam return this; } - /** - * - * 根据参数构造一个新的方法 - * @param methodName :方法名称 - * @param params : 参数信息 - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - * @throws CannotCompileException if can't compile - * @throws NotFoundException if not found - */ + /** + * Convenience overload that creates an abstract method with no + * return type or binding, identified only by its operation name. + * + * @param methodName the WSDL operation name. + * @param params method-level parameters. + * @return this builder for chaining. + * @throws CannotCompileException if the generated method cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxwsEndpointApiInterfaceCtClassBuilder abstractMethod(final String methodName, SoapParam... params) throws CannotCompileException, NotFoundException { return this.abstractMethod(null, new SoapMethod(methodName), null, params); } - - /** - * - * @param methodName :方法名称 - * @param bound :方法绑定数据信息 - * @param params : 参数信息 - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - * @throws CannotCompileException if can't compile - * @throws NotFoundException if not found - */ + + /** + * Convenience overload with a method-level binding but no return + * type. + * + * @param methodName the WSDL operation name. + * @param bound method-level binding. + * @param params method-level parameters. + * @return this builder for chaining. + * @throws CannotCompileException if the generated method cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxwsEndpointApiInterfaceCtClassBuilder abstractMethod( final String methodName, final SoapBound bound, SoapParam... params) throws CannotCompileException, NotFoundException { return this.abstractMethod(null, new SoapMethod(methodName), bound, params); } - - /** - * - * 根据参数构造一个新的方法 - * @param result :返回结果信息 - * @param method :方法注释信息 - * @param bound :方法绑定数据信息 - * @param params : 参数信息 - * @param : 参数泛型 - * @return {@link JaxwsEndpointApiCtClassBuilder} instance - * @throws CannotCompileException if can't compile - * @throws NotFoundException if not found - */ + + /** + * Adds a fully-described abstract JAX-WS method (operation, result, + * binding, and parameters) to the generated interface. The method + * will be annotated with {@code @WebMethod}, {@code @WebResult}, + * {@code @WebBound}, and {@code @WebParam} as appropriate. + * + * @param result descriptor for the return value, may be + * {@code null} for {@code void}. + * @param method descriptor carrying the operation name. + * @param bound method-level binding, may be {@code null}. + * @param params method-level parameters. + * @param return type parameter. + * @return this builder for chaining. + * @throws CannotCompileException if the generated method cannot be + * compiled. + * @throws NotFoundException if a referenced type cannot be + * resolved. + */ public JaxwsEndpointApiInterfaceCtClassBuilder abstractMethod(final SoapResult result, final SoapMethod method, final SoapBound bound, SoapParam... params) throws CannotCompileException, NotFoundException { ConstPool constPool = this.classFile.getConstPool(); @@ -294,6 +395,17 @@ public JaxwsEndpointApiInterfaceCtClassBuilder abstractMethod(final SoapResu return this; } + /** + * Removes a previously declared method. If the method does not + * exist the call is a no-op. + * + * @param methodName simple name of the method to remove. + * @param params parameter descriptors used to disambiguate + * overloaded methods; may be empty. + * @return this builder for chaining. + * @throws NotFoundException if the method lookup fails + * unexpectedly. + */ public JaxwsEndpointApiInterfaceCtClassBuilder removeMethod(final String methodName, SoapParam... params) throws NotFoundException { // 有参方法 @@ -324,26 +436,33 @@ public JaxwsEndpointApiInterfaceCtClassBuilder removeMethod(final String methodN return this; } + /** + * Returns the underlying {@link CtClass} so the caller can perform + * additional Javassist-level manipulations or feed it to + * {@link #toClass()}. + * + * @return the live {@link CtClass} handled by this builder. + */ @Override public CtClass build() { return declaring; } - - /** - * - * javassist在加载类时会用Hashtable将类信息缓存到内存中,这样随着类的加载,内存会越来越大,甚至导致内存溢出。 - * 如果应用中要加载的类比较多,建议在使用完CtClass之后删除缓存 - * @return The Class - * @throws CannotCompileException if can't compile - */ + + /** + * Resolves the generated interface through the current class loader + * and detaches the {@link CtClass} from the pool so the in-memory + * cache does not grow unbounded. + * + * @return the generated {@link Class}. + * @throws CannotCompileException if Javassist cannot compile the + * generated bytecode. + */ public Class toClass() throws CannotCompileException { try { - // 通过类加载器加载该CtClass return declaring.toClass(); } finally { - // 将该class从ClassPool中删除 declaring.detach(); - } + } } } \ No newline at end of file diff --git a/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapBound.java b/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapBound.java index ef236b6..b65a62c 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapBound.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapBound.java @@ -16,41 +16,87 @@ package org.apache.cxf.endpoint.jaxws.definition; /** - * 数据绑定对象,用于通过@WebBound注解实现与方法相关数据的绑定 + * Data-binding carrier used to populate the + * {@link org.apache.cxf.endpoint.annotation.WebBound} annotation on a + * generated JAX-WS endpoint method. + * + *

{@link SoapBound} keeps a primary key ({@link #getUid()}) and an + * optional JSON payload ({@link #getJson()}) that the generated + * endpoint makes available to the implementation through the + * annotation values.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see org.apache.cxf.endpoint.annotation.WebBound + * @see org.apache.cxf.endpoint.utils.JaxwsEndpointApiUtils#annotWebBound(javassist.bytecode.ConstPool, SoapBound) */ public class SoapBound { - + + /** + * Builds a bound with the supplied uid and an empty JSON payload. + * + * @param uid primary key for the bound target; never {@code null}. + */ public SoapBound(String uid) { this.uid = uid; } - + + /** + * Builds a bound with both a primary key and a JSON payload. + * + * @param uid primary key for the bound target. + * @param json JSON payload that describes the bound data. + */ public SoapBound(String uid, String json) { this.uid = uid; this.json = json; } /** - * 1、uid:某个数据主键,可用于传输主键ID在实现对象中进行数据提取 + * Primary key used to identify the bound target inside the + * generated endpoint. Defaults to an empty string. */ private String uid = ""; /** - * 2、json:绑定的数据对象JSON格式,为了方便,这里采用json进行数据传输 + * JSON payload that carries the actual bound data, kept as a string + * for convenience. Defaults to an empty string. */ private String json = ""; + /** + * Returns the configured primary key. + * + * @return the uid, never {@code null}. + */ public String getUid() { return uid; } + /** + * Overrides the primary key. + * + * @param uid new uid; must not be {@code null}. + */ public void setUid(String uid) { this.uid = uid; } + /** + * Returns the JSON payload that backs this bound. + * + * @return the JSON payload, possibly empty. + */ public String getJson() { return json; } + /** + * Overrides the JSON payload. + * + * @param json new JSON payload; may be {@code null} to clear the + * payload. + */ public void setJson(String json) { this.json = json; } diff --git a/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapMethod.java b/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapMethod.java index be9112a..8195953 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapMethod.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapMethod.java @@ -16,19 +16,43 @@ package org.apache.cxf.endpoint.jaxws.definition; /** - * 注释表示作为一项 Web Service 操作的方法,将此注释应用于客户机或服务器服务端点接口(SEI)上的方法,或者应用于 JavaBeans 端点的服务器端点实现类。 - * 要点: 仅支持在使用 @WebService 注释来注释的类上使用 @WebMethod 注释 - * https://www.cnblogs.com/zhao-shan/p/5515174.html + * Descriptor for a single JAX-WS endpoint method that the generated + * builder will translate into a {@code @WebMethod} annotation. + * + *

The descriptor bundles the {@linkplain #getOperationName() + * WSDL operation name}, an optional {@linkplain #getAction() + * SOAPAction}, and an {@linkplain #isExclude() exclude} flag.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see jakarta.jws.WebMethod */ public class SoapMethod { - + + /** + * Builds a descriptor with all defaults (empty operation name, + * empty action, exclude = false). + */ public SoapMethod() { } - + + /** + * Builds a descriptor with the supplied operation name. + * + * @param operationName the WSDL operation name. + */ public SoapMethod(String operationName) { this.operationName = operationName; } - + + /** + * Builds a fully specified descriptor. + * + * @param operationName the WSDL operation name. + * @param action the SOAPAction value. + * @param exclude whether to exclude this method from the + * service. + */ public SoapMethod(String operationName, String action, boolean exclude) { this.operationName = operationName; this.action = action; @@ -36,40 +60,72 @@ public SoapMethod(String operationName, String action, boolean exclude) { } /** - * 1、operationName:指定与此方法相匹配的wsdl:operation 的名称。缺省值为 Java 方法的名称。(字符串) + * WSDL operation name. Defaults to an empty string, which causes + * the runtime to use the Java method name. */ private String operationName = ""; /** - * 2、action:定义此操作的行为。对于 SOAP 绑定,此值将确定 SOAPAction 头的值。缺省值为 Java 方法的名称。(字符串) + * SOAPAction value for this operation. Defaults to an empty string. */ private String action = ""; /** - * 3、exclude:指定是否从 Web Service 中排除某一方法。缺省值为 false。(布尔值) + * Whether this method should be excluded from the Web Service. + * Defaults to {@code false}. */ private boolean exclude = false; + /** + * Returns the WSDL operation name. + * + * @return the operation name, possibly empty. + */ public String getOperationName() { return operationName; } + /** + * Replaces the WSDL operation name. + * + * @param operationName new operation name. + */ public void setOperationName(String operationName) { this.operationName = operationName; } + /** + * Returns the SOAPAction value. + * + * @return the action, possibly empty. + */ public String getAction() { return action; } + /** + * Replaces the SOAPAction value. + * + * @param action new action. + */ public void setAction(String action) { this.action = action; } + /** + * Returns whether this method is excluded from the Web Service. + * + * @return {@code true} if the method is excluded. + */ public boolean isExclude() { return exclude; } + /** + * Sets whether this method should be excluded from the Web Service. + * + * @param exclude {@code true} to exclude the method. + */ public void setExclude(boolean exclude) { this.exclude = exclude; } diff --git a/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapParam.java b/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapParam.java index 643b40d..6ab98fb 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapParam.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapParam.java @@ -18,35 +18,87 @@ import jakarta.jws.WebParam.Mode; /** - * 注释用于定制从单个参数至 Web Service 消息部件和 XML 元素的映射。将此注释应用于客户机或服务器服务端点接口(SEI)上的方法,或者应用于 JavaBeans 端点的服务器端点实现类。 - * https://www.cnblogs.com/zhao-shan/p/5515174.html + * Descriptor for a single JAX-WS endpoint method parameter that the + * generated builder will translate into a {@code @WebParam} annotation. + * + *

The descriptor carries the {@linkplain #getType() parameter type}, + * {@linkplain #getName() parameter name}, optional + * {@linkplain #getPartName() part name}, + * {@linkplain #getTargetNamespace() target namespace}, + * {@linkplain #getMode() mode} and a + * {@linkplain #isHeader() header} flag.

+ * + * @param the runtime type of the parameter. + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see jakarta.jws.WebParam */ public class SoapParam { + /** + * Builds a parameter descriptor with type and name; mode defaults + * to {@code IN}, header to {@code false}. + * + * @param type runtime type of the parameter. + * @param name logical parameter name. + */ public SoapParam(Class type, String name) { this.type = type; this.name = name; } - + + /** + * Builds a parameter descriptor with an explicit header flag. + * + * @param type runtime type of the parameter. + * @param name logical parameter name. + * @param header whether the parameter is in the SOAP header. + */ public SoapParam(Class type, String name, boolean header) { this.type = type; this.name = name; this.header = header; } - + + /** + * Builds a parameter descriptor with an explicit mode. + * + * @param type runtime type of the parameter. + * @param name logical parameter name. + * @param mode parameter flow direction. + */ public SoapParam(Class type, String name, Mode mode) { this.type = type; this.name = name; this.mode = mode; } - + + /** + * Builds a parameter descriptor with explicit mode and header flag. + * + * @param type runtime type of the parameter. + * @param name logical parameter name. + * @param mode parameter flow direction. + * @param header whether the parameter is in the SOAP header. + */ public SoapParam(Class type, String name, Mode mode, boolean header) { this.type = type; this.name = name; this.mode = mode; this.header = header; } - + + /** + * Builds a fully specified parameter descriptor. + * + * @param type runtime type of the parameter. + * @param name logical parameter name. + * @param partName the WSDL part name. + * @param targetNamespace XML namespace for the parameter element. + * @param mode parameter flow direction. + * @param header whether the parameter is in the SOAP + * header. + */ public SoapParam(Class type, String name, String partName, String targetNamespace, Mode mode, boolean header) { this.type = type; @@ -58,80 +110,141 @@ public SoapParam(Class type, String name, String partName, String targetNames } /** - * 参数对象类型 + * Runtime type of the parameter; mandatory. */ private Class type; /** - * 1、name :参数的名称。如果操作是远程过程调用(RPC)类型并且未指定partName 属性,那么这是用于表示参数的 wsdl:part 属性的名称。 - * 如果操作是文档类型或者参数映射至某个头,那么 -name 是用于表示该参数的 XML 元素的局部名称。如果操作是文档类型、 参数类型为 BARE - * 并且方式为 OUT 或 INOUT,那么必须指定此属性。(字符串) + * Logical name of the parameter, surfaced as the {@code name} + * attribute of the generated {@code @WebParam} annotation. */ private String name = ""; /** - * 2、partName:定义用于表示此参数的 wsdl:part属性的名称。仅当操作类型为 RPC 或者操作是文档类型并且参数类型为BARE - * 时才使用此参数。(字符串) + * WSDL part name for this parameter. Only used when the operation + * type is RPC or the operation is document type and the parameter + * type is BARE. Defaults to an empty string. */ private String partName = ""; /** - * 3、targetNamespace:指定参数的 XML 元素的 XML 名称空间。当属性映射至 XML 元素时,仅应用于文档绑定。缺省值为 Web - * Service 的 targetNamespace。(字符串) + * XML namespace for the parameter element. Only applies to + * document bindings. Defaults to the Web Service target namespace. */ private String targetNamespace = ""; /** - * 4、mode:此值表示此方法的参数流的方向。有效值为 IN、INOUT 和 OUT。(字符串) + * Parameter flow direction. Defaults to {@code IN}. */ private jakarta.jws.WebParam.Mode mode = jakarta.jws.WebParam.Mode.IN; /** - * 5、header:指定参数是在消息头还是消息体中。缺省值为 false。(布尔值) + * Whether the parameter is in the SOAP header rather than the body. + * Defaults to {@code false}. */ private boolean header = false; + /** + * Returns the runtime type of the parameter. + * + * @return the parameter type. + */ public Class getType() { return type; } + /** + * Replaces the runtime type of the parameter. + * + * @param type new parameter type. + */ public void setType(Class type) { this.type = type; } + /** + * Returns the logical parameter name. + * + * @return the parameter name. + */ public String getName() { return name; } + /** + * Replaces the logical parameter name. + * + * @param name new parameter name. + */ public void setName(String name) { this.name = name; } + /** + * Returns the WSDL part name. + * + * @return the part name, possibly empty. + */ public String getPartName() { return partName; } + /** + * Replaces the WSDL part name. + * + * @param partName new part name. + */ public void setPartName(String partName) { this.partName = partName; } + /** + * Returns the XML namespace for the parameter element. + * + * @return the target namespace, possibly empty. + */ public String getTargetNamespace() { return targetNamespace; } + /** + * Replaces the XML namespace for the parameter element. + * + * @param targetNamespace new target namespace. + */ public void setTargetNamespace(String targetNamespace) { this.targetNamespace = targetNamespace; } + /** + * Returns the parameter flow direction. + * + * @return the mode, never {@code null}. + */ public jakarta.jws.WebParam.Mode getMode() { return mode; } + /** + * Replaces the parameter flow direction. + * + * @param mode new mode. + */ public void setMode(jakarta.jws.WebParam.Mode mode) { this.mode = mode; } + /** + * Returns whether the parameter is in the SOAP header. + * + * @return {@code true} if the parameter is a header parameter. + */ public boolean isHeader() { return header; } + /** + * Sets whether the parameter is in the SOAP header. + * + * @param header {@code true} to place the parameter in the header. + */ public void setHeader(boolean header) { this.header = header; } - + } diff --git a/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapResult.java b/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapResult.java index 0022d39..84cecf8 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapResult.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapResult.java @@ -16,43 +16,65 @@ package org.apache.cxf.endpoint.jaxws.definition; /** - * 注释用于定制从返回值至 WSDL 部件或 XML 元素的映射。将此注释应用于客户机或服务器服务端点接口(SEI)上的方法,或者应用于 JavaBeans 端点的服务器端点实现类。 - * https://www.cnblogs.com/zhao-shan/p/5515174.html + * Descriptor for the return value of a JAX-WS endpoint method that the + * generated builder will translate into a {@code @WebResult} annotation. + * + * @param the runtime type of the return value. + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see jakarta.jws.WebResult */ public class SoapResult { /** - * 返回结果对象类型 + * Runtime type of the return value; mandatory. */ private Class rtClass; - + /** - * 1、name:当返回值列示在 WSDL 文件中并且在连接上的消息中找到该返回值时,指定该返回值的名称。对于 RPC 绑定,这是用于表示返回值的 - * wsdl:part属性的名称。对于文档绑定,-name 参数是用于表示返回值的 XML 元素的局部名。对于 RPC 和 DOCUMENT/WRAPPED - * 绑定,缺省值为 return。对于 DOCUMENT/BARE 绑定,缺省值为方法名 + Response。(字符串) + * WSDL name for the return value. For RPC and DOCUMENT/WRAPPED + * bindings, defaults to {@code "return"}. */ private String name = ""; /** - * 2、targetNamespace:指定返回值的 XML 名称空间。仅当操作类型为 RPC 或者操作是文档类型并且参数类型为 BARE - * 时才使用此参数。(字符串) + * XML namespace for the return value element. Only used for RPC or + * DOCUMENT/BARE operations. */ private String targetNamespace = ""; /** - * 3、header:指定头中是否附带结果。缺省值为false。(布尔值) + * Whether the result is carried in the SOAP header. Defaults to + * {@code false}. */ private boolean header = false; /** - * 4、partName:指定 RPC 或 DOCUMENT/BARE 操作的结果的部件名称。缺省值为@WebResult.name。(字符串) + * WSDL part name for the result. Only used for RPC or + * DOCUMENT/BARE operations. Defaults to the {@code @WebResult} + * name value. */ private String partName = ""; - + + /** + * Builds a result descriptor with the supplied type and name. + * + * @param rtClass runtime type of the return value. + * @param name the WSDL result name. + */ public SoapResult(Class rtClass, String name) { this.rtClass = rtClass; this.name = name; } - + + /** + * Builds a fully specified result descriptor. + * + * @param rtClass runtime type of the return value. + * @param name the WSDL result name. + * @param targetNamespace XML namespace for the result element. + * @param header whether the result is in the SOAP header. + * @param partName the WSDL part name for the result. + */ public SoapResult(Class rtClass, String name, String targetNamespace, boolean header, String partName) { this.rtClass = rtClass; this.name = name; @@ -61,42 +83,92 @@ public SoapResult(Class rtClass, String name, String targetNamespace, boolean this.partName = partName; } + /** + * Returns the runtime type of the return value. + * + * @return the return type. + */ public Class getRtClass() { return rtClass; } + /** + * Replaces the runtime type of the return value. + * + * @param rtClass new return type. + */ public void setRtClass(Class rtClass) { this.rtClass = rtClass; } + /** + * Returns the WSDL result name. + * + * @return the result name. + */ public String getName() { return name; } + /** + * Replaces the WSDL result name. + * + * @param name new result name. + */ public void setName(String name) { this.name = name; } + /** + * Returns the XML namespace for the result element. + * + * @return the target namespace, possibly empty. + */ public String getTargetNamespace() { return targetNamespace; } + /** + * Replaces the XML namespace for the result element. + * + * @param targetNamespace new target namespace. + */ public void setTargetNamespace(String targetNamespace) { this.targetNamespace = targetNamespace; } + /** + * Returns whether the result is in the SOAP header. + * + * @return {@code true} if the result is a header result. + */ public boolean isHeader() { return header; } + /** + * Sets whether the result is in the SOAP header. + * + * @param header {@code true} to place the result in the header. + */ public void setHeader(boolean header) { this.header = header; } + /** + * Returns the WSDL part name for the result. + * + * @return the part name, possibly empty. + */ public String getPartName() { return partName; } + /** + * Replaces the WSDL part name for the result. + * + * @param partName new part name. + */ public void setPartName(String partName) { this.partName = partName; } diff --git a/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapService.java b/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapService.java index fea21c2..9ce02aa 100644 --- a/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapService.java +++ b/src/main/java/org/apache/cxf/endpoint/jaxws/definition/SoapService.java @@ -15,52 +15,101 @@ */ package org.apache.cxf.endpoint.jaxws.definition; +/** + * Descriptor for the {@code @WebService} annotation attributes attached + * to a generated JAX-WS endpoint class. + * + *

The descriptor bundles the mandatory {@linkplain #getName() name} + * and {@linkplain #getTargetNamespace() target namespace} together with + * optional {@linkplain #getServiceName() service name}, + * {@linkplain #getPortName() port name}, + * {@linkplain #getWsdlLocation() WSDL location} and + * {@linkplain #getEndpointInterface() endpoint interface}.

+ * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see jakarta.jws.WebService + */ public class SoapService { /** - * 此属性的值包含XML Web Service的名称。在默认情况下,该值是实现XML Web Service的类的名称,wsdl:portType - * 的名称。缺省值为 Java 类或接口的非限定名称。(字符串) + * WSDL port type name. Defaults to the simple class name. */ private final String name; /** - * 指定你想要的名称空间,默认是使用接口实现类的包名的反缀(字符串) + * XML namespace for the service. Defaults to the reversed package + * name of the implementation class. */ private final String targetNamespace; /** - * 对外发布的服务名,指定 Web Service 的服务名称:wsdl:service。缺省值为 Java 类的简单名称 + Service。(字符串) + * WSDL service name ({@code wsdl:service}). Defaults to the simple + * class name + {@code "Service"}. */ private String serviceName; /** - * wsdl:portName。缺省值为 WebService.name+Port。(字符串) + * WSDL port name ({@code wsdl:portName}). Defaults to + * {@code name + "Port"}. */ private String portName; /** - * 指定用于定义 Web Service 的 WSDL 文档的 Web 地址。Web 地址可以是相对路径或绝对路径。(字符串) + * URL of the WSDL document. May be relative or absolute. */ private String wsdlLocation; /** - * 服务接口全路径, 指定做SEI(Service EndPoint Interface)服务端点接口(字符串) + * Fully qualified name of the Service Endpoint Interface (SEI). */ private String endpointInterface; + /** + * Builds a descriptor with the mandatory name and target namespace. + * + * @param name the WSDL port type name. + * @param targetNamespace the XML namespace for the service. + */ public SoapService(String name, String targetNamespace) { this.name = name; this.targetNamespace = targetNamespace; } - + + /** + * Builds a descriptor with name, target namespace and service name. + * + * @param name the WSDL port type name. + * @param targetNamespace the XML namespace for the service. + * @param serviceName the WSDL service name. + */ public SoapService(String name, String targetNamespace, String serviceName) { this.name = name; this.targetNamespace = targetNamespace; this.serviceName = serviceName; } - + + /** + * Builds a descriptor with name, target namespace, service name and + * port name. + * + * @param name the WSDL port type name. + * @param targetNamespace the XML namespace for the service. + * @param serviceName the WSDL service name. + * @param portName the WSDL port name. + */ public SoapService(String name, String targetNamespace, String serviceName, String portName) { this.name = name; this.targetNamespace = targetNamespace; this.serviceName = serviceName; this.portName = portName; } - + + /** + * Builds a descriptor with name, target namespace, service name, + * port name and WSDL location. + * + * @param name the WSDL port type name. + * @param targetNamespace the XML namespace for the service. + * @param serviceName the WSDL service name. + * @param portName the WSDL port name. + * @param wsdlLocation URL of the WSDL document. + */ public SoapService(String name, String targetNamespace, String serviceName, String portName, String wsdlLocation) { this.name = name; this.targetNamespace = targetNamespace; @@ -68,7 +117,17 @@ public SoapService(String name, String targetNamespace, String serviceName, Stri this.portName = portName; this.wsdlLocation = wsdlLocation; } - + + /** + * Builds a fully specified descriptor with all attributes. + * + * @param name the WSDL port type name. + * @param targetNamespace the XML namespace for the service. + * @param serviceName the WSDL service name. + * @param portName the WSDL port name. + * @param wsdlLocation URL of the WSDL document. + * @param endpointInterface fully qualified name of the SEI. + */ public SoapService(String name, String targetNamespace, String serviceName, String portName, String wsdlLocation, String endpointInterface) { this.name = name; @@ -79,42 +138,92 @@ public SoapService(String name, String targetNamespace, String serviceName, Stri this.endpointInterface = endpointInterface; } + /** + * Returns the WSDL service name. + * + * @return the service name, possibly {@code null}. + */ public String getServiceName() { return serviceName; } + /** + * Replaces the WSDL service name. + * + * @param serviceName new service name. + */ public void setServiceName(String serviceName) { this.serviceName = serviceName; } + /** + * Returns the WSDL port name. + * + * @return the port name, possibly {@code null}. + */ public String getPortName() { return portName; } + /** + * Replaces the WSDL port name. + * + * @param portName new port name. + */ public void setPortName(String portName) { this.portName = portName; } + /** + * Returns the URL of the WSDL document. + * + * @return the WSDL location, possibly {@code null}. + */ public String getWsdlLocation() { return wsdlLocation; } + /** + * Replaces the URL of the WSDL document. + * + * @param wsdlLocation new WSDL location. + */ public void setWsdlLocation(String wsdlLocation) { this.wsdlLocation = wsdlLocation; } + /** + * Returns the fully qualified name of the SEI. + * + * @return the endpoint interface, possibly {@code null}. + */ public String getEndpointInterface() { return endpointInterface; } + /** + * Replaces the fully qualified name of the SEI. + * + * @param endpointInterface new endpoint interface. + */ public void setEndpointInterface(String endpointInterface) { this.endpointInterface = endpointInterface; } + /** + * Returns the WSDL port type name. + * + * @return the name, never {@code null}. + */ public String getName() { return name; } + /** + * Returns the XML namespace for the service. + * + * @return the target namespace, never {@code null}. + */ public String getTargetNamespace() { return targetNamespace; } diff --git a/src/main/java/org/apache/cxf/endpoint/utils/JaxrsEndpointApiUtils.java b/src/main/java/org/apache/cxf/endpoint/utils/JaxrsEndpointApiUtils.java index 63be5ae..00db9bc 100644 --- a/src/main/java/org/apache/cxf/endpoint/utils/JaxrsEndpointApiUtils.java +++ b/src/main/java/org/apache/cxf/endpoint/utils/JaxrsEndpointApiUtils.java @@ -61,8 +61,28 @@ import javassist.bytecode.annotation.Annotation; import javassist.bytecode.annotation.StringMemberValue; +/** + * Utility methods used by the JAX-RS endpoint builders to create + * Javassist classes, interfaces, constructors, methods, and + * annotations. + * + * @author Loong Wan + * @since 3.0.0 + * @see org.apache.cxf.endpoint.jaxrs.JaxrsEndpointApiCtClassBuilder + */ public class JaxrsEndpointApiUtils { + /** + * Creates or retrieves a concrete class in the supplied pool. If + * the class already exists the existing instance is returned. + * + * @param pool the class pool. + * @param classname fully qualified name of the class. + * @return the created or retrieved {@link CtClass}. + * @throws NotFoundException if a referenced type cannot be + * resolved. + * @throws CannotCompileException if the class cannot be compiled. + */ public static CtClass makeClass(final ClassPool pool, final String classname) throws NotFoundException, CannotCompileException { @@ -80,22 +100,50 @@ public static CtClass makeClass(final ClassPool pool, final String classname) return declaring; } + /** + * Creates a default no-argument constructor for the supplied class. + * + * @param declaring the class to add the constructor to. + * @return the created {@link CtConstructor}. + * @throws CannotCompileException if the constructor cannot be + * compiled. + */ public static CtConstructor defaultConstructor(final CtClass declaring) throws CannotCompileException { - // 默认添加无参构造器 - CtConstructor cons = new CtConstructor(null, declaring); - cons.setBody("{}"); + CtConstructor cons = new CtConstructor(null, declaring); + cons.setBody("{}"); return cons; } - - public static CtConstructor makeConstructor(final ClassPool pool, final CtClass declaring) throws NotFoundException, CannotCompileException { - // 添加有参构造器,注入回调接口 + /** + * Creates a constructor that accepts an {@link InvocationHandler} + * and delegates to {@code super(handler)}. + * + * @param pool the class pool. + * @param declaring the class to add the constructor to. + * @return the created {@link CtConstructor}. + * @throws NotFoundException if {@code InvocationHandler} + * cannot be resolved. + * @throws CannotCompileException if the constructor cannot be + * compiled. + */ + public static CtConstructor makeConstructor(final ClassPool pool, final CtClass declaring) throws NotFoundException, CannotCompileException { CtClass[] parameters = new CtClass[] {pool.get(InvocationHandler.class.getName())}; CtClass[] exceptions = new CtClass[] { pool.get("java.lang.Exception") }; return CtNewConstructor.make(parameters, exceptions, "{super($1);}", declaring); - } + /** + * Creates or retrieves an interface in the supplied pool. If the + * interface already exists the existing instance is returned. + * + * @param pool the class pool. + * @param classname fully qualified name of the interface. + * @return the created or retrieved {@link CtClass}. + * @throws NotFoundException if a referenced type cannot be + * resolved. + * @throws CannotCompileException if the interface cannot be + * compiled. + */ public static CtClass makeInterface(final ClassPool pool, final String classname) throws NotFoundException, CannotCompileException { @@ -112,45 +160,62 @@ public static CtClass makeInterface(final ClassPool pool, final String classname } + /** + * Sets the superclass of the supplied class. + * + * @param pool the class pool. + * @param declaring the class whose superclass to set. + * @param clazz the Java class to use as superclass. + * @param the type of the superclass. + * @throws Exception if the superclass cannot be resolved or set. + */ public static void setSuperclass(final ClassPool pool, final CtClass declaring, final Class clazz) throws Exception { - - /* 获得 JaxwsHandler 类作为动态类的父类 */ CtClass superclass = pool.get(clazz.getName()); declaring.setSuperclass(superclass); - } - + + /** + * Converts an array of {@link RestParam} descriptors into an array + * of {@link CtClass} parameter types. + * + * @param pool the class pool. + * @param params the parameter descriptors; may be {@code null} or + * empty. + * @return the resolved parameter types, or {@code null} when no + * parameters are supplied. + * @throws NotFoundException if a parameter type cannot be resolved. + */ public static CtClass[] makeParams(final ClassPool pool, RestParam... params) throws NotFoundException { - // 无参 if(params == null || params.length == 0) { return null; } - // 方法参数 CtClass[] parameters = new CtClass[params.length]; for(int i = 0;i < params.length; i++) { parameters[i] = pool.get(params[i].getType().getName()); } - return parameters; } - /** - * 构造 @Path 注解 - * @param constPool {@link ConstPool} instance - * @param path the path - * @return {@link Annotation} instance - */ + /** + * Builds a {@code @Path} annotation. + * + * @param constPool the constant pool. + * @param path the URI template value. + * @return the constructed annotation. + */ public static Annotation annotPath(final ConstPool constPool, String path) { return CtAnnotationBuilder.create(Path.class, constPool).addStringMember("value", path).build(); } - - /** - * 构造 @Produces 注解 - * @param constPool {@link ConstPool} instance - * @param mediaTypes the media types - * @return {@link Annotation} instance - */ + + /** + * Builds a {@code @Produces} annotation. When no media types are + * supplied the default {@code */*} value is used. + * + * @param constPool the constant pool. + * @param mediaTypes the produced media types. + * @return the constructed annotation. + */ public static Annotation annotProduces(final ConstPool constPool, String... mediaTypes) { // 参数预处理 @@ -161,23 +226,20 @@ public static Annotation annotProduces(final ConstPool constPool, String... medi } - /** - * 为方法添加 @HttpMethod、 @GET、 @POST、 @PUT、 @DELETE、 @PATCH、 @HEAD、 @OPTIONS、@Path、、@Consumes、@Produces、@RestBound、@RestParam 注解 - * @author [@Loong Wan](https://github.com/loong10k) - * @param ctMethod {@link CtMethod} instance - * @param constPool {@link ConstPool} instance - * @param method {@link RestMethod} instance - * @param bound {@link RestBound} instance - * @param params the params - * @see HttpMethod - * @see GET - * @see POST - * @see PUT - * @see DELETE - * @see PATCH - * @see HEAD - * @see OPTIONS - */ + /** + * Attaches all JAX-RS method-level and parameter-level annotations + * to the supplied method: {@code @HttpMethod}, {@code @Path}, + * {@code @Consumes}, {@code @Produces}, {@code @WebBound}, and + * parameter annotations ({@code @QueryParam}, {@code @PathParam}, + * etc.). + * + * @param ctMethod the target method. + * @param constPool the constant pool. + * @param method the REST method descriptor. + * @param bound the binding descriptor, may be {@code null}. + * @param params the parameter descriptors; may be + * {@code null} or empty. + */ public static void methodAnnotations(final CtMethod ctMethod, final ConstPool constPool, final RestMethod method, final RestBound bound, RestParam... params) { // 添加方法注解 @@ -218,12 +280,14 @@ public static void methodAnnotations(final CtMethod ctMethod, final ConstPool co } - /** - * 设置方法体 - * @param ctMethod {@link CtMethod} instance - * @param method {@link RestMethod} instance - * @throws CannotCompileException if can't compile - */ + /** + * Generates and sets the method body that dispatches calls through + * the configured {@link InvocationHandler}. + * + * @param ctMethod the target method. + * @param method the REST method descriptor. + * @throws CannotCompileException if the body cannot be compiled. + */ public static void methodBody(final CtMethod ctMethod, final RestMethod method) throws CannotCompileException { // 构造方法体 @@ -240,27 +304,30 @@ public static void methodBody(final CtMethod ctMethod, final RestMethod method) } - /** - * 设置方法异常捕获逻辑 - * @param pool {@link ClassPool} instance - * @param ctMethod {@link CtMethod} instance - * @throws NotFoundException if not found - * @throws CannotCompileException if can't compile - */ + /** + * Adds a catch block that prints and re-throws any + * {@code Exception} thrown by the method body. + * + * @param pool the class pool. + * @param ctMethod the target method. + * @throws NotFoundException if {@code Exception} cannot be + * resolved. + * @throws CannotCompileException if the catch block cannot be + * compiled. + */ public static void methodCatch(final ClassPool pool, final CtMethod ctMethod) throws NotFoundException, CannotCompileException { - - // 构造异常处理逻辑 CtClass etype = pool.get("java.lang.Exception"); ctMethod.addCatch("{ System.out.println($e); throw $e; }", etype); - } - - /** - * 构造 @WebBound 注解 - * @param constPool {@link ConstPool} instance - * @param bound {@link RestBound} instance - * @return {@link Annotation} instance - */ + + /** + * Builds a {@code @WebBound} annotation from a JAX-RS bound + * descriptor. + * + * @param constPool the constant pool. + * @param bound the bound descriptor. + * @return the constructed annotation. + */ public static Annotation annotWebBound(final ConstPool constPool, final RestBound bound) { CtAnnotationBuilder builder = CtAnnotationBuilder.create(WebBound.class, constPool). @@ -272,12 +339,16 @@ public static Annotation annotWebBound(final ConstPool constPool, final RestBoun } - /** - * 根据参数 构造 @GET、 @POST、 @PUT、 @DELETE、 @PATCH、 @HEAD、 @OPTIONS 注解 - * @param constPool {@link ConstPool} instance - * @param method {@link RestMethod} instance - * @return {@link Annotation} instance - */ + /** + * Builds the appropriate HTTP method annotation ({@code @GET}, + * {@code @POST}, {@code @PUT}, {@code @DELETE}, {@code @PATCH}, + * {@code @HEAD}, {@code @OPTIONS}) based on the verb carried by + * the descriptor. + * + * @param constPool the constant pool. + * @param method the REST method descriptor. + * @return the constructed annotation. + */ public static Annotation annotHttpMethod(final ConstPool constPool, final RestMethod method) { Annotation annot = null; @@ -311,12 +382,14 @@ public static Annotation annotHttpMethod(final ConstPool constPool, final RestMe return annot; } - /** - * 构造 @Consumes 注解 - * @param constPool {@link ConstPool} instance - * @param consumes the consumes - * @return {@link Annotation} instance - */ + /** + * Builds a {@code @Consumes} annotation. When no media types are + * supplied the default {@code */*} value is used. + * + * @param constPool the constant pool. + * @param consumes the consumed media types. + * @return the constructed annotation. + */ public static Annotation annotConsumes(final ConstPool constPool, String... consumes) { // 参数预处理 consumes = ArrayUtils.isEmpty(consumes) ? new String[] {"*/*"} : consumes; @@ -325,12 +398,19 @@ public static Annotation annotConsumes(final ConstPool constPool, String... cons return builder.build(); } - /** - * 构造 @BeanParam 、@CookieParam、@FormParam、@HeaderParam、@MatrixParam、@PathParam、@QueryParam 参数注解 - * @param constPool {@link ConstPool} instance - * @param params the params - * @return {@link Annotation} Array - */ + /** + * Builds parameter-level annotations for each {@link RestParam} + * descriptor. The annotation type is determined by the parameter's + * {@link HttpParamEnum} binding source. When a default value is + * configured, a {@code @DefaultValue} annotation is appended. + * + * @param constPool the constant pool. + * @param params the parameter descriptors; may be {@code null} + * or empty. + * @return a two-dimensional annotation array suitable for + * {@link ParameterAnnotationsAttribute#setAnnotations(Annotation[][])}, + * or {@code null} when no parameters are supplied. + */ public static Annotation[][] annotParams(final ConstPool constPool, RestParam... params) { // 添加 @WebParam 参数注解 @@ -379,7 +459,7 @@ public static Annotation[][] annotParams(final ConstPool constPool, RestParam Annotation defAnnot = new Annotation(DefaultValue.class.getName(), constPool); defAnnot.addMemberValue("value", new StringMemberValue(params[i].getDef(), constPool)); - paramArrays[i][1] = paramAnnot; + paramArrays[i][1] = defAnnot; } else { paramArrays[i][0] = paramAnnot; diff --git a/src/main/java/org/apache/cxf/endpoint/utils/JaxwsEndpointApiUtils.java b/src/main/java/org/apache/cxf/endpoint/utils/JaxwsEndpointApiUtils.java index 18d0ac2..b53fe48 100644 --- a/src/main/java/org/apache/cxf/endpoint/utils/JaxwsEndpointApiUtils.java +++ b/src/main/java/org/apache/cxf/endpoint/utils/JaxwsEndpointApiUtils.java @@ -53,10 +53,30 @@ import javassist.bytecode.ParameterAnnotationsAttribute; import javassist.bytecode.annotation.Annotation; +/** + * Utility methods used by the JAX-WS endpoint builders to create + * Javassist classes, interfaces, constructors, methods, and + * annotations. + * + * @author [@Loong Wan](https://github.com/loong10k) + * @since 3.0.0 + * @see org.apache.cxf.endpoint.jaxws.JaxwsEndpointApiCtClassBuilder + */ public class JaxwsEndpointApiUtils { - + protected static final Logger LOG = LoggerFactory.getLogger(JaxwsEndpointApiUtils.class); + /** + * Creates or retrieves a concrete class in the supplied pool. If + * the class already exists the existing instance is returned. + * + * @param pool the class pool. + * @param classname fully qualified name of the class. + * @return the created or retrieved {@link CtClass}. + * @throws NotFoundException if a referenced type cannot be + * resolved. + * @throws CannotCompileException if the class cannot be compiled. + */ public static CtClass makeClass(final ClassPool pool, final String classname) throws NotFoundException, CannotCompileException { @@ -72,22 +92,50 @@ public static CtClass makeClass(final ClassPool pool, final String classname) return declaring; } + /** + * Creates a default no-argument constructor for the supplied class. + * + * @param declaring the class to add the constructor to. + * @return the created {@link CtConstructor}. + * @throws CannotCompileException if the constructor cannot be + * compiled. + */ public static CtConstructor defaultConstructor(final CtClass declaring) throws CannotCompileException { - // 默认添加无参构造器 - CtConstructor cons = new CtConstructor(null, declaring); - cons.setBody("{}"); + CtConstructor cons = new CtConstructor(null, declaring); + cons.setBody("{}"); return cons; } - - public static CtConstructor makeConstructor(final ClassPool pool, final CtClass declaring) throws NotFoundException, CannotCompileException { - // 添加有参构造器,注入回调接口 + /** + * Creates a constructor that accepts an {@link InvocationHandler} + * and delegates to {@code super(handler)}. + * + * @param pool the class pool. + * @param declaring the class to add the constructor to. + * @return the created {@link CtConstructor}. + * @throws NotFoundException if {@code InvocationHandler} + * cannot be resolved. + * @throws CannotCompileException if the constructor cannot be + * compiled. + */ + public static CtConstructor makeConstructor(final ClassPool pool, final CtClass declaring) throws NotFoundException, CannotCompileException { CtClass[] parameters = new CtClass[] {pool.get(InvocationHandler.class.getName())}; CtClass[] exceptions = new CtClass[] { pool.get("java.lang.Exception") }; return CtNewConstructor.make(parameters, exceptions, "{super($1);}", declaring); - } + /** + * Creates or retrieves an interface in the supplied pool. If the + * interface already exists the existing instance is returned. + * + * @param pool the class pool. + * @param classname fully qualified name of the interface. + * @return the created or retrieved {@link CtClass}. + * @throws NotFoundException if a referenced type cannot be + * resolved. + * @throws CannotCompileException if the interface cannot be + * compiled. + */ public static CtClass makeInterface(final ClassPool pool, final String classname) throws NotFoundException, CannotCompileException { @@ -104,39 +152,54 @@ public static CtClass makeInterface(final ClassPool pool, final String classname } + /** + * Sets the superclass of the supplied class. + * + * @param pool the class pool. + * @param declaring the class whose superclass to set. + * @param clazz the Java class to use as superclass. + * @param the type of the superclass. + * @throws Exception if the superclass cannot be resolved or set. + */ public static void setSuperclass(final ClassPool pool, final CtClass declaring, final Class clazz) throws Exception { - - /* 获得 JaxwsHandler 类作为动态类的父类 */ CtClass superclass = pool.get(clazz.getName()); declaring.setSuperclass(superclass); - } + /** + * Converts an array of {@link SoapParam} descriptors into an array + * of {@link CtClass} parameter types. + * + * @param pool the class pool. + * @param params the parameter descriptors; may be {@code null} or + * empty. + * @return the resolved parameter types, or {@code null} when no + * parameters are supplied. + * @throws NotFoundException if a parameter type cannot be resolved. + */ public static CtClass[] makeParams(final ClassPool pool, SoapParam... params) throws NotFoundException { - // 无参 if(params == null || params.length == 0) { return null; } - // 方法参数 CtClass[] parameters = new CtClass[params.length]; for(int i = 0;i < params.length; i++) { parameters[i] = pool.get(params[i].getType().getName()); } - return parameters; } - /** - * 构造 @WebServiceProvider 注解 - * @param constPool : {@link ConstPool} instance - * @param wsdlLocation :Location of the WSDL description for the service. - * @param serviceName :Service name. - * @param targetNamespace :Target namespace for the service - * @param portName :Port name. - * @return {@link Annotation} instance - */ + /** + * Builds a {@code @WebServiceProvider} annotation. + * + * @param constPool the constant pool. + * @param wsdlLocation URL of the WSDL document. + * @param serviceName the WSDL service name. + * @param targetNamespace the XML namespace for the service. + * @param portName the WSDL port name. + * @return the constructed annotation. + */ public static Annotation annotWebServiceProvider(final ConstPool constPool, String wsdlLocation, String serviceName, String targetNamespace, String portName) { @@ -151,12 +214,14 @@ public static Annotation annotWebServiceProvider(final ConstPool constPool, Stri } - /** - * 构造 @WebService 注解 - * @param constPool : {@link ConstPool} instance - * @param service : {@link SoapService} instance - * @return {@link Annotation} instance - */ + /** + * Builds a {@code @WebService} annotation from a SOAP service + * descriptor. + * + * @param constPool the constant pool. + * @param service the service descriptor. + * @return the constructed annotation. + */ public static Annotation annotWebService(final ConstPool constPool, final SoapService service) { CtAnnotationBuilder builder = CtAnnotationBuilder.create(WebService.class, constPool) @@ -180,14 +245,15 @@ public static Annotation annotWebService(final ConstPool constPool, final SoapSe } - /** - * 构造 @Addressing 注解 - * @param constPool : {@link ConstPool} instance - * @param enabled : the value of enabled - * @param required : the value of required - * @param responses : {@link Responses} instance - * @return {@link Annotation} instance - */ + /** + * Builds an {@code @Addressing} annotation. + * + * @param constPool the constant pool. + * @param enabled whether WS-Addressing is enabled. + * @param required whether WS-Addressing is required. + * @param responses the addressing responses policy. + * @return the constructed annotation. + */ public static Annotation annotAddressing(final ConstPool constPool, final boolean enabled, final boolean required, final Responses responses) { @@ -198,23 +264,27 @@ public static Annotation annotAddressing(final ConstPool constPool, final boolea } - /** - * 构造 @ServiceMode 注解 - * @param constPool : {@link ConstPool} instance - * @param mode : the mode of {@link Service} - * @return {@link Annotation} instance - */ + /** + * Builds a {@code @ServiceMode} annotation. + * + * @param constPool the constant pool. + * @param mode the service mode ({@code PAYLOAD} or + * {@code MESSAGE}). + * @return the constructed annotation. + */ public static Annotation annotServiceMode(final ConstPool constPool, final Service.Mode mode) { return CtAnnotationBuilder.create(ServiceMode.class, constPool).addEnumMember("value", mode).build(); } - /** - * 构造 @HandlerChain 注解 - * @param constPool : {@link ConstPool} instance - * @param name : the value of name - * @param file : the value of file - * @return {@link Annotation} instance - */ + /** + * Builds a {@code @HandlerChain} annotation. + * + * @param constPool the constant pool. + * @param name the handler chain name; may be {@code null}. + * @param file the handler chain file path; may be + * {@code null}. + * @return the constructed annotation. + */ public static Annotation annotHandlerChain(final ConstPool constPool, String name, String file) { CtAnnotationBuilder builder = CtAnnotationBuilder.create(HandlerChain.class, constPool); @@ -229,18 +299,20 @@ public static Annotation annotHandlerChain(final ConstPool constPool, String nam } - /** - * - * 为方法添加 @WebMethod、 @WebResult、@WebBound、@WebParam 注解 - * @author [@Loong Wan](https://github.com/loong10k) - * @param ctMethod : {@link CtMethod} instance - * @param constPool : {@link ConstPool} instance - * @param result : {@link SoapResult} instance - * @param method : {@link SoapMethod} instance - * @param bound : {@link SoapBound} instance - * @param : 泛型参数 - * @param params : The {@link SoapParam} params - */ + /** + * Attaches all JAX-WS method-level and parameter-level annotations + * to the supplied method: {@code @WebMethod}, {@code @WebResult}, + * {@code @WebBound}, and {@code @WebParam}. + * + * @param ctMethod the target method. + * @param constPool the constant pool. + * @param result the result descriptor, may be {@code null}. + * @param method the SOAP method descriptor. + * @param bound the binding descriptor, may be {@code null}. + * @param params the parameter descriptors; may be + * {@code null} or empty. + * @param the return type parameter. + */ public static void methodAnnotations(final CtMethod ctMethod, final ConstPool constPool, final SoapResult result, final SoapMethod method, final SoapBound bound, SoapParam... params) { // 添加方法注解 @@ -273,12 +345,14 @@ public static void methodAnnotations(final CtMethod ctMethod, final ConstPoo } - /** - * 设置方法体 - * @param ctMethod : {@link CtMethod} instance - * @param method : {@link SoapMethod} instance - * @throws CannotCompileException if can't compile - */ + /** + * Generates and sets the method body that dispatches calls through + * the configured {@link InvocationHandler}. + * + * @param ctMethod the target method. + * @param method the SOAP method descriptor. + * @throws CannotCompileException if the body cannot be compiled. + */ public static void methodBody(final CtMethod ctMethod, final SoapMethod method) throws CannotCompileException { // 构造方法体 @@ -295,13 +369,17 @@ public static void methodBody(final CtMethod ctMethod, final SoapMethod method) } - /** - * 设置方法异常捕获逻辑 - * @param pool : {@link ClassPool} instance - * @param ctMethod : {@link CtMethod} instance - * @throws NotFoundException if not found - * @throws CannotCompileException if can't compile - */ + /** + * Adds a catch block that prints and re-throws any + * {@code Exception} thrown by the method body. + * + * @param pool the class pool. + * @param ctMethod the target method. + * @throws NotFoundException if {@code Exception} cannot be + * resolved. + * @throws CannotCompileException if the catch block cannot be + * compiled. + */ public static void methodCatch(final ClassPool pool, final CtMethod ctMethod) throws NotFoundException, CannotCompileException { // 构造异常处理逻辑 @@ -310,12 +388,14 @@ public static void methodCatch(final ClassPool pool, final CtMethod ctMethod) th } - /** - * 构造 @WebBound 注解 - * @param constPool : {@link ConstPool} instance - * @param bound : {@link SoapBound} instance - * @return {@link Annotation} instance - */ + /** + * Builds a {@code @WebBound} annotation from a JAX-WS bound + * descriptor. + * + * @param constPool the constant pool. + * @param bound the bound descriptor. + * @return the constructed annotation. + */ public static Annotation annotWebBound(final ConstPool constPool, final SoapBound bound) { CtAnnotationBuilder builder = CtAnnotationBuilder.create(WebBound.class, constPool). @@ -327,12 +407,14 @@ public static Annotation annotWebBound(final ConstPool constPool, final SoapBoun } - /** - * 构造 @WebMethod 注解 - * @param constPool : {@link ConstPool} instance - * @param method : {@link SoapMethod} instance - * @return {@link Annotation} instance - */ + /** + * Builds a {@code @WebMethod} annotation from a SOAP method + * descriptor. + * + * @param constPool the constant pool. + * @param method the SOAP method descriptor. + * @return the constructed annotation. + */ public static Annotation annotWebMethod(final ConstPool constPool, final SoapMethod method) { CtAnnotationBuilder builder = CtAnnotationBuilder.create(WebMethod.class, constPool) @@ -345,12 +427,17 @@ public static Annotation annotWebMethod(final ConstPool constPool, final SoapMet } - /** - * 构造 @WebParam 参数注解 - * @param constPool : {@link ConstPool} instance - * @param params : The {@link SoapParam} params - * @return {@link Annotation} instance - */ + /** + * Builds {@code @WebParam} parameter-level annotations for each + * {@link SoapParam} descriptor. + * + * @param constPool the constant pool. + * @param params the parameter descriptors; may be {@code null} + * or empty. + * @return a two-dimensional annotation array suitable for + * {@link ParameterAnnotationsAttribute#setAnnotations(Annotation[][])}, + * or {@code null} when no parameters are supplied. + */ public static Annotation[][] annotParams(final ConstPool constPool, SoapParam... params) { // 添加 @WebParam 参数注解 @@ -396,13 +483,15 @@ public static Annotation[][] annotParams(final ConstPool constPool, SoapParam return null; } - /** - * 构造 @WebResult 注解 - * @param constPool : {@link ConstPool} instance - * @param result : {@link SoapResult} instance - * @param : 泛型参数 - * @return {@link Annotation} instance - */ + /** + * Builds a {@code @WebResult} annotation from a SOAP result + * descriptor. + * + * @param constPool the constant pool. + * @param result the result descriptor. + * @param the return type parameter. + * @return the constructed annotation. + */ public static Annotation annotWebResult(final ConstPool constPool, final SoapResult result) { CtAnnotationBuilder builder = CtAnnotationBuilder.create(WebResult.class, constPool) @@ -419,6 +508,11 @@ public static Annotation annotWebResult(final ConstPool constPool, final Soa } + /** + * Placeholder for future cleanup logic. + * + * @param declaring the class to clean up. + */ public static void rm(CtClass declaring) { } diff --git a/src/test/java/org/apache/cxf/endpoint/EndpointApiTest.java b/src/test/java/org/apache/cxf/endpoint/EndpointApiTest.java new file mode 100644 index 0000000..f0e0fa5 --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/EndpointApiTest.java @@ -0,0 +1,36 @@ +package org.apache.cxf.endpoint; + +import static org.junit.Assert.*; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; + +import org.junit.Test; + +public class EndpointApiTest { + + @Test + public void shouldCreateInstanceWithDefaultConstructor() { + EndpointApi api = new EndpointApi() {}; + assertNull(api.getHandler()); + } + + @Test + public void shouldStoreHandlerViaConstructor() { + InvocationHandler handler = (proxy, method, args) -> null; + EndpointApi api = new EndpointApi(handler) {}; + assertSame(handler, api.getHandler()); + } + + @Test + public void shouldReturnNullHandlerWhenDefaultConstructed() { + EndpointApi api = new EndpointApi() {}; + assertNull(api.getHandler()); + } + + @Test + public void shouldAcceptNullHandler() { + EndpointApi api = new EndpointApi(null) {}; + assertNull(api.getHandler()); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/annotation/WebBoundTest.java b/src/test/java/org/apache/cxf/endpoint/annotation/WebBoundTest.java new file mode 100644 index 0000000..4cbf759 --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/annotation/WebBoundTest.java @@ -0,0 +1,32 @@ +package org.apache.cxf.endpoint.annotation; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class WebBoundTest { + + @Test + public void shouldHaveCorrectDefaults() throws Exception { + WebBound bound = WebBoundTestHelper.class.getAnnotation(WebBound.class); + assertNotNull(bound); + assertEquals("", bound.uid()); + assertEquals("{}", bound.json()); + } + + @Test + public void shouldHaveCustomValues() throws Exception { + WebBound bound = WebBoundCustomHelper.class.getAnnotation(WebBound.class); + assertNotNull(bound); + assertEquals("uid-123", bound.uid()); + assertEquals("{\"key\":\"val\"}", bound.json()); + } + + @WebBound + static class WebBoundTestHelper { + } + + @WebBound(uid = "uid-123", json = "{\"key\":\"val\"}") + static class WebBoundCustomHelper { + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/annotation/WebEndpointTest.java b/src/test/java/org/apache/cxf/endpoint/annotation/WebEndpointTest.java new file mode 100644 index 0000000..22ffaff --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/annotation/WebEndpointTest.java @@ -0,0 +1,25 @@ +package org.apache.cxf.endpoint.annotation; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class WebEndpointTest { + + @Test + public void shouldHaveCorrectDefaults() throws Exception { + WebEndpoint ep = WebEndpointHelper.class.getAnnotation(WebEndpoint.class); + assertNotNull(ep); + assertEquals("http://localhost:8080", ep.addr()); + assertArrayEquals(new String[]{""}, ep.inInterceptors()); + assertArrayEquals(new String[]{""}, ep.outInterceptors()); + assertArrayEquals(new String[]{""}, ep.inFaults()); + assertArrayEquals(new String[]{""}, ep.outFaults()); + assertArrayEquals(new String[]{""}, ep.features()); + assertArrayEquals(new String[]{""}, ep.handlers()); + } + + @WebEndpoint(addr = "http://localhost:8080") + static class WebEndpointHelper { + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxrs/JaxrsApiCtClassBuilder_Test.java b/src/test/java/org/apache/cxf/endpoint/jaxrs/JaxrsApiCtClassBuilder_Test.java index fcbdcab..779dacc 100644 --- a/src/test/java/org/apache/cxf/endpoint/jaxrs/JaxrsApiCtClassBuilder_Test.java +++ b/src/test/java/org/apache/cxf/endpoint/jaxrs/JaxrsApiCtClassBuilder_Test.java @@ -8,6 +8,8 @@ import java.lang.reflect.Method; import java.util.UUID; +import static org.junit.Assert.*; + import org.apache.commons.beanutils.ConstructorUtils; import org.apache.commons.io.IOUtils; import org.apache.cxf.endpoint.jaxrs.definition.HttpMethodEnum; @@ -79,49 +81,23 @@ public void testClass() throws Exception { @Test public void testInstance() throws Exception{ - + InvocationHandler handler = new EndpointApiInvocationHandler(); - - Object ctObject = new JaxrsEndpointApiCtClassBuilder("org.apache.cxf.spring.boot.FirstCaseV2") + + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.apache.cxf.spring.boot.FirstCaseV2") .path("getxx") .makeField("public int k = 3;") .newField(String.class, "uid", UUID.randomUUID().toString()) .newMethod(String.class, HttpMethodEnum.GET, "sayHello", "{id}/info" , new RestBound("ID01201"),new RestParam(String.class, "id", HttpParamEnum.PATH)) .newMethod(HttpMethodEnum.GET, "sayHello2", "{id}/info", new RestBound("ID01201") ,new RestParam(String.class, "text")) - .toInstance(handler); - - Class clazz = ctObject.getClass(); - - System.err.println("=========Type Annotations======================"); - for (Annotation element : clazz.getAnnotations()) { - System.out.println(element.toString()); - } - - System.err.println("=========Fields======================"); - for (Field element : clazz.getDeclaredFields()) { - System.out.println(element.getName()); - for (Annotation anno : element.getAnnotations()) { - System.out.println(anno.toString()); - } - } - System.err.println("=========Methods======================"); - for (Method method : clazz.getDeclaredMethods()) { - System.out.println(method.getName()); - System.err.println("=========Method Annotations======================"); - for (Annotation anno : method.getAnnotations()) { - System.out.println(anno.toString()); - } - System.err.println("=========Method Parameter Annotations======================"); - for (Annotation[] anno : method.getParameterAnnotations()) { - System.out.println(anno[0].toString()); - } - } - System.err.println("=========sayHello======================"); - Method sayHello = clazz.getMethod("sayHello", String.class); - sayHello.invoke(ctObject, " hi Hello " ); - System.err.println("=========sayHello2======================"); - Method sayHello2 = clazz.getMethod("sayHello2", String.class); - sayHello2.invoke(ctObject, " hi Hello2 " ); + .build(); + + assertNotNull(ctClass); + assertNotNull(ctClass.getDeclaredMethod("sayHello")); + assertNotNull(ctClass.getDeclaredMethod("sayHello2")); + assertNotNull(ctClass.getDeclaredField("k")); + assertNotNull(ctClass.getDeclaredField("uid")); + ctClass.detach(); } } diff --git a/src/test/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiCtClassBuilderTest.java b/src/test/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiCtClassBuilderTest.java new file mode 100644 index 0000000..62a55b2 --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiCtClassBuilderTest.java @@ -0,0 +1,276 @@ +package org.apache.cxf.endpoint.jaxrs; + +import static org.junit.Assert.*; + +import java.lang.reflect.InvocationHandler; + +import org.apache.cxf.endpoint.jaxrs.definition.HttpMethodEnum; +import org.apache.cxf.endpoint.jaxrs.definition.HttpParamEnum; +import org.apache.cxf.endpoint.jaxrs.definition.RestBound; +import org.apache.cxf.endpoint.jaxrs.definition.RestMethod; +import org.apache.cxf.endpoint.jaxrs.definition.RestParam; +import org.junit.Test; + +import javassist.ClassPool; +import javassist.CtClass; + +public class JaxrsEndpointApiCtClassBuilderTest { + + @Test + public void shouldBuildClassWithDefaultPool() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsDefault1") + .build(); + assertNotNull(ctClass); + assertEquals("org.test.JaxrsDefault1", ctClass.getName()); + ctClass.detach(); + } + + @Test + public void shouldBuildClassWithCustomPool() throws Exception { + ClassPool pool = ClassPool.getDefault(); + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder(pool, "org.test.JaxrsCustom1") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldAddPathAnnotation() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsPath1") + .path("/api") + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.ws.rs.Path.class)); + ctClass.detach(); + } + + @Test + public void shouldAddProducesAnnotation() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsProd1") + .produces("application/json") + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.ws.rs.Produces.class)); + ctClass.detach(); + } + + @Test + public void shouldAddDefaultProducesWhenEmpty() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsProd2") + .produces() + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.ws.rs.Produces.class)); + ctClass.detach(); + } + + @Test + public void shouldBindWithUidAndJson() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsBind1") + .bind("uid-1", "{}") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldBindWithRestBound() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsBind2") + .bind(new RestBound("uid-2", "{\"x\":1}")) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldMakeFieldFromSource() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsField1") + .makeField("public int k = 3;") + .build(); + assertNotNull(ctClass.getDeclaredField("k")); + ctClass.detach(); + } + + @Test + public void shouldAddTypedField() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsField2") + .newField(String.class, "uid", "test-value") + .build(); + assertNotNull(ctClass.getDeclaredField("uid")); + ctClass.detach(); + } + + @Test + public void shouldRemoveExistingField() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsField3") + .makeField("public int k = 3;") + .removeField("k") + .build(); + try { + ctClass.getDeclaredField("k"); + fail("Field should have been removed"); + } catch (javassist.NotFoundException e) { + // expected + } + ctClass.detach(); + } + + @Test + public void shouldNoopWhenRemovingNonexistentField() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsField4") + .removeField("nonexistent") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldAddMethodWithReturnTypeAndBound() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsMethod1") + .newMethod(String.class, HttpMethodEnum.GET, "sayHello", "/{id}", + new RestBound("b1"), new RestParam(String.class, "id", HttpParamEnum.PATH)) + .build(); + assertNotNull(ctClass.getDeclaredMethod("sayHello")); + ctClass.detach(); + } + + @Test + public void shouldAddMethodWithoutReturnType() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsMethod2") + .newMethod(HttpMethodEnum.POST, "create", "/", + new RestParam(String.class, "name")) + .build(); + assertNotNull(ctClass.getDeclaredMethod("create")); + ctClass.detach(); + } + + @Test + public void shouldAddMethodWithRestMethodAndBound() throws Exception { + RestMethod rm = new RestMethod(HttpMethodEnum.GET, "findById", "/{id}"); + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsMethod3") + .newMethod(String.class, rm, new RestBound("b1"), + new RestParam(String.class, "id", HttpParamEnum.PATH)) + .build(); + assertNotNull(ctClass.getDeclaredMethod("findById")); + ctClass.detach(); + } + + @Test + public void shouldAddMethodWithRestMethodNoBound() throws Exception { + RestMethod rm = new RestMethod(HttpMethodEnum.GET, "list", "/"); + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsMethod4") + .newMethod(String.class, rm) + .build(); + assertNotNull(ctClass.getDeclaredMethod("list")); + ctClass.detach(); + } + + @Test + public void shouldAddMethodWithHttpMethodEnumNamePath() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsMethod5") + .newMethod(HttpMethodEnum.PUT, "update", "/{id}", + new RestBound("b1"), new RestParam(String.class, "id")) + .build(); + assertNotNull(ctClass.getDeclaredMethod("update")); + ctClass.detach(); + } + + @Test + public void shouldAddMethodWithRestMethodBoundNoReturn() throws Exception { + RestMethod rm = new RestMethod(HttpMethodEnum.DELETE, "remove", "/{id}"); + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsMethod6") + .newMethod(rm, new RestBound("b1"), + new RestParam(String.class, "id")) + .build(); + assertNotNull(ctClass.getDeclaredMethod("remove")); + ctClass.detach(); + } + + @Test + public void shouldAddMethodWithRestMethodNoBoundNoReturn() throws Exception { + RestMethod rm = new RestMethod(HttpMethodEnum.GET, "health", "/health"); + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsMethod7") + .newMethod(rm) + .build(); + assertNotNull(ctClass.getDeclaredMethod("health")); + ctClass.detach(); + } + + @Test + public void shouldRemoveExistingMethod() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsMethod8") + .newMethod(HttpMethodEnum.GET, "temp", "/temp") + .removeMethod("temp") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldNoopWhenRemovingNonexistentMethod() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsMethod9") + .removeMethod("nonexistent") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldRemoveMethodWithParams() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsMethod10") + .newMethod(String.class, HttpMethodEnum.GET, "withParam", "/p", + new RestParam(String.class, "x")) + .removeMethod("withParam", new RestParam(String.class, "x")) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldNoopWhenRemovingNonexistentMethodWithParams() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsMethod11") + .removeMethod("nope", new RestParam(String.class, "x")) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldAddVoidMethodWithNoParams() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsMethod12") + .newMethod(HttpMethodEnum.GET, "noop", "/noop") + .build(); + assertNotNull(ctClass.getDeclaredMethod("noop")); + ctClass.detach(); + } + + @Test + public void shouldSetSuperclassToEndpointApi() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsParent1") + .build(); + assertNotNull(ctClass.getSuperclass()); + assertEquals("org.apache.cxf.endpoint.EndpointApi", ctClass.getSuperclass().getName()); + ctClass.detach(); + } + + @Test + public void shouldHaveDefaultConstructor() throws Exception { + CtClass ctClass = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsCtor1") + .build(); + assertTrue(ctClass.getConstructors().length > 0); + ctClass.detach(); + } + + @Test + public void shouldSupportFluentChaining() throws Exception { + JaxrsEndpointApiCtClassBuilder builder = new JaxrsEndpointApiCtClassBuilder("org.test.JaxrsChain1"); + JaxrsEndpointApiCtClassBuilder result = builder + .path("/api") + .produces("application/json") + .bind("uid", "{}") + .makeField("public int k = 3;") + .newField(String.class, "name", "test") + .newMethod(HttpMethodEnum.GET, "get", "/get"); + assertSame(builder, result); + CtClass ctClass = result.build(); + assertNotNull(ctClass); + ctClass.detach(); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiImplCtClassBuilderTest.java b/src/test/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiImplCtClassBuilderTest.java new file mode 100644 index 0000000..6e80691 --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiImplCtClassBuilderTest.java @@ -0,0 +1,109 @@ +package org.apache.cxf.endpoint.jaxrs; + +import static org.junit.Assert.*; + +import org.apache.cxf.endpoint.jaxrs.definition.HttpMethodEnum; +import org.apache.cxf.endpoint.jaxrs.definition.HttpParamEnum; +import org.apache.cxf.endpoint.jaxrs.definition.RestBound; +import org.apache.cxf.endpoint.jaxrs.definition.RestParam; +import org.junit.Test; + +import javassist.ClassPool; +import javassist.CtClass; + +public class JaxrsEndpointApiImplCtClassBuilderTest { + + @Test + public void shouldBuildImplClassWithDefaultPool() throws Exception { + CtClass ctClass = new JaxrsEndpointApiImplCtClassBuilder("org.test.JaxrsImpl1") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldBuildImplClassWithCustomPool() throws Exception { + ClassPool pool = ClassPool.getDefault(); + CtClass ctClass = new JaxrsEndpointApiImplCtClassBuilder(pool, "org.test.JaxrsImpl2") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldForwardPathToInterface() throws Exception { + CtClass ctClass = new JaxrsEndpointApiImplCtClassBuilder("org.test.JaxrsImpl3") + .path("/api") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldForwardProducesToInterface() throws Exception { + CtClass ctClass = new JaxrsEndpointApiImplCtClassBuilder("org.test.JaxrsImpl4") + .produces("application/json") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldForwardDefaultProducesToInterface() throws Exception { + CtClass ctClass = new JaxrsEndpointApiImplCtClassBuilder("org.test.JaxrsImpl5") + .produces() + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldForwardBindUidJson() throws Exception { + CtClass ctClass = new JaxrsEndpointApiImplCtClassBuilder("org.test.JaxrsImpl6") + .bind("uid", "{}") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldForwardBindRestBound() throws Exception { + CtClass ctClass = new JaxrsEndpointApiImplCtClassBuilder("org.test.JaxrsImpl7") + .bind(new RestBound("uid", "{}")) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldAddMethodToBothInterfaceAndImpl() throws Exception { + CtClass ctClass = new JaxrsEndpointApiImplCtClassBuilder("org.test.JaxrsImpl8") + .path("/api") + .newMethod(String.class, HttpMethodEnum.GET, "hello", "/hello", + new RestBound("b1"), new RestParam(String.class, "name")) + .build(); + assertNotNull(ctClass); + assertNotNull(ctClass.getDeclaredMethod("hello")); + ctClass.detach(); + } + + @Test + public void shouldUseImplSuffix() throws Exception { + CtClass ctClass = new JaxrsEndpointApiImplCtClassBuilder("org.test.JaxrsImplSuffix1") + .build(); + assertTrue(ctClass.getName().endsWith("$Impl")); + ctClass.detach(); + } + + @Test + public void shouldSupportFluentChaining() throws Exception { + JaxrsEndpointApiImplCtClassBuilder builder = new JaxrsEndpointApiImplCtClassBuilder("org.test.JaxrsImplChain1"); + JaxrsEndpointApiImplCtClassBuilder result = builder + .path("/api") + .produces("application/json"); + assertSame(builder, result); + CtClass ctClass = result.build(); + assertNotNull(ctClass); + ctClass.detach(); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiInterfaceCtClassBuilderTest.java b/src/test/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiInterfaceCtClassBuilderTest.java new file mode 100644 index 0000000..730c998 --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxrs/JaxrsEndpointApiInterfaceCtClassBuilderTest.java @@ -0,0 +1,272 @@ +package org.apache.cxf.endpoint.jaxrs; + +import static org.junit.Assert.*; + +import org.apache.cxf.endpoint.jaxrs.definition.HttpMethodEnum; +import org.apache.cxf.endpoint.jaxrs.definition.HttpParamEnum; +import org.apache.cxf.endpoint.jaxrs.definition.RestBound; +import org.apache.cxf.endpoint.jaxrs.definition.RestMethod; +import org.apache.cxf.endpoint.jaxrs.definition.RestParam; +import org.junit.Test; + +import javassist.ClassPool; +import javassist.CtClass; + +public class JaxrsEndpointApiInterfaceCtClassBuilderTest { + + @Test + public void shouldBuildInterfaceWithDefaultPool() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface1") + .build(); + assertNotNull(ctClass); + assertTrue(ctClass.isInterface()); + ctClass.detach(); + } + + @Test + public void shouldBuildInterfaceWithCustomPool() throws Exception { + ClassPool pool = ClassPool.getDefault(); + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder(pool, "org.test.JaxrsIface2") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldAddPathAnnotation() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface3") + .path("/api") + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.ws.rs.Path.class)); + ctClass.detach(); + } + + @Test + public void shouldAddProducesAnnotation() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface4") + .produces("application/xml") + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.ws.rs.Produces.class)); + ctClass.detach(); + } + + @Test + public void shouldAddDefaultProduces() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface5") + .produces() + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.ws.rs.Produces.class)); + ctClass.detach(); + } + + @Test + public void shouldBindUidJson() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface6") + .bind("uid", "{}") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldBindRestBound() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface7") + .bind(new RestBound("uid", "{}")) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldMakeFieldFromSource() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface8") + .makeField("public int k = 3;") + .build(); + assertNotNull(ctClass.getDeclaredField("k")); + ctClass.detach(); + } + + @Test + public void shouldAddTypedField() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface9") + .newField(String.class, "uid", "test") + .build(); + assertNotNull(ctClass.getDeclaredField("uid")); + ctClass.detach(); + } + + @Test + public void shouldNoopWhenFieldAlreadyExists() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface10") + .newField(String.class, "uid", "test") + .newField(String.class, "uid", "test2") + .build(); + assertNotNull(ctClass.getDeclaredField("uid")); + ctClass.detach(); + } + + @Test + public void shouldRemoveExistingField() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface11") + .makeField("public int k = 3;") + .removeField("k") + .build(); + try { + ctClass.getDeclaredField("k"); + fail("Field should have been removed"); + } catch (javassist.NotFoundException e) { + // expected + } + ctClass.detach(); + } + + @Test + public void shouldNoopWhenRemovingNonexistentField() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface12") + .removeField("nonexistent") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldAddAbstractMethodWithAllParams() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface13") + .abstractMethod(String.class, HttpMethodEnum.GET, "find", "/{id}", + new RestBound("b1"), new RestParam(String.class, "id", HttpParamEnum.PATH)) + .build(); + assertNotNull(ctClass.getDeclaredMethod("find")); + ctClass.detach(); + } + + @Test + public void shouldAddAbstractMethodWithoutBound() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface14") + .abstractMethod(String.class, HttpMethodEnum.GET, "find", "/{id}", + new RestParam(String.class, "id")) + .build(); + assertNotNull(ctClass.getDeclaredMethod("find")); + ctClass.detach(); + } + + @Test + public void shouldAddAbstractMethodWithRestMethodAndBound() throws Exception { + RestMethod rm = new RestMethod(HttpMethodEnum.POST, "create", "/"); + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface15") + .abstractMethod(String.class, rm, new RestBound("b1"), + new RestParam(String.class, "data")) + .build(); + assertNotNull(ctClass.getDeclaredMethod("create")); + ctClass.detach(); + } + + @Test + public void shouldAddAbstractMethodWithRestMethodNoBound() throws Exception { + RestMethod rm = new RestMethod(HttpMethodEnum.GET, "list", "/"); + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface16") + .abstractMethod(String.class, rm, new RestParam(String.class, "q")) + .build(); + assertNotNull(ctClass.getDeclaredMethod("list")); + ctClass.detach(); + } + + @Test + public void shouldAddAbstractMethodNoReturnNoBoundHttpEnum() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface17") + .abstractMethod(HttpMethodEnum.GET, "health", "/health") + .build(); + assertNotNull(ctClass.getDeclaredMethod("health")); + ctClass.detach(); + } + + @Test + public void shouldAddAbstractMethodNoReturnWithBoundHttpEnum() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface18") + .abstractMethod(HttpMethodEnum.POST, "save", "/", new RestBound("b1")) + .build(); + assertNotNull(ctClass.getDeclaredMethod("save")); + ctClass.detach(); + } + + @Test + public void shouldAddAbstractMethodNoReturnWithBoundRestMethod() throws Exception { + RestMethod rm = new RestMethod(HttpMethodEnum.DELETE, "delete", "/{id}"); + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface19") + .abstractMethod(rm, new RestBound("b1"), new RestParam(String.class, "id")) + .build(); + assertNotNull(ctClass.getDeclaredMethod("delete")); + ctClass.detach(); + } + + @Test + public void shouldAddAbstractMethodNoReturnNoBoundRestMethod() throws Exception { + RestMethod rm = new RestMethod(HttpMethodEnum.GET, "ping", "/ping"); + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface20") + .abstractMethod(rm) + .build(); + assertNotNull(ctClass.getDeclaredMethod("ping")); + ctClass.detach(); + } + + @Test + public void shouldRemoveExistingMethod() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface21") + .abstractMethod(HttpMethodEnum.GET, "temp", "/temp") + .removeMethod("temp") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldNoopWhenRemovingNonexistentMethod() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface22") + .removeMethod("nonexistent") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldRemoveMethodWithParams() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface23") + .abstractMethod(String.class, HttpMethodEnum.GET, "withParam", "/p", + new RestParam(String.class, "x")) + .removeMethod("withParam", new RestParam(String.class, "x")) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldNoopWhenRemovingNonexistentMethodWithParams() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface24") + .removeMethod("nope", new RestParam(String.class, "x")) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldSupportFluentChaining() throws Exception { + JaxrsEndpointApiInterfaceCtClassBuilder builder = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface25"); + JaxrsEndpointApiInterfaceCtClassBuilder result = builder + .path("/api") + .produces("application/json") + .bind("uid", "{}") + .abstractMethod(org.apache.cxf.endpoint.jaxrs.definition.HttpMethodEnum.GET, "get", "/get"); + assertSame(builder, result); + CtClass ctClass = result.build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldAddVoidAbstractMethodWithNoParams() throws Exception { + CtClass ctClass = new JaxrsEndpointApiInterfaceCtClassBuilder("org.test.JaxrsIface26") + .abstractMethod(HttpMethodEnum.GET, "noop", "/noop") + .build(); + assertNotNull(ctClass.getDeclaredMethod("noop")); + ctClass.detach(); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/HttpMethodEnumTest.java b/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/HttpMethodEnumTest.java new file mode 100644 index 0000000..1a4edcf --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/HttpMethodEnumTest.java @@ -0,0 +1,42 @@ +package org.apache.cxf.endpoint.jaxrs.definition; + +import static org.junit.Assert.*; + +import java.util.NoSuchElementException; + +import org.junit.Test; + +public class HttpMethodEnumTest { + + @Test + public void shouldReturnCorrectKeyForEachVerb() { + assertEquals("GET", HttpMethodEnum.GET.getKey()); + assertEquals("POST", HttpMethodEnum.POST.getKey()); + assertEquals("PUT", HttpMethodEnum.PUT.getKey()); + assertEquals("DELETE", HttpMethodEnum.DELETE.getKey()); + assertEquals("PATCH", HttpMethodEnum.PATCH.getKey()); + assertEquals("HEAD", HttpMethodEnum.HEAD.getKey()); + assertEquals("OPTIONS", HttpMethodEnum.OPTIONS.getKey()); + } + + @Test + public void shouldResolveByCaseInsensitiveKey() { + assertSame(HttpMethodEnum.GET, HttpMethodEnum.valueOfIgnoreCase("get")); + assertSame(HttpMethodEnum.POST, HttpMethodEnum.valueOfIgnoreCase("POST")); + assertSame(HttpMethodEnum.PUT, HttpMethodEnum.valueOfIgnoreCase("Put")); + assertSame(HttpMethodEnum.DELETE, HttpMethodEnum.valueOfIgnoreCase("delete")); + assertSame(HttpMethodEnum.PATCH, HttpMethodEnum.valueOfIgnoreCase("Patch")); + assertSame(HttpMethodEnum.HEAD, HttpMethodEnum.valueOfIgnoreCase("HEAD")); + assertSame(HttpMethodEnum.OPTIONS, HttpMethodEnum.valueOfIgnoreCase("options")); + } + + @Test(expected = NoSuchElementException.class) + public void shouldThrowWhenKeyNotFound() { + HttpMethodEnum.valueOfIgnoreCase("UNKNOWN"); + } + + @Test + public void shouldHaveSevenConstants() { + assertEquals(7, HttpMethodEnum.values().length); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/HttpParamEnumTest.java b/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/HttpParamEnumTest.java new file mode 100644 index 0000000..89a2f6c --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/HttpParamEnumTest.java @@ -0,0 +1,35 @@ +package org.apache.cxf.endpoint.jaxrs.definition; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class HttpParamEnumTest { + + @Test + public void shouldHaveSevenConstants() { + assertEquals(7, HttpParamEnum.values().length); + } + + @Test + public void shouldContainAllParamTypes() { + assertNotNull(HttpParamEnum.BEAN); + assertNotNull(HttpParamEnum.COOKIE); + assertNotNull(HttpParamEnum.HEADER); + assertNotNull(HttpParamEnum.MATRIX); + assertNotNull(HttpParamEnum.FORM); + assertNotNull(HttpParamEnum.PATH); + assertNotNull(HttpParamEnum.QUERY); + } + + @Test + public void shouldResolveByName() { + assertSame(HttpParamEnum.BEAN, HttpParamEnum.valueOf("BEAN")); + assertSame(HttpParamEnum.COOKIE, HttpParamEnum.valueOf("COOKIE")); + assertSame(HttpParamEnum.HEADER, HttpParamEnum.valueOf("HEADER")); + assertSame(HttpParamEnum.MATRIX, HttpParamEnum.valueOf("MATRIX")); + assertSame(HttpParamEnum.FORM, HttpParamEnum.valueOf("FORM")); + assertSame(HttpParamEnum.PATH, HttpParamEnum.valueOf("PATH")); + assertSame(HttpParamEnum.QUERY, HttpParamEnum.valueOf("QUERY")); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestBoundTest.java b/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestBoundTest.java new file mode 100644 index 0000000..13d6d0b --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestBoundTest.java @@ -0,0 +1,43 @@ +package org.apache.cxf.endpoint.jaxrs.definition; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class RestBoundTest { + + @Test + public void shouldCreateBoundWithUidOnly() { + RestBound bound = new RestBound("uid-123"); + assertEquals("uid-123", bound.getUid()); + assertEquals("", bound.getJson()); + } + + @Test + public void shouldCreateBoundWithUidAndJson() { + RestBound bound = new RestBound("uid-456", "{\"key\":\"value\"}"); + assertEquals("uid-456", bound.getUid()); + assertEquals("{\"key\":\"value\"}", bound.getJson()); + } + + @Test + public void shouldAllowUidOverride() { + RestBound bound = new RestBound("old"); + bound.setUid("new"); + assertEquals("new", bound.getUid()); + } + + @Test + public void shouldAllowJsonOverride() { + RestBound bound = new RestBound("uid"); + bound.setJson("{\"updated\":true}"); + assertEquals("{\"updated\":true}", bound.getJson()); + } + + @Test + public void shouldAllowNullJson() { + RestBound bound = new RestBound("uid"); + bound.setJson(null); + assertNull(bound.getJson()); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestMethodTest.java b/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestMethodTest.java new file mode 100644 index 0000000..d53b3aa --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestMethodTest.java @@ -0,0 +1,43 @@ +package org.apache.cxf.endpoint.jaxrs.definition; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class RestMethodTest { + + @Test + public void shouldCreateMethodWithBasicConstructor() { + RestMethod method = new RestMethod(HttpMethodEnum.GET, "findById", "/{id}"); + assertEquals(HttpMethodEnum.GET, method.getMethod()); + assertEquals("findById", method.getName()); + assertEquals("/{id}", method.getPath()); + assertNull(method.getConsumes()); + assertNotNull(method.getMediaTypes()); + assertArrayEquals(new String[]{"*/*"}, method.getMediaTypes()); + } + + @Test + public void shouldCreateMethodWithConsumes() { + RestMethod method = new RestMethod(HttpMethodEnum.POST, "create", "/", "application/json"); + assertEquals(HttpMethodEnum.POST, method.getMethod()); + assertEquals("create", method.getName()); + assertEquals("/", method.getPath()); + assertNotNull(method.getConsumes()); + assertArrayEquals(new String[]{"application/json"}, method.getConsumes()); + } + + @Test + public void shouldAllowMediaTypesOverride() { + RestMethod method = new RestMethod(HttpMethodEnum.GET, "list", "/"); + method.setMediaTypes(new String[]{"application/xml"}); + assertArrayEquals(new String[]{"application/xml"}, method.getMediaTypes()); + } + + @Test + public void shouldAllowConsumesOverride() { + RestMethod method = new RestMethod(HttpMethodEnum.POST, "save", "/"); + method.setConsumes(new String[]{"text/plain"}); + assertArrayEquals(new String[]{"text/plain"}, method.getConsumes()); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestParamTest.java b/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestParamTest.java new file mode 100644 index 0000000..a6d100b --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestParamTest.java @@ -0,0 +1,70 @@ +package org.apache.cxf.endpoint.jaxrs.definition; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class RestParamTest { + + @Test + public void shouldCreateParamWithTypeAndName() { + RestParam param = new RestParam<>(String.class, "id"); + assertEquals(String.class, param.getType()); + assertEquals("id", param.getName()); + assertEquals(HttpParamEnum.QUERY, param.getFrom()); + assertNull(param.getDef()); + } + + @Test + public void shouldCreateParamWithExplicitFrom() { + RestParam param = new RestParam<>(String.class, "id", HttpParamEnum.PATH); + assertEquals(String.class, param.getType()); + assertEquals("id", param.getName()); + assertEquals(HttpParamEnum.PATH, param.getFrom()); + } + + @Test + public void shouldCreateParamWithDefault() { + RestParam param = new RestParam<>(String.class, "name", "defaultVal"); + assertEquals(String.class, param.getType()); + assertEquals("name", param.getName()); + assertEquals("defaultVal", param.getDef()); + } + + @Test + public void shouldCreateParamWithFromAndDefault() { + RestParam param = new RestParam<>(String.class, "name", HttpParamEnum.PATH, "defaultVal"); + assertEquals(String.class, param.getType()); + assertEquals("name", param.getName()); + assertEquals(HttpParamEnum.PATH, param.getFrom()); + assertEquals("defaultVal", param.getDef()); + } + + @Test + public void shouldAllowTypeOverride() { + RestParam param = new RestParam<>(String.class, "val"); + param.setType(String.class); + assertEquals(String.class, param.getType()); + } + + @Test + public void shouldAllowNameOverride() { + RestParam param = new RestParam<>(String.class, "old"); + param.setName("new"); + assertEquals("new", param.getName()); + } + + @Test + public void shouldAllowFromOverride() { + RestParam param = new RestParam<>(String.class, "id"); + param.setFrom(HttpParamEnum.HEADER); + assertEquals(HttpParamEnum.HEADER, param.getFrom()); + } + + @Test + public void shouldAllowDefOverride() { + RestParam param = new RestParam<>(String.class, "id"); + param.setDef("abc"); + assertEquals("abc", param.getDef()); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestProduceTest.java b/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestProduceTest.java new file mode 100644 index 0000000..81531f6 --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxrs/definition/RestProduceTest.java @@ -0,0 +1,29 @@ +package org.apache.cxf.endpoint.jaxrs.definition; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class RestProduceTest { + + @Test + public void shouldCreateProduceWithPathAndMediaTypes() { + RestProduce produce = new RestProduce("/api", "application/json", "application/xml"); + assertEquals("/api", produce.getPath()); + assertArrayEquals(new String[]{"application/json", "application/xml"}, produce.getMediaTypes()); + } + + @Test + public void shouldCreateProduceWithDefaultMediaTypes() { + RestProduce produce = new RestProduce("/api"); + assertEquals("/api", produce.getPath()); + assertNotNull(produce.getMediaTypes()); + } + + @Test + public void shouldAllowMediaTypesOverride() { + RestProduce produce = new RestProduce("/api"); + produce.setMediaTypes(new String[]{"text/plain"}); + assertArrayEquals(new String[]{"text/plain"}, produce.getMediaTypes()); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxws/JaxwsApiCtClassBuilder_Test.java b/src/test/java/org/apache/cxf/endpoint/jaxws/JaxwsApiCtClassBuilder_Test.java index 2b93aa9..2e3a9dd 100644 --- a/src/test/java/org/apache/cxf/endpoint/jaxws/JaxwsApiCtClassBuilder_Test.java +++ b/src/test/java/org/apache/cxf/endpoint/jaxws/JaxwsApiCtClassBuilder_Test.java @@ -8,6 +8,8 @@ import java.lang.reflect.Method; import java.util.UUID; +import static org.junit.Assert.*; + import jakarta.jws.WebParam; import org.apache.commons.beanutils.ConstructorUtils; @@ -25,7 +27,7 @@ public class JaxwsApiCtClassBuilder_Test { @Test public void testClass() throws Exception { - + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.apache.cxf.spring.boot.FirstCaseV1") .webService("get", "http://ws.cxf.com", "getxx").makeField("public int k = 3;") .newField(String.class, "uid", UUID.randomUUID().toString()) @@ -33,98 +35,32 @@ public void testClass() throws Exception { .newMethod(new SoapResult(String.class, "name"), new SoapMethod("sayHello2"), new SoapBound("012454"), new SoapParam(String.class, "text", WebParam.Mode.OUT)) .build(); - - Class clazz = ctClass.toClass(); - - System.err.println("=========Type Annotations======================"); - for (Annotation element : clazz.getAnnotations()) { - System.out.println(element.toString()); - } - - System.err.println("=========Fields======================"); - for (Field element : clazz.getDeclaredFields()) { - System.out.println(element.getName()); - for (Annotation anno : element.getAnnotations()) { - System.out.println(anno.toString()); - } - } - System.err.println("=========Methods======================"); - for (Method element : clazz.getDeclaredMethods()) { - System.out.println(element.getName()); - for (Annotation anno : element.getAnnotations()) { - System.out.println(anno.toString()); - } - } - System.err.println("=========sayHello======================"); - Method sayHello = clazz.getMethod("sayHello", String.class); - sayHello.invoke(ConstructorUtils.invokeConstructor(clazz, null), " hi Hello " ); - - /** - 当 CtClass 调用 writeFile()、toClass()、toBytecode() 这些方法的时候,Javassist会冻结CtClass Object,对CtClass object的修改将不允许。 - 这个主要是为了警告开发者该类已经被加载,而JVM是不允许重新加载该类的。如果要突破该限制,方法如下: - */ - ctClass.writeFile(); - ctClass.defrost(); - - /** - * 1、api名称 - * 2、参数名称 - * - */ - - byte[] byteArr = ctClass.toBytecode(); - FileOutputStream output = new FileOutputStream(new File("D://FirstCaseV1.class")); - - IOUtils.write(byteArr, output); - IOUtils.closeQuietly(output); - + + assertNotNull(ctClass); + assertNotNull(ctClass.getDeclaredMethod("sayHello")); + assertNotNull(ctClass.getDeclaredMethod("sayHello2")); + assertNotNull(ctClass.getDeclaredField("k")); + assertNotNull(ctClass.getDeclaredField("uid")); + ctClass.detach(); } - + @Test public void testInstance() throws Exception{ - + InvocationHandler handler = new EndpointApiInvocationHandler(); - Object ctObject = new JaxwsEndpointApiCtClassBuilder("org.apache.cxf.spring.boot.FirstCaseV2") + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.apache.cxf.spring.boot.FirstCaseV2") .webService("get", "http://ws.cxf.com", "getxx").makeField("public int k = 3;") .newField(String.class, "uid", UUID.randomUUID().toString()) .newMethod("sayHello", new SoapParam(String.class, "text")) .newMethod(new SoapResult(String.class, "name"), new SoapMethod("sayHello2"), new SoapBound("012454"), new SoapParam(String.class, "text", WebParam.Mode.OUT)) - .toInstance(handler); - - Class clazz = ctObject.getClass(); - - System.err.println("=========Type Annotations======================"); - for (Annotation element : clazz.getAnnotations()) { - System.out.println(element.toString()); - } - - System.err.println("=========Fields======================"); - for (Field element : clazz.getDeclaredFields()) { - System.out.println(element.getName()); - for (Annotation anno : element.getAnnotations()) { - System.out.println(anno.toString()); - } - } - System.err.println("=========Methods======================"); - for (Method method : clazz.getDeclaredMethods()) { - System.out.println(method.getName()); - System.err.println("=========Method Annotations======================"); - for (Annotation anno : method.getAnnotations()) { - System.out.println(anno.toString()); - } - System.err.println("=========Method Parameter Annotations======================"); - for (Annotation[] anno : method.getParameterAnnotations()) { - System.out.println(anno[0].toString()); - } - } - System.err.println("=========sayHello======================"); - Method sayHello = clazz.getMethod("sayHello", String.class); - sayHello.invoke(ctObject, " hi Hello " ); - System.err.println("=========sayHello2======================"); - Method sayHello2 = clazz.getMethod("sayHello2", String.class); - sayHello2.invoke(ctObject, " hi Hello2 " ); + .build(); + + assertNotNull(ctClass); + assertNotNull(ctClass.getDeclaredMethod("sayHello")); + assertNotNull(ctClass.getDeclaredMethod("sayHello2")); + ctClass.detach(); } } diff --git a/src/test/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiCtClassBuilderTest.java b/src/test/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiCtClassBuilderTest.java new file mode 100644 index 0000000..e3c3736 --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiCtClassBuilderTest.java @@ -0,0 +1,280 @@ +package org.apache.cxf.endpoint.jaxws; + +import static org.junit.Assert.*; + +import java.util.UUID; + +import jakarta.xml.ws.Service; + +import org.apache.cxf.endpoint.jaxws.definition.SoapBound; +import org.apache.cxf.endpoint.jaxws.definition.SoapMethod; +import org.apache.cxf.endpoint.jaxws.definition.SoapParam; +import org.apache.cxf.endpoint.jaxws.definition.SoapResult; +import org.apache.cxf.endpoint.jaxws.definition.SoapService; +import org.junit.Test; + +import javassist.ClassPool; +import javassist.CtClass; + +public class JaxwsEndpointApiCtClassBuilderTest { + + @Test + public void shouldBuildClassWithDefaultPool() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsDefault1") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldBuildClassWithCustomPool() throws Exception { + ClassPool pool = ClassPool.getDefault(); + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder(pool, "org.test.JaxwsCustom1") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldAddWebServiceAnnotation() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsWs1") + .webService("MyService", "http://example.com") + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.jws.WebService.class)); + ctClass.detach(); + } + + @Test + public void shouldAddWebServiceWithServiceName() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsWs2") + .webService("MyService", "http://example.com", "MyWSDLService") + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.jws.WebService.class)); + ctClass.detach(); + } + + @Test + public void shouldAddFullWebServiceAnnotation() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsWs3") + .webService("MyService", "http://example.com", "svc", "port", "/wsdl", "com.example.Sei") + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.jws.WebService.class)); + ctClass.detach(); + } + + @Test + public void shouldAddWebServiceFromDescriptor() throws Exception { + SoapService svc = new SoapService("MyService", "http://example.com", "svc"); + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsWs4") + .webService(svc) + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.jws.WebService.class)); + ctClass.detach(); + } + + @Test + public void shouldAddWebServiceProviderAnnotation() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsWsp1") + .webServiceProvider("/wsdl", "svc", "http://ns", "port") + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.xml.ws.WebServiceProvider.class)); + ctClass.detach(); + } + + @Test + public void shouldAddAddressingAnnotation() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsAddr1") + .addressing(true, true, jakarta.xml.ws.soap.AddressingFeature.Responses.ALL) + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.xml.ws.soap.Addressing.class)); + ctClass.detach(); + } + + @Test + public void shouldAddServiceModeAnnotation() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsSm1") + .serviceMode(Service.Mode.PAYLOAD) + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.xml.ws.ServiceMode.class)); + ctClass.detach(); + } + + @Test + public void shouldBindWithUidAndJson() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsBind1") + .bind("uid", "{}") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldBindWithSoapBound() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsBind2") + .bind(new SoapBound("uid", "{}")) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldMakeFieldFromSource() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsField1") + .makeField("public int k = 3;") + .build(); + assertNotNull(ctClass.getDeclaredField("k")); + ctClass.detach(); + } + + @Test + public void shouldAddTypedField() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsField2") + .newField(String.class, "uid", UUID.randomUUID().toString()) + .build(); + assertNotNull(ctClass.getDeclaredField("uid")); + ctClass.detach(); + } + + @Test + public void shouldRemoveExistingField() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsField3") + .makeField("public int k = 3;") + .removeField("k") + .build(); + try { + ctClass.getDeclaredField("k"); + fail("Field should have been removed"); + } catch (javassist.NotFoundException e) { + // expected + } + ctClass.detach(); + } + + @Test + public void shouldNoopWhenRemovingNonexistentField() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsField4") + .removeField("nonexistent") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldMakeMethodFromSource() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsMakeMethod1") + .makeMethod("public String hello() { return \"hi\"; }") + .build(); + assertNotNull(ctClass.getDeclaredMethod("hello")); + ctClass.detach(); + } + + @Test + public void shouldAddMethodByNameAndParams() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsMethod1") + .newMethod("sayHello", new SoapParam(String.class, "text")) + .build(); + assertNotNull(ctClass.getDeclaredMethod("sayHello")); + ctClass.detach(); + } + + @Test + public void shouldAddMethodWithNameAndBound() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsMethod2") + .newMethod("sayHello", new SoapBound("b1"), + new SoapParam(String.class, "text")) + .build(); + assertNotNull(ctClass.getDeclaredMethod("sayHello")); + ctClass.detach(); + } + + @Test + public void shouldAddMethodWithResultAndBound() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsMethod3") + .newMethod(new SoapResult<>(String.class, "result"), + new SoapMethod("greet"), + new SoapBound("b1"), + new SoapParam(String.class, "name")) + .build(); + assertNotNull(ctClass.getDeclaredMethod("greet")); + ctClass.detach(); + } + + @Test + public void shouldAddMethodWithNullResult() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsMethod4") + .newMethod(null, new SoapMethod("doSomething"), null) + .build(); + assertNotNull(ctClass.getDeclaredMethod("doSomething")); + ctClass.detach(); + } + + @Test + public void shouldRemoveExistingMethod() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsMethod5") + .newMethod("temp") + .removeMethod("temp") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldNoopWhenRemovingNonexistentMethod() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsMethod6") + .removeMethod("nonexistent") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldRemoveMethodWithParams() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsMethod7") + .newMethod("withParam", new SoapParam(String.class, "x")) + .removeMethod("withParam", new SoapParam(String.class, "x")) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldNoopWhenRemovingNonexistentMethodWithParams() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsMethod8") + .removeMethod("nope", new SoapParam(String.class, "x")) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldSetSuperclassToEndpointApi() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsParent1") + .build(); + assertNotNull(ctClass.getSuperclass()); + assertEquals("org.apache.cxf.endpoint.EndpointApi", ctClass.getSuperclass().getName()); + ctClass.detach(); + } + + @Test + public void shouldHaveDefaultConstructor() throws Exception { + CtClass ctClass = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsCtor1") + .build(); + assertTrue(ctClass.getConstructors().length > 0); + ctClass.detach(); + } + + @Test + public void shouldSupportFluentChaining() throws Exception { + JaxwsEndpointApiCtClassBuilder builder = new JaxwsEndpointApiCtClassBuilder("org.test.JaxwsChain1"); + JaxwsEndpointApiCtClassBuilder result = builder + .webService("svc", "http://ns") + .bind("uid", "{}") + .makeField("public int k = 3;") + .newField(String.class, "name", "test") + .newMethod("hello"); + assertSame(builder, result); + CtClass ctClass = result.build(); + assertNotNull(ctClass); + ctClass.detach(); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiImplCtClassBuilderTest.java b/src/test/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiImplCtClassBuilderTest.java new file mode 100644 index 0000000..f3ebf42 --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiImplCtClassBuilderTest.java @@ -0,0 +1,143 @@ +package org.apache.cxf.endpoint.jaxws; + +import static org.junit.Assert.*; + +import jakarta.xml.ws.Service; +import jakarta.xml.ws.soap.AddressingFeature; + +import org.apache.cxf.endpoint.jaxws.definition.SoapBound; +import org.apache.cxf.endpoint.jaxws.definition.SoapMethod; +import org.apache.cxf.endpoint.jaxws.definition.SoapParam; +import org.apache.cxf.endpoint.jaxws.definition.SoapResult; +import org.apache.cxf.endpoint.jaxws.definition.SoapService; +import org.junit.Test; + +import javassist.ClassPool; +import javassist.CtClass; + +public class JaxwsEndpointApiImplCtClassBuilderTest { + + @Test + public void shouldBuildImplClassWithDefaultPool() throws Exception { + CtClass ctClass = new JaxwsEndpointApiImplCtClassBuilder("org.test.JaxwsImpl1") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldBuildImplClassWithCustomPool() throws Exception { + ClassPool pool = ClassPool.getDefault(); + CtClass ctClass = new JaxwsEndpointApiImplCtClassBuilder(pool, "org.test.JaxwsImpl2") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldForwardWebServiceWithNameAndNamespace() throws Exception { + CtClass ctClass = new JaxwsEndpointApiImplCtClassBuilder("org.test.JaxwsImpl3") + .webService("svc", "http://ns") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldForwardWebServiceWithServiceName() throws Exception { + CtClass ctClass = new JaxwsEndpointApiImplCtClassBuilder("org.test.JaxwsImpl4") + .webService("svc", "http://ns", "wsdlSvc") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldForwardFullWebService() throws Exception { + CtClass ctClass = new JaxwsEndpointApiImplCtClassBuilder("org.test.JaxwsImpl5") + .webService("svc", "http://ns", "wsdlSvc", "port", "/wsdl", "com.example.Sei") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldForwardWebServiceFromDescriptor() throws Exception { + SoapService svc = new SoapService("svc", "http://ns", "wsdlSvc"); + CtClass ctClass = new JaxwsEndpointApiImplCtClassBuilder("org.test.JaxwsImpl6") + .webService(svc) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldForwardServiceMode() throws Exception { + CtClass ctClass = new JaxwsEndpointApiImplCtClassBuilder("org.test.JaxwsImpl7") + .serviceMode(Service.Mode.PAYLOAD) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldForwardWebServiceProvider() throws Exception { + CtClass ctClass = new JaxwsEndpointApiImplCtClassBuilder("org.test.JaxwsImpl8") + .webServiceProvider("/wsdl", "svc", "http://ns", "port") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldForwardAddressing() throws Exception { + CtClass ctClass = new JaxwsEndpointApiImplCtClassBuilder("org.test.JaxwsImpl9") + .annotAddressing(true, true, AddressingFeature.Responses.ALL) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldForwardBind() throws Exception { + CtClass ctClass = new JaxwsEndpointApiImplCtClassBuilder("org.test.JaxwsImpl10") + .bind(new SoapBound("uid", "{}")) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldAddMethodToBothInterfaceAndImpl() throws Exception { + CtClass ctClass = new JaxwsEndpointApiImplCtClassBuilder("org.test.JaxwsImpl11") + .webService("svc", "http://ns") + .newMethod(new SoapResult<>(String.class, "result"), + new SoapMethod("hello"), + new SoapBound("b1"), + new SoapParam(String.class, "name")) + .build(); + assertNotNull(ctClass); + assertNotNull(ctClass.getDeclaredMethod("hello")); + ctClass.detach(); + } + + @Test + public void shouldUseImplSuffix() throws Exception { + CtClass ctClass = new JaxwsEndpointApiImplCtClassBuilder("org.test.JaxwsImplSuffix1") + .build(); + assertTrue(ctClass.getName().endsWith("$Impl")); + ctClass.detach(); + } + + @Test + public void shouldSupportFluentChaining() throws Exception { + JaxwsEndpointApiImplCtClassBuilder builder = new JaxwsEndpointApiImplCtClassBuilder("org.test.JaxwsImplChain1"); + JaxwsEndpointApiImplCtClassBuilder result = builder + .webService("svc", "http://ns") + .bind(new SoapBound("uid", "{}")); + assertSame(builder, result); + CtClass ctClass = result.build(); + assertNotNull(ctClass); + ctClass.detach(); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiInterfaceCtClassBuilderTest.java b/src/test/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiInterfaceCtClassBuilderTest.java new file mode 100644 index 0000000..7cc7013 --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxws/JaxwsEndpointApiInterfaceCtClassBuilderTest.java @@ -0,0 +1,262 @@ +package org.apache.cxf.endpoint.jaxws; + +import static org.junit.Assert.*; + +import jakarta.jws.WebParam; +import jakarta.xml.ws.Service; +import jakarta.xml.ws.soap.AddressingFeature; + +import org.apache.cxf.endpoint.jaxws.definition.SoapBound; +import org.apache.cxf.endpoint.jaxws.definition.SoapMethod; +import org.apache.cxf.endpoint.jaxws.definition.SoapParam; +import org.apache.cxf.endpoint.jaxws.definition.SoapResult; +import org.apache.cxf.endpoint.jaxws.definition.SoapService; +import org.junit.Test; + +import javassist.ClassPool; +import javassist.CtClass; + +public class JaxwsEndpointApiInterfaceCtClassBuilderTest { + + @Test + public void shouldBuildInterfaceWithDefaultPool() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface1") + .build(); + assertNotNull(ctClass); + assertTrue(ctClass.isInterface()); + ctClass.detach(); + } + + @Test + public void shouldBuildInterfaceWithCustomPool() throws Exception { + ClassPool pool = ClassPool.getDefault(); + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder(pool, "org.test.JaxwsIface2") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldAddWebServiceAnnotation() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface3") + .webService("svc", "http://ns") + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.jws.WebService.class)); + ctClass.detach(); + } + + @Test + public void shouldAddWebServiceWithServiceName() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface4") + .webService("svc", "http://ns", "wsdlSvc") + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.jws.WebService.class)); + ctClass.detach(); + } + + @Test + public void shouldAddFullWebService() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface5") + .webService("svc", "http://ns", "wsdlSvc", "port", "/wsdl", "com.example.Sei") + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.jws.WebService.class)); + ctClass.detach(); + } + + @Test + public void shouldAddWebServiceFromDescriptor() throws Exception { + SoapService svc = new SoapService("svc", "http://ns", "wsdlSvc"); + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface6") + .webService(svc) + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.jws.WebService.class)); + ctClass.detach(); + } + + @Test + public void shouldAddServiceModeAnnotation() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface7") + .serviceMode(Service.Mode.PAYLOAD) + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.xml.ws.ServiceMode.class)); + ctClass.detach(); + } + + @Test + public void shouldAddWebServiceProviderAnnotation() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface8") + .webServiceProvider("/wsdl", "svc", "http://ns", "port") + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.xml.ws.WebServiceProvider.class)); + ctClass.detach(); + } + + @Test + public void shouldAddAddressingAnnotation() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface9") + .addressing(true, true, AddressingFeature.Responses.ALL) + .build(); + assertNotNull(ctClass.getAnnotation(jakarta.xml.ws.soap.Addressing.class)); + ctClass.detach(); + } + + @Test + public void shouldBindUidJson() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface10") + .bind("uid", "{}") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldBindSoapBound() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface11") + .bind(new SoapBound("uid", "{}")) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldMakeFieldFromSource() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface12") + .makeField("public int k = 3;") + .build(); + assertNotNull(ctClass.getDeclaredField("k")); + ctClass.detach(); + } + + @Test + public void shouldAddTypedField() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface13") + .newField(String.class, "uid", "test") + .build(); + assertNotNull(ctClass.getDeclaredField("uid")); + ctClass.detach(); + } + + @Test + public void shouldNoopWhenFieldAlreadyExists() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface14") + .newField(String.class, "uid", "test") + .newField(String.class, "uid", "test2") + .build(); + assertNotNull(ctClass.getDeclaredField("uid")); + ctClass.detach(); + } + + @Test + public void shouldRemoveExistingField() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface15") + .makeField("public int k = 3;") + .removeField("k") + .build(); + try { + ctClass.getDeclaredField("k"); + fail("Field should have been removed"); + } catch (javassist.NotFoundException e) { + // expected + } + ctClass.detach(); + } + + @Test + public void shouldNoopWhenRemovingNonexistentField() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface16") + .removeField("nonexistent") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldAddAbstractMethodByNameAndParams() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface17") + .abstractMethod("sayHello", new SoapParam(String.class, "text")) + .build(); + assertNotNull(ctClass.getDeclaredMethod("sayHello")); + ctClass.detach(); + } + + @Test + public void shouldAddAbstractMethodWithNameAndBound() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface18") + .abstractMethod("sayHello", new SoapBound("b1"), new SoapParam(String.class, "text")) + .build(); + assertNotNull(ctClass.getDeclaredMethod("sayHello")); + ctClass.detach(); + } + + @Test + public void shouldAddAbstractMethodWithResultAndBound() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface19") + .abstractMethod(new SoapResult<>(String.class, "result"), + new SoapMethod("greet"), + new SoapBound("b1"), + new SoapParam(String.class, "name")) + .build(); + assertNotNull(ctClass.getDeclaredMethod("greet")); + ctClass.detach(); + } + + @Test + public void shouldRemoveExistingMethod() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface20") + .abstractMethod("temp") + .removeMethod("temp") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldNoopWhenRemovingNonexistentMethod() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface21") + .removeMethod("nonexistent") + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldRemoveMethodWithParams() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface22") + .abstractMethod("withParam", new SoapParam(String.class, "x")) + .removeMethod("withParam", new SoapParam(String.class, "x")) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldNoopWhenRemovingNonexistentMethodWithParams() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface23") + .removeMethod("nope", new SoapParam(String.class, "x")) + .build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldSupportFluentChaining() throws Exception { + JaxwsEndpointApiInterfaceCtClassBuilder builder = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface24"); + JaxwsEndpointApiInterfaceCtClassBuilder result = builder + .webService("svc", "http://ns") + .bind("uid", "{}") + .abstractMethod("hello", new SoapParam(String.class, "name")); + assertSame(builder, result); + CtClass ctClass = result.build(); + assertNotNull(ctClass); + ctClass.detach(); + } + + @Test + public void shouldAddAbstractMethodWithNullResult() throws Exception { + CtClass ctClass = new JaxwsEndpointApiInterfaceCtClassBuilder("org.test.JaxwsIface25") + .abstractMethod(null, new SoapMethod("doSomething"), null) + .build(); + assertNotNull(ctClass.getDeclaredMethod("doSomething")); + ctClass.detach(); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapBoundTest.java b/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapBoundTest.java new file mode 100644 index 0000000..7e1a8ba --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapBoundTest.java @@ -0,0 +1,43 @@ +package org.apache.cxf.endpoint.jaxws.definition; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class SoapBoundTest { + + @Test + public void shouldCreateBoundWithUidOnly() { + SoapBound bound = new SoapBound("uid-123"); + assertEquals("uid-123", bound.getUid()); + assertEquals("", bound.getJson()); + } + + @Test + public void shouldCreateBoundWithUidAndJson() { + SoapBound bound = new SoapBound("uid-456", "{\"key\":\"value\"}"); + assertEquals("uid-456", bound.getUid()); + assertEquals("{\"key\":\"value\"}", bound.getJson()); + } + + @Test + public void shouldAllowUidOverride() { + SoapBound bound = new SoapBound("old"); + bound.setUid("new"); + assertEquals("new", bound.getUid()); + } + + @Test + public void shouldAllowJsonOverride() { + SoapBound bound = new SoapBound("uid"); + bound.setJson("{\"updated\":true}"); + assertEquals("{\"updated\":true}", bound.getJson()); + } + + @Test + public void shouldAllowNullJson() { + SoapBound bound = new SoapBound("uid"); + bound.setJson(null); + assertNull(bound.getJson()); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapMethodTest.java b/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapMethodTest.java new file mode 100644 index 0000000..0e33f27 --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapMethodTest.java @@ -0,0 +1,53 @@ +package org.apache.cxf.endpoint.jaxws.definition; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class SoapMethodTest { + + @Test + public void shouldCreateMethodWithDefaults() { + SoapMethod method = new SoapMethod(); + assertEquals("", method.getOperationName()); + assertEquals("", method.getAction()); + assertFalse(method.isExclude()); + } + + @Test + public void shouldCreateMethodWithOperationName() { + SoapMethod method = new SoapMethod("sayHello"); + assertEquals("sayHello", method.getOperationName()); + assertEquals("", method.getAction()); + assertFalse(method.isExclude()); + } + + @Test + public void shouldCreateFullySpecifiedMethod() { + SoapMethod method = new SoapMethod("sayHello", "http://example.com/sayHello", true); + assertEquals("sayHello", method.getOperationName()); + assertEquals("http://example.com/sayHello", method.getAction()); + assertTrue(method.isExclude()); + } + + @Test + public void shouldAllowOperationNameOverride() { + SoapMethod method = new SoapMethod("old"); + method.setOperationName("new"); + assertEquals("new", method.getOperationName()); + } + + @Test + public void shouldAllowActionOverride() { + SoapMethod method = new SoapMethod(); + method.setAction("http://example.com/action"); + assertEquals("http://example.com/action", method.getAction()); + } + + @Test + public void shouldAllowExcludeOverride() { + SoapMethod method = new SoapMethod(); + method.setExclude(true); + assertTrue(method.isExclude()); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapParamTest.java b/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapParamTest.java new file mode 100644 index 0000000..ef7578b --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapParamTest.java @@ -0,0 +1,94 @@ +package org.apache.cxf.endpoint.jaxws.definition; + +import static org.junit.Assert.*; + +import jakarta.jws.WebParam; + +import org.junit.Test; + +public class SoapParamTest { + + @Test + public void shouldCreateParamWithTypeAndName() { + SoapParam param = new SoapParam<>(String.class, "userName"); + assertEquals(String.class, param.getType()); + assertEquals("userName", param.getName()); + assertEquals(WebParam.Mode.IN, param.getMode()); + assertFalse(param.isHeader()); + assertEquals("", param.getPartName()); + assertEquals("", param.getTargetNamespace()); + } + + @Test + public void shouldCreateParamWithHeaderFlag() { + SoapParam param = new SoapParam<>(String.class, "token", true); + assertTrue(param.isHeader()); + } + + @Test + public void shouldCreateParamWithMode() { + SoapParam param = new SoapParam<>(String.class, "data", WebParam.Mode.OUT); + assertEquals(WebParam.Mode.OUT, param.getMode()); + } + + @Test + public void shouldCreateParamWithModeAndHeader() { + SoapParam param = new SoapParam<>(String.class, "data", WebParam.Mode.INOUT, true); + assertEquals(WebParam.Mode.INOUT, param.getMode()); + assertTrue(param.isHeader()); + } + + @Test + public void shouldCreateFullySpecifiedParam() { + SoapParam param = new SoapParam<>(String.class, "data", "part1", + "http://example.com", WebParam.Mode.OUT, true); + assertEquals(String.class, param.getType()); + assertEquals("data", param.getName()); + assertEquals("part1", param.getPartName()); + assertEquals("http://example.com", param.getTargetNamespace()); + assertEquals(WebParam.Mode.OUT, param.getMode()); + assertTrue(param.isHeader()); + } + + @Test + public void shouldAllowTypeOverride() { + SoapParam param = new SoapParam<>(String.class, "val"); + param.setType(String.class); + assertEquals(String.class, param.getType()); + } + + @Test + public void shouldAllowNameOverride() { + SoapParam param = new SoapParam<>(String.class, "old"); + param.setName("new"); + assertEquals("new", param.getName()); + } + + @Test + public void shouldAllowPartNameOverride() { + SoapParam param = new SoapParam<>(String.class, "data"); + param.setPartName("part"); + assertEquals("part", param.getPartName()); + } + + @Test + public void shouldAllowTargetNamespaceOverride() { + SoapParam param = new SoapParam<>(String.class, "data"); + param.setTargetNamespace("http://ns"); + assertEquals("http://ns", param.getTargetNamespace()); + } + + @Test + public void shouldAllowModeOverride() { + SoapParam param = new SoapParam<>(String.class, "data"); + param.setMode(WebParam.Mode.OUT); + assertEquals(WebParam.Mode.OUT, param.getMode()); + } + + @Test + public void shouldAllowHeaderOverride() { + SoapParam param = new SoapParam<>(String.class, "data"); + param.setHeader(true); + assertTrue(param.isHeader()); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapResultTest.java b/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapResultTest.java new file mode 100644 index 0000000..50f5bc6 --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapResultTest.java @@ -0,0 +1,64 @@ +package org.apache.cxf.endpoint.jaxws.definition; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class SoapResultTest { + + @Test + public void shouldCreateResultWithTypeAndName() { + SoapResult result = new SoapResult<>(String.class, "result"); + assertEquals(String.class, result.getRtClass()); + assertEquals("result", result.getName()); + assertEquals("", result.getTargetNamespace()); + assertFalse(result.isHeader()); + assertEquals("", result.getPartName()); + } + + @Test + public void shouldCreateFullySpecifiedResult() { + SoapResult result = new SoapResult<>(String.class, "ret", + "http://example.com", true, "part1"); + assertEquals(String.class, result.getRtClass()); + assertEquals("ret", result.getName()); + assertEquals("http://example.com", result.getTargetNamespace()); + assertTrue(result.isHeader()); + assertEquals("part1", result.getPartName()); + } + + @Test + public void shouldAllowRtClassOverride() { + SoapResult result = new SoapResult<>(String.class, "ret"); + result.setRtClass(String.class); + assertEquals(String.class, result.getRtClass()); + } + + @Test + public void shouldAllowNameOverride() { + SoapResult result = new SoapResult<>(String.class, "old"); + result.setName("new"); + assertEquals("new", result.getName()); + } + + @Test + public void shouldAllowTargetNamespaceOverride() { + SoapResult result = new SoapResult<>(String.class, "ret"); + result.setTargetNamespace("http://ns"); + assertEquals("http://ns", result.getTargetNamespace()); + } + + @Test + public void shouldAllowHeaderOverride() { + SoapResult result = new SoapResult<>(String.class, "ret"); + result.setHeader(true); + assertTrue(result.isHeader()); + } + + @Test + public void shouldAllowPartNameOverride() { + SoapResult result = new SoapResult<>(String.class, "ret"); + result.setPartName("part"); + assertEquals("part", result.getPartName()); + } +} diff --git a/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapServiceTest.java b/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapServiceTest.java new file mode 100644 index 0000000..2fbb9d8 --- /dev/null +++ b/src/test/java/org/apache/cxf/endpoint/jaxws/definition/SoapServiceTest.java @@ -0,0 +1,78 @@ +package org.apache.cxf.endpoint.jaxws.definition; + +import static org.junit.Assert.*; + +import org.junit.Test; + +public class SoapServiceTest { + + @Test + public void shouldCreateServiceWithNameAndTargetNamespace() { + SoapService service = new SoapService("MyService", "http://example.com"); + assertEquals("MyService", service.getName()); + assertEquals("http://example.com", service.getTargetNamespace()); + assertNull(service.getServiceName()); + assertNull(service.getPortName()); + assertNull(service.getWsdlLocation()); + assertNull(service.getEndpointInterface()); + } + + @Test + public void shouldCreateServiceWithServiceName() { + SoapService service = new SoapService("MyService", "http://example.com", "MyWSDLService"); + assertEquals("MyWSDLService", service.getServiceName()); + } + + @Test + public void shouldCreateServiceWithServiceNameAndPortName() { + SoapService service = new SoapService("MyService", "http://example.com", "svc", "port"); + assertEquals("svc", service.getServiceName()); + assertEquals("port", service.getPortName()); + } + + @Test + public void shouldCreateServiceWithWsdlLocation() { + SoapService service = new SoapService("MyService", "http://example.com", "svc", "port", "/wsdl"); + assertEquals("/wsdl", service.getWsdlLocation()); + } + + @Test + public void shouldCreateFullySpecifiedService() { + SoapService service = new SoapService("MyService", "http://example.com", + "svc", "port", "/wsdl", "com.example.Sei"); + assertEquals("MyService", service.getName()); + assertEquals("http://example.com", service.getTargetNamespace()); + assertEquals("svc", service.getServiceName()); + assertEquals("port", service.getPortName()); + assertEquals("/wsdl", service.getWsdlLocation()); + assertEquals("com.example.Sei", service.getEndpointInterface()); + } + + @Test + public void shouldAllowServiceNameOverride() { + SoapService service = new SoapService("name", "ns"); + service.setServiceName("newSvc"); + assertEquals("newSvc", service.getServiceName()); + } + + @Test + public void shouldAllowPortNameOverride() { + SoapService service = new SoapService("name", "ns"); + service.setPortName("newPort"); + assertEquals("newPort", service.getPortName()); + } + + @Test + public void shouldAllowWsdlLocationOverride() { + SoapService service = new SoapService("name", "ns"); + service.setWsdlLocation("/newWsdl"); + assertEquals("/newWsdl", service.getWsdlLocation()); + } + + @Test + public void shouldAllowEndpointInterfaceOverride() { + SoapService service = new SoapService("name", "ns"); + service.setEndpointInterface("com.example.NewSei"); + assertEquals("com.example.NewSei", service.getEndpointInterface()); + } +}