Skip to content

Commit 2e786f6

Browse files
openapi: implement service/operation exclusion filtering
Adds two new configuration properties to OpenApiConfiguration: ignoredServices (Set<String>) Exact service names to suppress from the generated spec. Checked in shouldIncludeService() before any inclusion rule, so a listed service is never emitted even when readAllResources=true. Properties key: openapi.ignoredServices (comma-separated) ignoredOperations (Set<String>) Per-operation exclusion with two match modes: - "ServiceName/operationName" — targeted: removes only that op on that service - "operationName" — global: removes that op name across every service Evaluated in the new shouldIncludeOperation() called from generateServicePaths() before the path is added to the Paths map. Properties key: openapi.ignoredOperations (comma-separated) applyPropertiesConfiguration() now also loads openapi.ignoredRoutes, openapi.ignoredServices, and openapi.ignoredOperations from any properties source (file, system properties, known scan locations). copy() propagates both new sets. Convenience API: addIgnoredService(String), addIgnoredOperation(String). Tests (5 new in OpenApiSpecGeneratorTest): - testIgnoredServiceIsExcluded - testIgnoredQualifiedOperationIsExcluded - testIgnoredBareOperationIsExcludedAcrossAllServices - testIgnoredServicesAndOperationsProgrammaticAPI (includes copy() check) - testQualifiedIgnoredOperationDoesNotAffectOtherServices Documentation: modules/openapi/README.md — full configuration reference covering all three exclusion mechanisms (ignoredServices, ignoredOperations, ignoredRoutes), precedence order, properties file format, and Java API examples. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 22329f7 commit 2e786f6

4 files changed

Lines changed: 452 additions & 0 deletions

File tree

