This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git

commit bd857c0a18e18b4967acdb0c723b1d4b16def0c9
Author: Claus Ibsen <[email protected]>
AuthorDate: Tue Apr 4 12:16:05 2023 +0200

    CAMEL-19244: camel-bean - Add support for declaring type in parameter 
values using 'type.class value' style
---
 .../bean/AmbiguousMethodCallException.java         | 17 ++++-
 .../org/apache/camel/component/bean/BeanInfo.java  | 30 ++++++---
 .../apache/camel/model/ProcessorDefinition.java    | 10 ++-
 .../component/bean/BeanOverloadedMethodTest.java   |  2 +-
 .../bean/BeanOverloadsWithAssignableParamTest.java |  2 +-
 .../bean/issues/BeanParameterTypeAndValueTest.java | 74 ++++++++++++++++++++++
 .../modules/ROOT/pages/bean-binding.adoc           | 20 +++++-
 7 files changed, 138 insertions(+), 17 deletions(-)

diff --git 
a/components/camel-bean/src/main/java/org/apache/camel/component/bean/AmbiguousMethodCallException.java
 
b/components/camel-bean/src/main/java/org/apache/camel/component/bean/AmbiguousMethodCallException.java
index 2ebe949c515..1cda5179744 100644
--- 
a/components/camel-bean/src/main/java/org/apache/camel/component/bean/AmbiguousMethodCallException.java
+++ 
b/components/camel-bean/src/main/java/org/apache/camel/component/bean/AmbiguousMethodCallException.java
@@ -17,6 +17,7 @@
 package org.apache.camel.component.bean;
 
 import java.util.Collection;
+import java.util.StringJoiner;
 
 import org.apache.camel.Exchange;
 import org.apache.camel.RuntimeExchangeException;
@@ -30,7 +31,7 @@ public class AmbiguousMethodCallException extends 
RuntimeExchangeException {
     private final Collection<MethodInfo> methods;
 
     public AmbiguousMethodCallException(Exchange exchange, 
Collection<MethodInfo> methods) {
-        super("Ambiguous method invocations possible: " + methods, exchange);
+        super(createMessage(methods), exchange);
         this.methods = methods;
     }
 
@@ -40,4 +41,18 @@ public class AmbiguousMethodCallException extends 
RuntimeExchangeException {
     public Collection<MethodInfo> getMethods() {
         return methods;
     }
+
+    private static String createMessage(Collection<MethodInfo> methods) {
+        Class<?> clazz = null;
+        StringJoiner sj = new StringJoiner("\n\t");
+        for (MethodInfo mi : methods) {
+            if (clazz == null) {
+                clazz = mi.getMethod().getDeclaringClass();
+                sj.add("\tClass: " + clazz.getName());
+            }
+            sj.add("\t" + mi.getMethod().toGenericString());
+        }
+        return "Ambiguous method invocations possible:\n" + sj + "\n\n";
+    }
+
 }
diff --git 
a/components/camel-bean/src/main/java/org/apache/camel/component/bean/BeanInfo.java
 
b/components/camel-bean/src/main/java/org/apache/camel/component/bean/BeanInfo.java
index b918711dc1a..c0f59bf911f 100644
--- 
a/components/camel-bean/src/main/java/org/apache/camel/component/bean/BeanInfo.java
+++ 
b/components/camel-bean/src/main/java/org/apache/camel/component/bean/BeanInfo.java
@@ -244,7 +244,7 @@ public class BeanInfo {
                         }
                     }
 
