CAMEL-9653: Bean language - Add support for calling purely static methods. 
Improved OGNL error message if invoking method chain not possible due last 
method returned null.


Project: http://git-wip-us.apache.org/repos/asf/camel/repo
Commit: http://git-wip-us.apache.org/repos/asf/camel/commit/71103f59
Tree: http://git-wip-us.apache.org/repos/asf/camel/tree/71103f59
Diff: http://git-wip-us.apache.org/repos/asf/camel/diff/71103f59

Branch: refs/heads/master
Commit: 71103f598e80a6dd4d818050ba1158c4def608d0
Parents: 6fc69f5
Author: Claus Ibsen <davscl...@apache.org>
Authored: Sun Feb 28 10:22:19 2016 +0100
Committer: Claus Ibsen <davscl...@apache.org>
Committed: Sun Feb 28 10:22:35 2016 +0100

----------------------------------------------------------------------
 .../apache/camel/component/bean/BeanInfo.java   | 12 +++++
 .../component/bean/ConstantTypeBeanHolder.java  |  7 ++-
 .../camel/language/bean/BeanExpression.java     | 44 +++++++++++++++--
 .../bean/RuntimeBeanExpressionException.java    |  7 +++
 .../bean/MethodCallStaticMethodTest.java        | 51 ++++++++++++++++++++
 .../camel/language/simple/SimpleTest.java       | 12 ++---
 6 files changed, 121 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/camel/blob/71103f59/camel-core/src/main/java/org/apache/camel/component/bean/BeanInfo.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/component/bean/BeanInfo.java 
