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()); } }