-                    if (methodInfo == null || name != null && 
!name.equals(methodInfo.getMethod().getName())) {
+                    if (methodInfo == null || 
!name.equals(methodInfo.getMethod().getName())) {
                         throw new AmbiguousMethodCallException(exchange, 
methods);
                     }
                 } else {
@@ -1091,7 +1091,7 @@ public class BeanInfo {
         String types = StringHelper.betweenOuterPair(methodName, '(', ')');
         if (org.apache.camel.util.ObjectHelper.isNotEmpty(types)) {
             // we must qualify based on types to match method
-            String[] parameters = StringQuoteHelper.splitSafeQuote(types, ',');
+            String[] parameters = StringQuoteHelper.splitSafeQuote(types, ',', 
true, true);
             Class<?>[] parameterTypes = null;
             Iterator<?> it = ObjectHelper.createIterator(parameters);
             for (int i = 0; i < method.getParameterCount(); i++) {
@@ -1107,18 +1107,23 @@ public class BeanInfo {
                     }
                     // trim the type
                     qualifyType = qualifyType.trim();
+                    String value = qualifyType;
+                    int pos1 = qualifyType.indexOf(' ');
+                    int pos2 = qualifyType.indexOf(".class");
+                    if (pos1 != -1 && pos2 != -1 && pos1 > pos2) {
+                        // a parameter can include type in the syntax to help 
with choosing correct method
+                        // therefore we need to check if type is provided in 
syntax (name.class value, name2.class value2, ...)
+                        value = qualifyType.substring(pos1);
+                        value = value.trim();
+                        qualifyType = qualifyType.substring(0, pos1);
+                        qualifyType = qualifyType.trim();
+                    }
 
                     if ("*".equals(qualifyType)) {
                         // * is a wildcard so we accept and match that 
parameter type
                         continue;
                     }
 
-                    if 
(BeanHelper.isValidParameterValue(getCamelContext().getClassResolver(), 
qualifyType)) {
-                        // its a parameter value, so continue to next parameter
-                        // as we should only check for FQN/type parameters
-                        continue;
-                    }
-
                     // if qualify type indeed is a class, then it must be 
assignable with the parameter type
                     Boolean assignable = 
BeanHelper.isAssignableToExpectedType(getCamelContext().getClassResolver(),
                             qualifyType, parameterType);
@@ -1127,6 +1132,13 @@ public class BeanInfo {
                         return false;
                     }
 
+                    if (!qualifyType.endsWith(".class")
+                            && 
!BeanHelper.isValidParameterValue(getCamelContext().getClassResolver(), value)) 
{
+                        // its a parameter value, so continue to next parameter
+                        // as we should only check for FQN/type parameters
+                        return false;
+                    }
+
                 } else {
                     // there method has more parameters than was specified in 
the method name qualifiers
                     return false;
@@ -1157,7 +1169,7 @@ public class BeanInfo {
     }
 
     /**
-     * Do we have a method with the given name.
+     * Do we have a method with the given name?
      * <p/>
      * Shorthand method names for getters is supported, so you can pass in eg 
'name' and Camel will can find the real
      * 'getName' method instead.
diff --git 
a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
 
b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
index 5e1519d9b97..a58bb3afb76 100644
--- 
a/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
+++ 
b/core/camel-core-model/src/main/java/org/apache/camel/model/ProcessorDefinition.java
@@ -2245,14 +2245,20 @@ public abstract class ProcessorDefinition<Type extends 
ProcessorDefinition<Type>
      * <a href="http://camel.apache.org/message-translator.html";>Message 
Translator EIP:</a> Adds a bean which is
      * invoked which could be a final destination, or could be a 
transformation in a pipeline
      *
-     * @param  bean   the bean to invoke, or a reference to a bean if the type 
is a String
+     * @param  bean   the bean to invoke (if String then a reference to a 
bean, prefix with type: to specify FQN java
+     *                class)
      * @param  method the method name to invoke on the bean (can be used to 
avoid ambiguity)
      * @return        the builder
      */
     public Type bean(Object bean, String method) {
         BeanDefinition answer = new BeanDefinition();
         if (bean instanceof String) {
-            answer.setRef((String) bean);
+            String str = (String) bean;
+            if (str.startsWith("type:")) {
+                answer.setBeanType(str.substring(5));
+            } else {
+                answer.setRef(str);
+            }
         } else {
             answer.setBean(bean);
         }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanOverloadedMethodTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanOverloadedMethodTest.java
index 1655eb68e9d..9753ef5751e 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanOverloadedMethodTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanOverloadedMethodTest.java
@@ -36,7 +36,7 @@ public class BeanOverloadedMethodTest extends 
ContextTestSupport {
     }
 
     @Test
-    public void testHelloOverloadedHeString() throws Exception {
+    public void testHelloOverloadedString() throws Exception {
         context.addRoutes(new RouteBuilder() {
             @Override
             public void configure() throws Exception {
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanOverloadsWithAssignableParamTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanOverloadsWithAssignableParamTest.java
index 8b6e11023b8..b572995abb8 100644
--- 
a/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanOverloadsWithAssignableParamTest.java
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/bean/BeanOverloadsWithAssignableParamTest.java
@@ -36,7 +36,7 @@ public class BeanOverloadsWithAssignableParamTest extends 
ContextTestSupport {
         return new RouteBuilder() {
             @Override
             public void configure() throws Exception {
-                from("direct:stringParam").bean(new MyOtherFooBean(), 
"toString(String)").to("mock:stringParamResult");
+                from("direct:stringParam").bean(new MyOtherFooBean(), 
"toString(String.class)").to("mock:stringParamResult");
             }
         };
     }
diff --git 
a/core/camel-core/src/test/java/org/apache/camel/component/bean/issues/BeanParameterTypeAndValueTest.java
 
b/core/camel-core/src/test/java/org/apache/camel/component/bean/issues/BeanParameterTypeAndValueTest.java
new file mode 100644
index 00000000000..38673ad1048
--- /dev/null
+++ 
b/core/camel-core/src/test/java/org/apache/camel/component/bean/issues/BeanParameterTypeAndValueTest.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.component.bean.issues;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.builder.RouteBuilder;
+import org.junit.jupiter.api.Test;
+
+public class BeanParameterTypeAndValueTest extends ContextTestSupport {
+
+    @Test
+    public void testBean() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("123");
+        
getMockEndpoint("mock:result").message(0).body().isInstanceOf(Integer.class);
+
+        template.sendBody("direct:bean", "Hello World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testBean2() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("44");
+        
getMockEndpoint("mock:result").message(0).body().isInstanceOf(Integer.class);
+
+        template.sendBody("direct:bean2", "Hi World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Test
+    public void testSimple() throws Exception {
+        getMockEndpoint("mock:result").expectedBodiesReceived("7");
+        
getMockEndpoint("mock:result").message(0).body().isInstanceOf(Integer.class);
+
+        template.sendBody("direct:simple", "Bye World");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:bean")
+                        .bean(Math.class, "abs(int.class -123)")
+                        .to("mock:result");
+
+                from("direct:bean2")
+                        .bean("type:java.lang.Math", "abs(int.class -44)")
+                        .to("mock:result");
+
+                from("direct:simple")
+                        
.setBody().simple("${bean:type:java.lang.Math?method=abs(int.class -7)}")
+                        .to("mock:result");
+            }
+        };
+    }
+}
diff --git a/docs/user-manual/modules/ROOT/pages/bean-binding.adoc 
b/docs/user-manual/modules/ROOT/pages/bean-binding.adoc
index 6236a3334e4..f5dce7d1489 100644
--- a/docs/user-manual/modules/ROOT/pages/bean-binding.adoc
+++ b/docs/user-manual/modules/ROOT/pages/bean-binding.adoc
@@ -369,11 +369,25 @@ the FQN, and *not* the simple name "MyOrder", then follow 
this example:
 .bean(OrderService.class, "doSomething(com.foo.MyOrder.class)")
 ----
 
-Camel currently only supports either specifying parameter binding or
+=== Declaring parameter type and value
+
+*Available as of Camel 4.0*
+
+Camel 3.x only supports either specifying parameter binding or
 type per parameter in the method name option. You *cannot* specify both
-at the same time, such as
+at the same time, such as:
+
+[source,text]
+----
+doSomething(com.foo.MyOrder.class ${body}, boolean ${header.high}, int 123)
+----
+
+However, we have implemented support for this in Camel 4,
+where you can declare both using _name.class value_ syntax as shown:
 
 [source,text]
 ----
-doSomething(com.foo.MyOrder.class ${body}, boolean ${header.high})
+doSomething(com.foo.MyOrder.class ${body}, boolean.class ${header.high}, 
int.class 123)
 ----
+
+Notice that you *MUST* use `name.class` when declaring the type, also for 
String, int, boolean etc.

Reply via email to