This is an automated email from the ASF dual-hosted git repository. robertlazarski pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/axis-axis2-java-core.git
commit 08a8b2b24c96d011b8ed9392591ba6e62d25e12b Author: Robert Lazarski <[email protected]> AuthorDate: Sun Apr 5 17:19:59 2026 -1000 openapi: replace custom Jackson mappers with swagger-core Json/Yaml utilities OpenAPI spec serialization now delegates entirely to io.swagger.v3.core.util.Json and io.swagger.v3.core.util.Yaml rather than constructing custom ObjectMappers. This removes all direct Jackson imports from OpenApiSpecGenerator; Jackson remains on the classpath transitively from swagger-core. swagger-core's ObjectMapperFactory already configures NON_NULL, disables WRITE_DATES_AS_TIMESTAMPS, and disables FAIL_ON_EMPTY_BEANS — the same settings we had manually, now handled by the library. Pretty vs compact output is selected at serialization time via Json.pretty()/Yaml.pretty() vs mapper().writeValueAsString(). Tests: add testPrettyAndCompactJsonOutput and testPrettyAndCompactYamlOutput to exercise both branches of the isPrettyPrint() path through the swagger-core utilities. Co-Authored-By: Claude Sonnet 4.6 <[email protected]> --- modules/openapi/README.md | 2 +- modules/openapi/pom.xml | 4 +- .../apache/axis2/openapi/OpenApiSpecGenerator.java | 61 +++++----------------- .../axis2/openapi/OpenApiSpecGeneratorTest.java | 47 ++++++++++++++++- 4 files changed, 62 insertions(+), 52 deletions(-) diff --git a/modules/openapi/README.md b/modules/openapi/README.md index add52816ca..674d065892 100644 --- a/modules/openapi/README.md +++ b/modules/openapi/README.md @@ -182,4 +182,4 @@ String yaml = generator.generateOpenApiYaml(httpRequest); - **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. - **GET operations** — all operations are mapped to `POST`; override via `OpenApiCustomizer` if GET endpoints are needed. -- **YAML format** — uses `jackson-dataformat-yaml` (transitive from `swagger-core`); no additional dependency required. +- **YAML format** — delegates to `io.swagger.v3.core.util.Yaml`, which uses `jackson-dataformat-yaml` internally; no additional dependency required. diff --git a/modules/openapi/pom.xml b/modules/openapi/pom.xml index 052a61c9bf..0a54f060b2 100644 --- a/modules/openapi/pom.xml +++ b/modules/openapi/pom.xml @@ -64,7 +64,9 @@ <artifactId>swagger-annotations</artifactId> </dependency> - <!-- Jackson comes transitively from swagger-core dependency --> + <!-- No direct Jackson dependency: serialization delegates to swagger-core's + Json/Yaml utilities (io.swagger.v3.core.util), which manage their own + Jackson mapper internally. Jackson arrives transitively from swagger-core. --> <!-- Moshi for JSON serialization (preferred over Jackson where possible) --> <dependency> diff --git a/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java b/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java index 0ea1a3224d..d67a3f02fe 100644 --- a/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java +++ b/modules/openapi/src/main/java/org/apache/axis2/openapi/OpenApiSpecGenerator.java @@ -44,11 +44,8 @@ import org.apache.axis2.engine.AxisConfiguration; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; -import com.fasterxml.jackson.dataformat.yaml.YAMLGenerator; +import io.swagger.v3.core.util.Json; +import io.swagger.v3.core.util.Yaml; import com.squareup.moshi.Moshi; import com.squareup.moshi.JsonAdapter; import com.squareup.moshi.adapters.Rfc3339DateJsonAdapter; @@ -78,8 +75,6 @@ public class OpenApiSpecGenerator { private final ConfigurationContext configurationContext; private final ServiceIntrospector serviceIntrospector; - private final ObjectMapper objectMapper; // Required for Swagger OpenAPI model serialization (JSON) - private final ObjectMapper yamlMapper; // Jackson with YAMLFactory for YAML output private final Moshi moshi; // Preferred for general JSON operations private final JsonProcessingMetrics metrics; private final OpenApiConfiguration configuration; @@ -99,36 +94,11 @@ public class OpenApiSpecGenerator { this.configuration = config != null ? config : new OpenApiConfiguration(); this.serviceIntrospector = new ServiceIntrospector(configContext); - // Configure Jackson for OpenAPI model serialization with HTTP/2 optimization metrics. - // Fix: NON_NULL inclusion is required because the Swagger model POJOs (Info, Contact, - // License, Schema, etc.) declare many optional fields that default to null. Without this, - // Jackson serializes every null field as "key": null, inflating a simple 3-service spec - // by ~300 null entries and producing invalid Swagger UI display artifacts. - this.objectMapper = new ObjectMapper(); - this.objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - this.objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - this.objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); - if (configuration.isPrettyPrint()) { - this.objectMapper.enable(SerializationFeature.INDENT_OUTPUT); - } - - // Fix: generateOpenApiYaml() was returning JSON because it delegated to - // generateOpenApiJson(). A second ObjectMapper backed by YAMLFactory produces - // proper YAML. WRITE_DOC_START_MARKER is disabled to suppress the "---" header - // that confuses some YAML parsers. jackson-dataformat-yaml is already on the - // classpath transitively from io.swagger.core.v3:swagger-core. - YAMLFactory yamlFactory = YAMLFactory.builder() - .disable(YAMLGenerator.Feature.WRITE_DOC_START_MARKER) - .build(); - this.yamlMapper = new ObjectMapper(yamlFactory); - this.yamlMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); - this.yamlMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); - this.yamlMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS); - if (configuration.isPrettyPrint()) { - this.yamlMapper.enable(SerializationFeature.INDENT_OUTPUT); - } + // OpenAPI spec serialization delegates entirely to swagger-core's Json/Yaml utilities, + // which already configure NON_NULL, WRITE_DATES_AS_TIMESTAMPS=false, and FAIL_ON_EMPTY_BEANS=false + // on their internal Jackson mapper. No direct Jackson dependency is needed in this class. - // Initialize Moshi for general JSON operations (preferred over Jackson where possible) + // Initialize Moshi for general JSON operations (Axis2 preference) this.moshi = new Moshi.Builder() .add(Date.class, new Rfc3339DateJsonAdapter()) .build(); @@ -136,7 +106,7 @@ public class OpenApiSpecGenerator { // Initialize performance metrics from moshih2 package for HTTP/2 optimization tracking this.metrics = new JsonProcessingMetrics(); - log.info("OpenAPI JSON processing configured with Moshi + Jackson hybrid approach (Jackson only for Swagger model serialization, Moshi preferred for other JSON operations, moshih2 performance tracking enabled)"); + log.info("OpenAPI spec generator configured: swagger-core Json/Yaml utilities for spec serialization, Moshi for general JSON, moshih2 HTTP/2 metrics enabled"); } /** @@ -180,24 +150,19 @@ public class OpenApiSpecGenerator { try { OpenAPI spec = generateOpenApiSpec(request); - // Use Jackson for OpenAPI model serialization (required by Swagger library) with enhanced HTTP/2 performance metrics tracking long startTime = System.currentTimeMillis(); - String jsonSpec = objectMapper.writeValueAsString(spec); + String jsonSpec = configuration.isPrettyPrint() ? Json.pretty(spec) : Json.mapper().writeValueAsString(spec); long processingTime = System.currentTimeMillis() - startTime; - // Record performance metrics using moshih2 infrastructure long specSize = jsonSpec.getBytes().length; metrics.recordProcessingStart(requestId, specSize, false); metrics.recordProcessingComplete(requestId, specSize, processingTime); - // Pretty printing is handled by Jackson configuration in constructor - - log.debug("Generated OpenAPI JSON specification (" + (specSize / 1024) + "KB) in " + processingTime + "ms using Jackson with HTTP/2 metrics"); + log.debug("Generated OpenAPI JSON specification (" + (specSize / 1024) + "KB) in " + processingTime + "ms"); return jsonSpec; } catch (Exception e) { - long errorTime = 0; // Error occurred, no meaningful processing time - metrics.recordProcessingError(requestId, e, errorTime); - log.error("Failed to generate OpenAPI JSON using Jackson with HTTP/2 metrics", e); + metrics.recordProcessingError(requestId, e, 0); + log.error("Failed to generate OpenAPI JSON", e); return "{\"error\":\"Failed to generate OpenAPI specification\"}"; } } @@ -210,7 +175,7 @@ public class OpenApiSpecGenerator { try { OpenAPI spec = generateOpenApiSpec(request); long startTime = System.currentTimeMillis(); - String yamlSpec = yamlMapper.writeValueAsString(spec); + String yamlSpec = configuration.isPrettyPrint() ? Yaml.pretty(spec) : Yaml.mapper().writeValueAsString(spec); long processingTime = System.currentTimeMillis() - startTime; long specSize = yamlSpec.getBytes().length; metrics.recordProcessingStart(requestId, specSize, false); @@ -664,7 +629,7 @@ public class OpenApiSpecGenerator { public String getOptimizationRecommendations() { JsonProcessingMetrics.Statistics stats = metrics.getStatistics(); StringBuilder recommendations = new StringBuilder(); - recommendations.append("OpenAPI JSON Processing Performance Analysis (Jackson + HTTP/2 Metrics):\n"); + recommendations.append("OpenAPI JSON Processing Performance Analysis (swagger-core + HTTP/2 Metrics):\n"); recommendations.append(" - Total specifications generated: ").append(stats.getTotalRequests()).append("\n"); recommendations.append(" - Average processing time: ").append(stats.getAverageProcessingTimeMs()).append("ms\n"); recommendations.append(" - Total data processed: ").append(stats.getTotalBytes() / 1024).append("KB\n"); diff --git a/modules/openapi/src/test/java/org/apache/axis2/openapi/OpenApiSpecGeneratorTest.java b/modules/openapi/src/test/java/org/apache/axis2/openapi/OpenApiSpecGeneratorTest.java index 08e80b3e2b..57901b9e22 100644 --- a/modules/openapi/src/test/java/org/apache/axis2/openapi/OpenApiSpecGeneratorTest.java +++ b/modules/openapi/src/test/java/org/apache/axis2/openapi/OpenApiSpecGeneratorTest.java @@ -406,8 +406,9 @@ public class OpenApiSpecGeneratorTest extends TestCase { /** * Test that generated JSON contains no null fields. - * Jackson must be configured with Include.NON_NULL so null-valued model - * fields (e.g. termsOfService, extensions, summary) are omitted entirely. + * swagger-core's ObjectMapperFactory configures NON_NULL on the shared mapper, + * so null-valued model fields (e.g. termsOfService, extensions, summary) are + * omitted entirely without any additional configuration in this module. */ public void testNoNullFieldsInJson() throws Exception { String json = generator.generateOpenApiJson(mockRequest); @@ -416,6 +417,48 @@ public class OpenApiSpecGeneratorTest extends TestCase { assertFalse("JSON output must not contain ':null' entries", json.contains(":null")); } + /** + * Test compact vs pretty JSON output — exercises the isPrettyPrint() branch + * that selects between Json.pretty(spec) and Json.mapper().writeValueAsString(spec). + * Both paths delegate to swagger-core's Json utility. + */ + public void testPrettyAndCompactJsonOutput() throws Exception { + OpenApiConfiguration prettyConfig = new OpenApiConfiguration(); + prettyConfig.setPrettyPrint(true); + OpenApiSpecGenerator prettyGen = new OpenApiSpecGenerator(configurationContext, prettyConfig); + String prettyJson = prettyGen.generateOpenApiJson(mockRequest); + assertTrue("Pretty JSON must contain newlines", prettyJson.contains("\n")); + assertFalse("Pretty JSON must not contain null fields", prettyJson.contains(": null")); + + OpenApiConfiguration compactConfig = new OpenApiConfiguration(); + compactConfig.setPrettyPrint(false); + OpenApiSpecGenerator compactGen = new OpenApiSpecGenerator(configurationContext, compactConfig); + String compactJson = compactGen.generateOpenApiJson(mockRequest); + // Compact output may not have newlines (single-line) but must still be valid JSON + assertTrue("Compact JSON must start with '{'", compactJson.trim().startsWith("{")); + assertFalse("Compact JSON must not contain null fields", compactJson.contains(":null")); + } + + /** + * Test compact vs pretty YAML output — exercises the isPrettyPrint() branch + * that selects between Yaml.pretty(spec) and Yaml.mapper().writeValueAsString(spec). + */ + public void testPrettyAndCompactYamlOutput() throws Exception { + OpenApiConfiguration prettyConfig = new OpenApiConfiguration(); + prettyConfig.setPrettyPrint(true); + OpenApiSpecGenerator prettyGen = new OpenApiSpecGenerator(configurationContext, prettyConfig); + String prettyYaml = prettyGen.generateOpenApiYaml(mockRequest); + assertFalse("Pretty YAML must not start with '{'", prettyYaml.trim().startsWith("{")); + assertTrue("Pretty YAML must contain openapi key", prettyYaml.contains("openapi:")); + + OpenApiConfiguration compactConfig = new OpenApiConfiguration(); + compactConfig.setPrettyPrint(false); + OpenApiSpecGenerator compactGen = new OpenApiSpecGenerator(configurationContext, compactConfig); + String compactYaml = compactGen.generateOpenApiYaml(mockRequest); + assertFalse("Compact YAML must not start with '{'", compactYaml.trim().startsWith("{")); + assertTrue("Compact YAML must contain openapi key", compactYaml.contains("openapi:")); + } + /** * Test that each generated operation carries a non-null requestBody. * All JSON-RPC services accept a POST body; omitting requestBody leaves