b/camel-core/src/main/java/org/apache/camel/component/bean/BeanInfo.java
index d185d1b..4409086 100644
--- a/camel-core/src/main/java/org/apache/camel/component/bean/BeanInfo.java
+++ b/camel-core/src/main/java/org/apache/camel/component/bean/BeanInfo.java
@@ -76,6 +76,7 @@ public class BeanInfo {
     private List<MethodInfo> operationsWithCustomAnnotation = new 
ArrayList<MethodInfo>();
     private List<MethodInfo> operationsWithHandlerAnnotation = new 
ArrayList<MethodInfo>();
     private Map<Method, MethodInfo> methodMap = new HashMap<Method, 
MethodInfo>();
+    private boolean publicConstructors;
 
     static {
         // exclude all java.lang.Object methods as we dont want to invoke them
@@ -122,6 +123,7 @@ public class BeanInfo {
             operationsWithCustomAnnotation = 
beanInfo.operationsWithCustomAnnotation;
             operationsWithHandlerAnnotation = 
beanInfo.operationsWithHandlerAnnotation;
             methodMap = beanInfo.methodMap;
+            publicConstructors = beanInfo.publicConstructors;
             return;
         }
 
@@ -299,6 +301,9 @@ public class BeanInfo {
 
         LOG.trace("Introspecting class: {}", clazz);
 
+        // does the class have any public constructors?
+        publicConstructors = clazz.getConstructors().length > 0;
+
         // favor declared methods, and then filter out duplicate interface 
methods
         List<Method> methods;
         if (Modifier.isPublic(clazz.getModifiers())) {
@@ -1143,6 +1148,13 @@ public class BeanInfo {
     }
 
     /**
+     * Returns whether the bean class has any public constructors.
+     */
+    public boolean hasPublicConstructors() {
+        return publicConstructors;
+    }
+
+    /**
      * Gets the list of methods sorted by A..Z method name.
      *
      * @return the methods.

http://git-wip-us.apache.org/repos/asf/camel/blob/71103f59/camel-core/src/main/java/org/apache/camel/component/bean/ConstantTypeBeanHolder.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/component/bean/ConstantTypeBeanHolder.java
 
b/camel-core/src/main/java/org/apache/camel/component/bean/ConstantTypeBeanHolder.java
index 8c9dc55..a40ed15 100644
--- 
a/camel-core/src/main/java/org/apache/camel/component/bean/ConstantTypeBeanHolder.java
+++ 
b/camel-core/src/main/java/org/apache/camel/component/bean/ConstantTypeBeanHolder.java
@@ -61,7 +61,12 @@ public class ConstantTypeBeanHolder implements 
BeanTypeHolder {
     }
 
     public Object getBean()  {
-        return getBeanInfo().getCamelContext().getInjector().newInstance(type);
+        // only create a bean if we have constructors
+        if (beanInfo.hasPublicConstructors()) {
+            return 
getBeanInfo().getCamelContext().getInjector().newInstance(type);
+        } else {
+            return null;
+        }
     }
 
     public Processor getProcessor() {

http://git-wip-us.apache.org/repos/asf/camel/blob/71103f59/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java 
b/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java
index bb9aeb8..f5834ee 100644
--- 
a/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java
+++ 
b/camel-core/src/main/java/org/apache/camel/language/bean/BeanExpression.java
@@ -114,7 +114,10 @@ public class BeanExpression implements Expression, 
Predicate {
                 ognl.process(exchange);
                 return ognl.getResult();
             } catch (Exception e) {
-                throw new RuntimeBeanExpressionException(exchange, beanName, 
method, e);
+                if (e instanceof RuntimeBeanExpressionException) {
+                    throw (RuntimeBeanExpressionException) e;
+                }
+                throw new RuntimeBeanExpressionException(exchange, 
getBeanName(beanName, beanHolder), method, e);
             }
         } else {
             // regular non ognl invocation
@@ -123,7 +126,10 @@ public class BeanExpression implements Expression, 
Predicate {
                 invoke.process(exchange);
                 return invoke.getResult();
             } catch (Exception e) {
-                throw new RuntimeBeanExpressionException(exchange, beanName, 
method, e);
+                if (e instanceof RuntimeBeanExpressionException) {
+                    throw (RuntimeBeanExpressionException) e;
+                }
+                throw new RuntimeBeanExpressionException(exchange, 
getBeanName(beanName, beanHolder), method, e);
             }
         }
     }
@@ -162,6 +168,17 @@ public class BeanExpression implements Expression, 
Predicate {
         return holder;
     }
 
+    private static String getBeanName(String beanName, BeanHolder beanHolder) {
+        String name = beanName;
+        if (name == null && beanHolder != null && beanHolder.getBean() != 
null) {
+            name = beanHolder.getBean().getClass().getCanonicalName();
+        }
+        if (name == null && beanHolder != null && beanHolder.getBeanInfo() != 
null && beanHolder.getBeanInfo().getType() != null) {
+            name = beanHolder.getBeanInfo().getType().getCanonicalName();
+        }
+        return name;
+    }
+
     /**
      * Invokes a given bean holder. The method name is optional.
      */
@@ -251,21 +268,36 @@ public class BeanExpression implements Expression, 
Predicate {
 
             // loop and invoke each method
             Object beanToCall = beanHolder.getBean();
+            Class<?> beanType = beanHolder.getBeanInfo().getType();
+
             // there must be a bean to call with, we currently does not 
support OGNL expressions on using purely static methods
-            if (beanToCall == null) {
-                throw new IllegalArgumentException("Bean instance is null. 
OGNL bean expressions requires bean instances.");
+            if (beanToCall == null && beanType == null) {
+                throw new IllegalArgumentException("Bean instance and bean 
type is null. OGNL bean expressions requires to have either a bean instance of 
the class name of the bean to use.");
             }
 
             // Split ognl except when this is not a Map, Array
             // and we would like to keep the dots within the key name
             List<String> methods = OgnlHelper.splitOgnl(ognl);
 
+            String methodChain = "";
             for (String methodName : methods) {
-                BeanHolder holder = new ConstantBeanHolder(beanToCall, 
exchange.getContext());
+                BeanHolder holder;
+                if (beanToCall != null) {
+                    holder = new ConstantBeanHolder(beanToCall, 
exchange.getContext());
+                } else if (beanType != null) {
+                    holder = new ConstantTypeBeanHolder(beanType, 
exchange.getContext());
+                } else {
+                    holder = null;
+                }
 
                 // support the null safe operator
                 boolean nullSafe = OgnlHelper.isNullSafeOperator(methodName);
 
+                if (holder == null) {
+                    String name = getBeanName(null, beanHolder);
+                    throw new RuntimeBeanExpressionException(exchange, name, 
ognl, "last method returned null and therefore cannot continue to invoke method 
" + methodName + " on a null instance");
+                }
+
                 // keep up with how far are we doing
                 ognlPath += methodName;
 
@@ -291,6 +323,7 @@ public class BeanExpression implements Expression, 
Predicate {
                     }
 
                     result = invoke.getResult();
+                    methodChain += "." + methodName;
                 }
 
                 // if there was a key then we need to lookup using the key
@@ -305,6 +338,7 @@ public class BeanExpression implements Expression, 
Predicate {
 
                 // prepare for next bean to invoke
                 beanToCall = result;
+                beanType = null;
             }
         }
 

http://git-wip-us.apache.org/repos/asf/camel/blob/71103f59/camel-core/src/main/java/org/apache/camel/language/bean/RuntimeBeanExpressionException.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/main/java/org/apache/camel/language/bean/RuntimeBeanExpressionException.java
 
b/camel-core/src/main/java/org/apache/camel/language/bean/RuntimeBeanExpressionException.java
index 6a846ee..2cd6920 100644
--- 
a/camel-core/src/main/java/org/apache/camel/language/bean/RuntimeBeanExpressionException.java
+++ 
b/camel-core/src/main/java/org/apache/camel/language/bean/RuntimeBeanExpressionException.java
@@ -38,6 +38,13 @@ public class RuntimeBeanExpressionException extends 
RuntimeExpressionException {
         this.method = method;
     }
 
+    public RuntimeBeanExpressionException(Exchange exchange, String beanName, 
String method, String message) {
+        super("Failed to invoke method: " + method + " on " + beanName + " due 
" + message);
+        this.exchange = exchange;
+        this.beanName = beanName;
+        this.method = method;
+    }
+
     public String getBeanName() {
         return beanName;
     }

http://git-wip-us.apache.org/repos/asf/camel/blob/71103f59/camel-core/src/test/java/org/apache/camel/component/bean/MethodCallStaticMethodTest.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/test/java/org/apache/camel/component/bean/MethodCallStaticMethodTest.java
 
b/camel-core/src/test/java/org/apache/camel/component/bean/MethodCallStaticMethodTest.java
new file mode 100644
index 0000000..045fd09
--- /dev/null
+++ 
b/camel-core/src/test/java/org/apache/camel/component/bean/MethodCallStaticMethodTest.java
@@ -0,0 +1,51 @@
+/**
+ * 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;
+
+import org.apache.camel.ContextTestSupport;
+import org.apache.camel.FailedToCreateRouteException;
+import org.apache.camel.NoSuchBeanException;
+import org.apache.camel.builder.RouteBuilder;
+import org.apache.camel.impl.JndiRegistry;
+import org.apache.camel.util.StringHelper;
+
+/**
+ * @version 
+ */
+public class MethodCallStaticMethodTest extends ContextTestSupport {
+
+    public void testStaticMethod() throws Exception {
+        getMockEndpoint("mock:result").expectedHeaderReceived("bar", "\"Hello 
World\"");
+        getMockEndpoint("mock:result").expectedHeaderReceived("foo", "Hello 
World");
+
+        template.sendBodyAndHeader("direct:start", "The body", "bar", "\"Hello 
World\"");
+
+        assertMockEndpointsSatisfied();
+    }
+
+    @Override
+    protected RouteBuilder createRouteBuilder() throws Exception {
+        return new RouteBuilder() {
+            @Override
+            public void configure() throws Exception {
+                from("direct:start")
+                    .setHeader("foo", method(StringHelper.class, 
"removeLeadingAndEndingQuotes(${header.bar})"))
+                    .to("mock:result");
+            }
+        };
+    }
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/camel/blob/71103f59/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
----------------------------------------------------------------------
diff --git 
a/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java 
b/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
index df6d850..3f3374c 100644
--- a/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
+++ b/camel-core/src/test/java/org/apache/camel/language/simple/SimpleTest.java
@@ -1102,7 +1102,7 @@ public class SimpleTest extends LanguageTestSupport {
             assertExpression("${in.body.getLines[0]?.getRating}", "");
             fail("Should have thrown exception");
         } catch (RuntimeBeanExpressionException e) {
-            MethodNotFoundException cause = 
assertIsInstanceOf(MethodNotFoundException.class, e.getCause().getCause());
+            MethodNotFoundException cause = 
assertIsInstanceOf(MethodNotFoundException.class, e.getCause());
             assertEquals("getRating", cause.getMethodName());
         }
     }
@@ -1119,7 +1119,7 @@ public class SimpleTest extends LanguageTestSupport {
             assertExpression("${in.body.lines[0]?.rating}", "");
             fail("Should have thrown exception");
         } catch (RuntimeBeanExpressionException e) {
-            MethodNotFoundException cause = 
assertIsInstanceOf(MethodNotFoundException.class, e.getCause().getCause());
+            MethodNotFoundException cause = 
assertIsInstanceOf(MethodNotFoundException.class, e.getCause());
             assertEquals("rating", cause.getMethodName());
         }
     }
@@ -1144,8 +1144,8 @@ public class SimpleTest extends LanguageTestSupport {
             assertExpression("${in.body.getFriend.getFriend.getName}", "");
             fail("Should have thrown exception");
         } catch (RuntimeBeanExpressionException e) {
-            assertEquals("Failed to invoke method: 
.getFriend.getFriend.getName on null due to: java.lang.NullPointerException", 
e.getMessage());
-            assertIsInstanceOf(NullPointerException.class, e.getCause());
+            assertEquals("Failed to invoke method: 
.getFriend.getFriend.getName on 
org.apache.camel.language.simple.SimpleTest.Animal"
+                    + " due last method returned null and therefore cannot 
continue to invoke method .getName on a null instance", e.getMessage());
         }
     }
 
@@ -1170,8 +1170,8 @@ public class SimpleTest extends LanguageTestSupport {
             assertExpression("${in.body.friend.friend.name}", "");
             fail("Should have thrown exception");
         } catch (RuntimeBeanExpressionException e) {
-            assertEquals("Failed to invoke method: .friend.friend.name on null 
due to: java.lang.NullPointerException", e.getMessage());
-            assertIsInstanceOf(NullPointerException.class, e.getCause());
+            assertEquals("Failed to invoke method: .friend.friend.name on 
org.apache.camel.language.simple.SimpleTest.Animal"
+                    + " due last method returned null and therefore cannot 
continue to invoke method .name on a null instance", e.getMessage());
         }
     }
 

Reply via email to