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

Reply via email to