The Apache Camel framework offers powerful mechanisms to determine which method of your beans to invoke. These are described here in the Camel documentation: http://camel.apache.org/bean-binding.html.
In practice, you have your beans defined with spring and declare their invocation in the Camel route definition either with „beanRef“:
[codesyntax lang="java"] .beanRef("orderService", "doSomething") [/codesyntax]
or with „to“:
[codesyntax lang="java"] .to("bean:orderService?method=doSomething") [/codesyntax]
This has some disadvantages, for which we want to suggest a utility classes as a solution:
- Your IDE cannot find the usages of the method invocation of method „doSomething“
- Your IDE cannot assist you during refactorings, e.g. when renaming the method or changing the signature
- Neither your IDE nor the compiler can complain about typos in method names. You will find typos during runtime, not during compile-time.
Replacing the calls with process() is no solution, because you still want the Camel-comfort of the powerful bean-binding.
Using class BeanRef
To use strongly typed invocations in your camel route definitions, that avoid the disadvantages, you can use the utility class BeanRef, with a little more verbose syntax:
[codesyntax lang="java"]// ... from("activemq:orders").to(doSomething()).to("file:confirm.txt") // ... private String doSomething() { return new BeanRef<OrderService>(OrderService.class) { protected void call() { bean.doSomething(null); } }.uri(); } [/codesyntax]
This results in a single URI:
[codesyntax lang="java"]from("activemq:orders").to("bean:orderService?method=doSomething").to("file:confirm.txt") [/codesyntax]
It does not matter, which parameter values you are using to invoke „doSomething“, but you need to pass the parameters of the required time, so that the compiler is satisfied. The real method of your bean will not be invoked with this parameter!
When your route calls more myOrderBookService than one method on the service bean, you can call the methods inside call() in their adequate sequence:
[codesyntax lang="java"]from("activemq:orders").to(new BeanRef<OrderService>(OrderService.class) { protected void call() { bean.validate(bean.parse(null)); bean.doSomething(null); } }.uris()).to(„file:confirm.txt“) [/codesyntax]
This results in an array of URIs:
[codesyntax lang="java"]from("activemq:orders").to( "bean:orderService?method=parse", "bean:orderService?method=validate", "bean:orderService?method=doSomething").to("file:confirm.txt") [/codesyntax]
When your service bean has a non-standard bean-name, you can use the second constructor of BeanRef to tell the bean-name you are using:
[codesyntax lang="java"]private String doSomething() { return new BeanRef<SmartWebService>( SmartWebService.class, "myOrderBookService"){ protected void call() { bean.doSomething (null); } }.uri(); } [/codesyntax]
This produces the URI:
[codesyntax lang="java"]"bean:myOrderBookService?method=doSomething" [/codesyntax]
How does it work?
During definition of the camel route, the call() method of the BeanRef-instance will be invoked with a proxy, that implements the interface of your service bean. The proxy just collects the method invocations and the uri()/uris() methods return the URI-strings of all method invocations.
This is he whole source of class BeanRef:
[codesyntax lang="java"]package de.viaboxx.camel; import java.lang.reflect.*; import java.util.*; public abstract class BeanRef { private final Class beanType; private final String beanName; protected T bean; public BeanRef(Class beanType) { this.beanType = beanType; this.beanName = uncapitalize(beanType.getSimpleName()); } public BeanRef(Class beanType, String beanName) { this.beanType = beanType; this.beanName = beanName; } public static String uncapitalize(String str) { int strLen; if (str == null || (strLen = str.length()) == 0) { return str; } int i = 0; while (i < str.length() && Character.isUpperCase(str.charAt(i))) { i++; } if (i == 0) { return str; } else { if (i > 1) i--; return new StringBuilder(strLen) .append(str.substring(0, i).toLowerCase()) .append(str.substring(i)) .toString(); } } protected abstract void call() throws Throwable; public String[] uris() { Collector collector = evaluate(); return toUris(collector); } private String[] toUris(Collector collector) { String[] uris = new String[collector.calls.size()]; for (int i = 0; i < collector.calls.size(); i++) { uris[i] = toUri(collector, i); } return uris; } public String uri() { Collector collector = evaluate(); if (collector.calls.size() != 1) { throw new RuntimeException( "uri() requires a single call, use uris() instead for " + Arrays.toString(toUris(collector))); } return toUri(collector, 0); } public String method() { Collector collector = evaluate(); if (collector.calls.size() != 1) { throw new RuntimeException( "method() requires a single call, at " + Arrays.toString(toUris(collector))); } return collector.calls.get(0); } public String[] methods() { Collector collector = evaluate(); return collector.calls.toArray(new String[collector.calls.size()]); } private Collector evaluate() { Collector collector = new Collector(); //noinspection unchecked bean = (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{beanType}, collector); try { call(); } catch (Throwable throwable) { throw new RuntimeException("Unexpected exception during configuration", throwable); } bean = null; return collector; } public String getBeanName() { return beanName; } private String toUri(Collector collector, int i) { return "bean:" + getBeanName() + "?method=" + collector.calls.get(i); } private static class Collector implements InvocationHandler { List calls = new LinkedList(); public Object invoke(Object o, Method method, Object[] objects) throws Throwable { calls.add(method.getName()); return null; } } } [/codesyntax]
If you are using Groovy instead of Java, you can easily think of a variant of BeanRef using Closures, that avoid most of the syntax overhead of Java, if you want to invoke your beans that way.