This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch binding3 in repository https://gitbox.apache.org/repos/asf/camel.git
commit 8d069c7a0fa85bb001573cfbeadf25d631321612 Author: Claus Ibsen <claus.ib...@gmail.com> AuthorDate: Thu Apr 4 07:23:51 2024 +0200 CAMEL-20557: Rest DSL to use openapi spec directly --- ...mHttpRestOpenApiConsumerRestDslBindingTest.java | 5 +-- .../openapi/RestOpenApiComponentConfigurer.java | 12 +++--- .../camel/component/rest/openapi/rest-openapi.json | 2 +- .../DefaultRestOpenapiProcessorStrategy.java | 11 +----- .../rest/openapi/RestOpenApiComponent.java | 25 ++++++++---- .../rest/openapi/RestOpenApiEndpoint.java | 12 +++--- .../rest/openapi/RestOpenApiProcessor.java | 45 ++++++++++++++++++---- 7 files changed, 72 insertions(+), 40 deletions(-) diff --git a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerRestDslBindingTest.java b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerRestDslBindingTest.java index 548cfb9635f..ca10f6bd29c 100644 --- a/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerRestDslBindingTest.java +++ b/components/camel-platform-http-vertx/src/test/java/org/apache/camel/component/platform/http/vertx/PlatformHttpRestOpenApiConsumerRestDslBindingTest.java @@ -74,9 +74,8 @@ public class PlatformHttpRestOpenApiConsumerRestDslBindingTest { context.addRoutes(new RouteBuilder() { @Override public void configure() { - // TODO: make it easy to set binding package name - RestOpenApiComponent rac = context.getComponent("rest-openapi", RestOpenApiComponent.class); - rac.setBindingPackageName(Pet.class.getPackageName()); + // TODO: make it easy to set binding package name in rest configuration + context.getCamelContextExtension().setBasePackageScan("org.apache.camel.component.platform.http.vertx.model"); restConfiguration().bindingMode(RestBindingMode.json); diff --git a/components/camel-rest-openapi/src/generated/java/org/apache/camel/component/rest/openapi/RestOpenApiComponentConfigurer.java b/components/camel-rest-openapi/src/generated/java/org/apache/camel/component/rest/openapi/RestOpenApiComponentConfigurer.java index 4b19529401f..a8f875d6e07 100644 --- a/components/camel-rest-openapi/src/generated/java/org/apache/camel/component/rest/openapi/RestOpenApiComponentConfigurer.java +++ b/components/camel-rest-openapi/src/generated/java/org/apache/camel/component/rest/openapi/RestOpenApiComponentConfigurer.java @@ -27,8 +27,8 @@ public class RestOpenApiComponentConfigurer extends PropertyConfigurerSupport im case "autowiredEnabled": target.setAutowiredEnabled(property(camelContext, boolean.class, value)); return true; case "basepath": case "basePath": target.setBasePath(property(camelContext, java.lang.String.class, value)); return true; - case "bindingpackagename": - case "bindingPackageName": target.setBindingPackageName(property(camelContext, java.lang.String.class, value)); return true; + case "bindingpackagescan": + case "bindingPackageScan": target.setBindingPackageScan(property(camelContext, java.lang.String.class, value)); return true; case "bridgeerrorhandler": case "bridgeErrorHandler": target.setBridgeErrorHandler(property(camelContext, boolean.class, value)); return true; case "clientrequestvalidation": @@ -69,8 +69,8 @@ public class RestOpenApiComponentConfigurer extends PropertyConfigurerSupport im case "autowiredEnabled": return boolean.class; case "basepath": case "basePath": return java.lang.String.class; - case "bindingpackagename": - case "bindingPackageName": return java.lang.String.class; + case "bindingpackagescan": + case "bindingPackageScan": return java.lang.String.class; case "bridgeerrorhandler": case "bridgeErrorHandler": return boolean.class; case "clientrequestvalidation": @@ -112,8 +112,8 @@ public class RestOpenApiComponentConfigurer extends PropertyConfigurerSupport im case "autowiredEnabled": return target.isAutowiredEnabled(); case "basepath": case "basePath": return target.getBasePath(); - case "bindingpackagename": - case "bindingPackageName": return target.getBindingPackageName(); + case "bindingpackagescan": + case "bindingPackageScan": return target.getBindingPackageScan(); case "bridgeerrorhandler": case "bridgeErrorHandler": return target.isBridgeErrorHandler(); case "clientrequestvalidation": diff --git a/components/camel-rest-openapi/src/generated/resources/META-INF/org/apache/camel/component/rest/openapi/rest-openapi.json b/components/camel-rest-openapi/src/generated/resources/META-INF/org/apache/camel/component/rest/openapi/rest-openapi.json index 41b59cebba5..b9e75277494 100644 --- a/components/camel-rest-openapi/src/generated/resources/META-INF/org/apache/camel/component/rest/openapi/rest-openapi.json +++ b/components/camel-rest-openapi/src/generated/resources/META-INF/org/apache/camel/component/rest/openapi/rest-openapi.json @@ -29,7 +29,7 @@ "bridgeErrorHandler": { "index": 3, "kind": "property", "displayName": "Bridge Error Handler", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Allows for bridging the consumer to the Camel routing Error Handler, which mean any exceptions (if possible) occurred while the Camel consumer is trying to pickup incoming messages, or the like [...] "clientRequestValidation": { "index": 4, "kind": "property", "displayName": "Client Request Validation", "group": "consumer", "label": "consumer", "required": false, "type": "boolean", "javaType": "boolean", "deprecated": false, "autowired": false, "secret": false, "defaultValue": false, "description": "Whether to enable validation of the client request to check if the incoming request is valid according to the OpenAPI specification" }, "missingOperation": { "index": 5, "kind": "property", "displayName": "Missing Operation", "group": "consumer", "label": "consumer", "required": false, "type": "string", "javaType": "java.lang.String", "enum": [ "fail", "ignore", "mock" ], "deprecated": false, "autowired": false, "secret": false, "defaultValue": "fail", "description": "Whether the consumer should fail,ignore or return a mock response for OpenAPI operations that are not mapped to a corresponding route." }, - "bindingPackageName": { "index": 6, "kind": "property", "displayName": "Binding Package Name", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Java package name where POJO classes are located when using binding mode is enabled for JSon or XML. Multiple package names can be separated by comma." }, + "bindingPackageScan": { "index": 6, "kind": "property", "displayName": "Binding Package Scan", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Package name to use as base (offset) for classpath scanning of POJO classes are located when using binding mode is enabled for JSon or XML. Multiple package names can be separated by comma." }, "consumerComponentName": { "index": 7, "kind": "property", "displayName": "Consumer Component Name", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "description": "Name of the Camel component that will service the requests. The component must be present in Camel registry and it must implement RestOpenApiConsumerFactory service provider interfac [...] "mockIncludePattern": { "index": 8, "kind": "property", "displayName": "Mock Include Pattern", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "string", "javaType": "java.lang.String", "deprecated": false, "autowired": false, "secret": false, "defaultValue": "classpath:camel-mock\/**", "description": "Used for inclusive filtering of mock data from directories. The pattern is using Ant-path style pattern. Multiple patterns can be specified sepa [...] "restOpenapiProcessorStrategy": { "index": 9, "kind": "property", "displayName": "Rest Openapi Processor Strategy", "group": "consumer (advanced)", "label": "consumer,advanced", "required": false, "type": "object", "javaType": "org.apache.camel.component.rest.openapi.RestOpenapiProcessorStrategy", "deprecated": false, "autowired": false, "secret": false, "description": "To use a custom strategy for how to process Rest DSL requests" }, diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java index 414a5efb1e3..d98723a1ae3 100644 --- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java +++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/DefaultRestOpenapiProcessorStrategy.java @@ -63,7 +63,6 @@ public class DefaultRestOpenapiProcessorStrategy extends ServiceSupport private String component = "direct"; private String missingOperation; private String mockIncludePattern; - private String apiContextPath; private final List<String> uris = new ArrayList<>(); @Override @@ -280,10 +279,12 @@ public class DefaultRestOpenapiProcessorStrategy extends ServiceSupport this.component = component; } + @Override public String getMissingOperation() { return missingOperation; } + @Override public void setMissingOperation(String missingOperation) { this.missingOperation = missingOperation; } @@ -298,14 +299,6 @@ public class DefaultRestOpenapiProcessorStrategy extends ServiceSupport this.mockIncludePattern = mockIncludePattern; } - public String getApiContextPath() { - return apiContextPath; - } - - public void setApiContextPath(String apiContextPath) { - this.apiContextPath = apiContextPath; - } - @Override protected void doInit() throws Exception { producerCache = new DefaultProducerCache(this, getCamelContext(), 1000); diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiComponent.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiComponent.java index b501c5eb372..c5d5f85e123 100644 --- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiComponent.java +++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiComponent.java @@ -121,8 +121,8 @@ public final class RestOpenApiComponent extends DefaultComponent implements SSLC label = "producer,advanced") private String produces; @Metadata(label = "consumer,advanced", - description = "Java package name where POJO classes are located when using binding mode is enabled for JSon or XML. Multiple package names can be separated by comma.") - private String bindingPackageName; + description = "Package name to use as base (offset) for classpath scanning of POJO classes are located when using binding mode is enabled for JSon or XML. Multiple package names can be separated by comma.") + private String bindingPackageScan; @Metadata(label = "consumer", description = "Whether to enable validation of the client request to check if the incoming request is valid according to the OpenAPI specification") private boolean clientRequestValidation; @@ -157,7 +157,7 @@ public final class RestOpenApiComponent extends DefaultComponent implements SSLC throws Exception { RestOpenApiEndpoint endpoint = new RestOpenApiEndpoint(uri, remaining, this, parameters); endpoint.setApiContextPath(getApiContextPath()); - endpoint.setBindingPackageName(getBindingPackageName()); + endpoint.setBindingPackageScan(getBindingPackageScan()); endpoint.setClientRequestValidation(isClientRequestValidation()); endpoint.setRequestValidationEnabled(isRequestValidationEnabled()); endpoint.setRestOpenapiProcessorStrategy(getRestOpenapiProcessorStrategy()); @@ -167,6 +167,17 @@ public final class RestOpenApiComponent extends DefaultComponent implements SSLC return endpoint; } + @Override + protected void doInit() throws Exception { + super.doInit(); + + // use package scan for binding model classes + String current = getCamelContext().getCamelContextExtension().getBasePackageScan(); + if (current != null && bindingPackageScan == null) { + bindingPackageScan = current; + } + } + public String getBasePath() { return basePath; } @@ -289,11 +300,11 @@ public final class RestOpenApiComponent extends DefaultComponent implements SSLC this.clientRequestValidation = clientRequestValidation; } - public String getBindingPackageName() { - return bindingPackageName; + public String getBindingPackageScan() { + return bindingPackageScan; } - public void setBindingPackageName(String bindingPackageName) { - this.bindingPackageName = bindingPackageName; + public void setBindingPackageScan(String bindingPackageScan) { + this.bindingPackageScan = bindingPackageScan; } } diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java index d2ee8cfe72b..d955bee0773 100644 --- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java +++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiEndpoint.java @@ -150,8 +150,8 @@ public final class RestOpenApiEndpoint extends DefaultEndpoint { defaultValueNote = "By default loads `openapi.json` file", label = "common") private String specificationUri; @Metadata(label = "consumer,advanced", - description = "Java package name where POJO classes are located when using binding mode is enabled for JSon or XML. Multiple package names can be separated by comma.") - private String bindingPackageName; + description = "Package name to use as base (offset) for classpath scanning of POJO classes are located when using binding mode is enabled for JSon or XML. Multiple package names can be separated by comma.") + private String bindingPackageScan; @UriParam(label = "consumer", description = "Whether to enable validation of the client request to check if the incoming request is valid according to the OpenAPI specification") private boolean clientRequestValidation; @@ -464,12 +464,12 @@ public final class RestOpenApiEndpoint extends DefaultEndpoint { this.apiContextPath = apiContextPath; } - public String getBindingPackageName() { - return bindingPackageName; + public String getBindingPackageScan() { + return bindingPackageScan; } - public void setBindingPackageName(String bindingPackageName) { - this.bindingPackageName = bindingPackageName; + public void setBindingPackageScan(String bindingPackageScan) { + this.bindingPackageScan = bindingPackageScan; } Producer createProducerFor( diff --git a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java index 42bac9d22e9..a3801378d06 100644 --- a/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java +++ b/components/camel-rest-openapi/src/main/java/org/apache/camel/component/rest/openapi/RestOpenApiProcessor.java @@ -18,13 +18,18 @@ package org.apache.camel.component.rest.openapi; import java.util.ArrayList; import java.util.Arrays; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.stream.Collectors; +import jakarta.xml.bind.annotation.XmlRootElement; + +import com.fasterxml.jackson.annotation.JsonTypeName; import io.swagger.v3.oas.models.OpenAPI; import io.swagger.v3.oas.models.Operation; import io.swagger.v3.oas.models.media.Content; @@ -35,12 +40,16 @@ import org.apache.camel.CamelContext; import org.apache.camel.CamelContextAware; import org.apache.camel.Exchange; import org.apache.camel.Processor; +import org.apache.camel.StartupStep; import org.apache.camel.http.base.HttpHelper; import org.apache.camel.spi.DataType; import org.apache.camel.spi.DataTypeAware; +import org.apache.camel.spi.PackageScanClassResolver; import org.apache.camel.spi.RestConfiguration; +import org.apache.camel.spi.StartupStepRecorder; import org.apache.camel.support.ExchangeHelper; import org.apache.camel.support.MessageHelper; +import org.apache.camel.support.PluginHelper; import org.apache.camel.support.RestConsumerContextPathMatcher; import org.apache.camel.support.processor.DelegateAsyncProcessor; import org.apache.camel.support.processor.RestBindingAdvice; @@ -67,6 +76,8 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came private final String apiContextPath; private final List<RestConsumerContextPathMatcher.ConsumerPath<Operation>> paths = new ArrayList<>(); private final RestOpenapiProcessorStrategy restOpenapiProcessorStrategy; + private final AtomicBoolean packageScanInit = new AtomicBoolean(); + private final Set<Class<?>> scannedClasses = new HashSet<>(); public RestOpenApiProcessor(RestOpenApiEndpoint endpoint, OpenAPI openAPI, String basePath, String apiContextPath, Processor processor, RestOpenapiProcessorStrategy restOpenapiProcessorStrategy) { @@ -334,6 +345,7 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came paths.add(new RestOpenApiConsumerPath(v, path, o.getValue(), binding)); } } + scannedClasses.clear(); // no longer needed ServiceHelper.buildService(restOpenapiProcessorStrategy); } @@ -477,18 +489,35 @@ public class RestOpenApiProcessor extends DelegateAsyncProcessor implements Came return null; } + if (packageScanInit.compareAndSet(false, true)) { + String base = endpoint.getBindingPackageScan(); + if (base != null) { + StartupStepRecorder recorder = camelContext.getCamelContextExtension().getStartupStepRecorder(); + StartupStep step = recorder.beginStep(RestOpenApiProcessor.class, "openapi-binding", + "OpenAPI binding classes package scan"); + String[] pcks = base.split(","); + PackageScanClassResolver resolver = PluginHelper.getPackageScanClassResolver(camelContext); + // discover POJO generated classes for JSon/XML + scannedClasses.addAll(resolver.findAnnotated(JsonTypeName.class, pcks)); + scannedClasses.addAll(resolver.findAnnotated(XmlRootElement.class, pcks)); + if (!scannedClasses.isEmpty()) { + LOG.info("Binding package scan found {} classes in packages: {}", scannedClasses.size(), base); + } + recorder.endStep(step); + } + } + // must refer to a class name, so upper case ref = Character.toUpperCase(ref.charAt(0)) + ref.substring(1); - if (endpoint.getBindingPackageName() != null) { - for (String pck : endpoint.getBindingPackageName().split(",")) { - String fqn = pck + "." + ref; - Class<?> clazz = camelContext.getClassResolver().resolveClass(fqn); - if (clazz != null) { - return clazz; - } + // find class via simple name + for (Class<?> clazz : scannedClasses) { + if (clazz.getSimpleName().equals(ref)) { + return clazz; } } - return camelContext.getClassResolver().resolveClass(ref); + + // class not found + return null; } @Override