modules/openapi/README.md

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# axis2-openapi — OpenAPI Integration Module
2+
3+
Auto-generates OpenAPI 3.0.1 specifications from deployed Axis2 services and serves Swagger UI at `/swagger-ui`.
4+
5+
## Endpoints
6+
7+
| URL | Description |
8+
|-----|-------------|
9+
| `/openapi.json` | OpenAPI 3.0.1 spec (JSON) |
10+
| `/openapi.yaml` | OpenAPI 3.0.1 spec (YAML) |
11+
| `/swagger-ui` | Interactive Swagger UI |
12+
13+
## Enabling the module
14+
15+
Add to `WEB-INF/conf/axis2.xml`:
16+
```xml
17+
<module ref="openapi"/>
18+
```
19+
20+
Copy `axis2-openapi-<version>.jar` to `WEB-INF/modules/openapi-<version>.mar`.
21+
22+
---
23+
24+
## Configuration
25+
26+
Configuration is loaded in this order (later sources win):
27+
28+
1. `module.xml` parameters
29+
2. `openapi.properties` on the classpath (or the path set by `propertiesFile` module param)
30+
3. Known locations: `META-INF/openapi.properties`, `WEB-INF/openapi.properties`, `openapi-config.properties`
31+
4. System properties (same key names)
32+
5. Programmatic `OpenApiConfiguration` API (highest precedence)
33+
34+
### API information
35+
36+
| Property key | Default | Description |
37+
|---|---|---|
38+
| `openapi.title` | `Apache Axis2 REST API` | Spec `info.title` |
39+
| `openapi.description` | (auto) | Spec `info.description` |
40+
| `openapi.version` | `1.0.0` | Spec `info.version` |
41+
| `openapi.contact.name` | `Apache Axis2` | Contact name |
42+
| `openapi.contact.url` | (Apache URL) | Contact URL |
43+
| `openapi.contact.email` || Contact e-mail |
44+
| `openapi.license.name` | `Apache License 2.0` | License name |
45+
| `openapi.license.url` | (Apache URL) | License URL |
46+
| `openapi.termsOfServiceUrl` || Terms of service URL |
47+
48+
### Generation flags
49+
50+
| Property key | Default | Description |
51+
|---|---|---|
52+
| `openapi.prettyPrint` | `true` | Indent JSON/YAML output |
53+
| `openapi.readAllResources` | `true` | Include all services unless filtered |
54+
| `openapi.swaggerUi.enabled` | `true` | Serve Swagger UI |
55+
| `openapi.swaggerUi.version` | `4.15.5` | CDN version of Swagger UI bundle |
56+
| `openapi.resourcePackages` || Comma-separated Java packages; only services whose `ServiceClass` is in these packages are included (requires `readAllResources=false`) |
57+
58+
---
59+
60+
## Filtering: excluding services and operations
61+
62+
Three independent mechanisms control what appears in the generated spec. All are evaluated **before** inclusion rules — an excluded entity never appears even if it would otherwise match `readAllResources` or `resourcePackages`.
63+
64+
### 1. `ignoredServices` — exclude entire services by name
65+
66+
Matches against `AxisService.getName()` (exact, case-sensitive).
67+
68+
**Properties file:**
69+
```properties
70+
# Comma-separated list of service names to exclude
71+
openapi.ignoredServices=InternalService, DebugService, AdminService
72+
```
73+
74+
**Java API:**
75+
```java
76+
OpenApiConfiguration config = new OpenApiConfiguration();
77+
config.addIgnoredService("InternalService");
78+
config.addIgnoredService("DebugService");
79+
```
80+
81+
### 2. `ignoredOperations` — exclude specific operations
82+
83+
Each entry is one of:
84+
85+
| Format | Effect |
86+
|---|---|
87+
| `ServiceName/operationName` | Excludes that operation on that service only |
88+
| `operationName` | Excludes that operation name on **every** service |
89+
90+
**Properties file:**
91+
```properties
92+
# Targeted: remove one op from one service
93+
# Global: remove an op name from all services
94+
openapi.ignoredOperations=AdminService/nukeDatabase, internalStatus, debugPing
95+
```
96+
97+
**Java API:**
98+
```java
99+
config.addIgnoredOperation("AdminService/nukeDatabase"); // targeted
100+
config.addIgnoredOperation("internalStatus"); // global (all services)
101+
```
102+
103+
### 3. `ignoredRoutes` — exclude by generated path pattern
104+
105+
Matches against the generated path string (e.g. `/services/MyService/myOp`).
106+
Each entry is tested as a Java regex (`String.matches()`) **or** a substring (`String.contains()`).
107+
108+
```properties
109+
openapi.ignoredRoutes=/services/internal/.*, /services/legacy/.*
110+
```
111+
112+
**Java API:**
113+
```java
114+
config.addIgnoredRoute("/services/internal/.*");
115+
```
116+
117+
> **Prefer `ignoredServices` and `ignoredOperations`** over `ignoredRoutes` — they match on logical names rather than generated path strings and are unaffected by future path format changes.
118+
119+
### Precedence within the generator
120+
121+
```
122+
isSystemService() (hardcoded: Version, AdminService, __)
123+
→ ignoredServices
124+
→ readAllResources / resourceClasses / resourcePackages
125+
→ shouldIncludeOperation() / ignoredOperations
126+
→ isIgnoredRoute() / ignoredRoutes
127+
→ (path added to spec)
128+
```
129+
130+
### Complete `openapi.properties` example
131+
132+
Place on the classpath as `openapi.properties` (or `META-INF/openapi.properties`):
133+
134+
```properties
135+
# API identity
136+
openapi.title=My Financial API
137+
openapi.version=2.1.0
138+
openapi.description=Internal portfolio management services
139+
140+
# Contact / license
141+
openapi.contact.name=Platform Team
142+
openapi.contact.email=platform@example.com
143+
openapi.license.name=Proprietary
144+
145+
# Service filtering
146+
openapi.ignoredServices=LegacySOAPService, InternalHealthCheck
147+
openapi.ignoredOperations=AdminService/resetDatabase, debugEcho
148+
149+
# UI
150+
openapi.prettyPrint=true
151+
openapi.swaggerUi.enabled=true
152+
openapi.swaggerUi.version=4.15.5
153+
```
154+
155+
---
156+
157+
## Programmatic configuration (Java)
158+
159+
```java
160+
OpenApiConfiguration config = new OpenApiConfiguration();
161+
162+
// API info
163+
config.setTitle("My API");
164+
config.setVersion("2.0.0");
165+
166+
// Exclude services
167+
config.addIgnoredService("InternalService");
168+
169+
// Exclude operations
170+
config.addIgnoredOperation("AdminService/nukeDatabase"); // this service only
171+
config.addIgnoredOperation("debugPing"); // all services
172+
173+
// Pass to the generator
174+
OpenApiSpecGenerator generator = new OpenApiSpecGenerator(configContext, config);
175+
String json = generator.generateOpenApiJson(httpRequest);
176+
String yaml = generator.generateOpenApiYaml(httpRequest);
177+
```
178+
179+
---
180+
181+
## Known limitations
182+
183+
- **Request body schema** — all operations are typed as `object` because Axis2 JSON-RPC services use `JsonRpcMessageReceiver` and have no annotation-level parameter metadata. Schema details must be added via `OpenApiCustomizer` or by serving a static schema file.
184+
- **GET operations** — all operations are mapped to `POST`; override via `OpenApiCustomizer` if GET endpoints are needed.
185+
- **YAML format** — uses `jackson-dataformat-yaml` (transitive from `swagger-core`); no additional dependency required.

modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiConfiguration.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,26 @@ public class OpenApiConfiguration {
8585
/** Routes/paths to ignore during generation */
8686
private Collection<String> ignoredRoutes = new ArrayList<>();
8787

88+
/**
89+
* Service names to exclude from the generated spec.
90+
* Checked against {@link org.apache.axis2.description.AxisService#getName()}.
91+
* Example: {@code ignoredServices = {"InternalService", "DebugService"}}
92+
* Properties key: {@code openapi.ignoredServices} (comma-separated)
93+
*/
94+
private Set<String> ignoredServices = new HashSet<>();
95+
96+
/**
97+
* Operations to exclude from the generated spec.
98+
* Each entry is either:
99+
* <ul>
100+
* <li>{@code "ServiceName/operationName"} — excludes that operation on that service only</li>
101+
* <li>{@code "operationName"} — excludes that operation name across all services</li>
102+
* </ul>
103+
* Example: {@code ignoredOperations = {"AdminService/deleteAll", "internalStatus"}}
104+
* Properties key: {@code openapi.ignoredOperations} (comma-separated)
105+
*/
106+
private Set<String> ignoredOperations = new HashSet<>();
107+
88108
/** Whether to scan known configuration locations */
89109
private boolean scanKnownConfigLocations = true;
90110

@@ -243,6 +263,24 @@ private void applyPropertiesConfiguration(Properties props) {
243263
if (packages != null) {
244264
resourcePackages.addAll(Arrays.asList(packages.split("\\s*,\\s*")));
245265
}
266+
267+
// Ignored routes (comma-separated path patterns)
268+
String routes = getProperty(props, "openapi.ignoredRoutes", null);
269+
if (routes != null) {
270+
ignoredRoutes.addAll(Arrays.asList(routes.split("\\s*,\\s*")));
271+
}
272+
273+
// Ignored service names (comma-separated exact names)
274+
String services = getProperty(props, "openapi.ignoredServices", null);
275+
if (services != null) {
276+
ignoredServices.addAll(Arrays.asList(services.split("\\s*,\\s*")));
277+
}
278+
279+
// Ignored operations (comma-separated, each "ServiceName/opName" or bare "opName")
280+
String operations = getProperty(props, "openapi.ignoredOperations", null);
281+
if (operations != null) {
282+
ignoredOperations.addAll(Arrays.asList(operations.split("\\s*,\\s*")));
283+
}
246284
}
247285

248286
/**
@@ -350,6 +388,12 @@ public Properties getUserProperties(Map<String, Object> userDefinedOptions) {
350388
public Collection<String> getIgnoredRoutes() { return ignoredRoutes; }
351389
public void setIgnoredRoutes(Collection<String> ignoredRoutes) { this.ignoredRoutes = ignoredRoutes; }
352390

391+
public Set<String> getIgnoredServices() { return ignoredServices; }
392+
public void setIgnoredServices(Set<String> ignoredServices) { this.ignoredServices = ignoredServices; }
393+
394+
public Set<String> getIgnoredOperations() { return ignoredOperations; }
395+
public void setIgnoredOperations(Set<String> ignoredOperations) { this.ignoredOperations = ignoredOperations; }
396+
353397
public boolean isPrettyPrint() { return prettyPrint; }
354398
public void setPrettyPrint(boolean prettyPrint) { this.prettyPrint = prettyPrint; }
355399

@@ -427,6 +471,23 @@ public void addIgnoredRoute(String route) {
427471
ignoredRoutes.add(route);
428472
}
429473

474+
/**
475+
* Exclude a service from the generated spec by its exact name.
476+
* @param serviceName value of {@link org.apache.axis2.description.AxisService#getName()}
477+
*/
478+
public void addIgnoredService(String serviceName) {
479+
ignoredServices.add(serviceName);
480+
}
481+
482+
/**
483+
* Exclude an operation from the generated spec.
484+
* @param entry either {@code "ServiceName/operationName"} (targeted) or
485+
* {@code "operationName"} (applies to every service)
486+
*/
487+
public void addIgnoredOperation(String entry) {
488+
ignoredOperations.add(entry);
489+
}
490+
430491
/**
431492
* Create a copy of this configuration.
432493
*/
@@ -458,6 +519,8 @@ public OpenApiConfiguration copy() {
458519
copy.resourcePackages = new HashSet<>(this.resourcePackages);
459520
copy.resourceClasses = new HashSet<>(this.resourceClasses);
460521
copy.ignoredRoutes = new ArrayList<>(this.ignoredRoutes);
522+
copy.ignoredServices = new HashSet<>(this.ignoredServices);
523+
copy.ignoredOperations = new HashSet<>(this.ignoredOperations);
461524
copy.securityDefinitions = new HashMap<>(this.securityDefinitions);
462525
copy.swaggerUiMediaTypes = new HashMap<>(this.swaggerUiMediaTypes);
463526

modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -337,6 +337,27 @@ private Paths generatePaths() {
337337
return paths;
338338
}
339339

340+
/**
341+
* Check if an operation should be included based on {@code ignoredOperations}.
342+
* An entry matches if it equals either:
343+
* <ul>
344+
* <li>{@code "ServiceName/operationName"} — targeted exclusion for one service</li>
345+
* <li>{@code "operationName"} — excludes this operation name from every service</li>
346+
* </ul>
347+
*/
348+
private boolean shouldIncludeOperation(AxisService service, AxisOperation operation) {
349+
String opName = operation.getName().getLocalPart();
350+
String qualified = service.getName() + "/" + opName;
351+
352+
for (String entry : configuration.getIgnoredOperations()) {
353+
if (entry.equals(qualified) || entry.equals(opName)) {
354+
log.debug("Skipping operation excluded by ignoredOperations '" + entry + "': " + qualified);
355+
return false;
356+
}
357+
}
358+
return true;
359+
}
360+
340361
/**
341362
* Generate paths for a specific service.
342363
*/
@@ -347,6 +368,11 @@ private void generateServicePaths(AxisService service, Paths paths) {
347368
while (operations.hasNext()) {
348369
AxisOperation operation = operations.next();
349370

371+
// Check per-operation exclusion before generating the path
372+
if (!shouldIncludeOperation(service, operation)) {
373+
continue;
374+
}
375+
350376
// Generate path for REST operation
351377
String path = generateOperationPath(service, operation);
352378
if (path != null && !isIgnoredRoute(path)) {
@@ -439,9 +465,18 @@ private boolean isSystemService(AxisService service) {
439465

440466
/**
441467
* Check if a service should be included based on configuration filters.
468+
* Exclusion is evaluated before inclusion: a service listed in
469+
* {@code ignoredServices} is always skipped regardless of other settings.
442470
*/
443471
private boolean shouldIncludeService(AxisService service) {
444472
String serviceName = service.getName();
473+
474+
// Explicit exclusion by name takes priority over all other filters
475+
if (configuration.getIgnoredServices().contains(serviceName)) {
476+
log.debug("Skipping service explicitly excluded by ignoredServices: " + serviceName);
477+
return false;
478+
}
479+
445480
String servicePackage = getServicePackage(service);
446481

447482
// If readAllResources is false, check specific resource classes/packages

0 commit comments

Comments
 (0)