10.11. Service Activator
Overview
The service activator pattern, shown in Figure 10.9, “Service Activator Pattern”, describes the scenario where a service's operations are invoked in response to an incoming request message. The service activator identifies which operation to call and extracts the data to use as the operation's parameters. Finally, the service activator invokes an operation using the data extracted from the message. The operation invocation can be either oneway (request only) or two-way (request/reply).
Figure 10.9. Service Activator Pattern
In many respects, a service activator resembles a conventional remote procedure call (RPC), where operation invocations are encoded as messages. The main difference is that a service activator needs to be more flexible. An RPC framework standardizes the request and reply message encodings (for example, Web service operations are encoded as SOAP messages), whereas a service activator typically needs to improvise the mapping between the messaging system and the service's operations.
Bean integration
The main mechanism that Apache Camel provides to support the service activator pattern is bean integration. Bean integration provides a general framework for mapping incoming messages to method invocations on Java objects. For example, the Java fluent DSL provides the processors
bean()
and beanRef()
that you can insert into a route to invoke methods on a registered Java bean. The detailed mapping of message data to Java method parameters is determined by the bean binding, which can be implemented by adding annotations to the bean class.
For example, consider the following route which calls the Java method,
BankBean.getUserAccBalance()
, to service requests incoming on a JMS/ActiveMQ queue:
from("activemq:BalanceQueries") .setProperty("userid", xpath("/Account/BalanceQuery/UserID").stringResult()) .beanRef("bankBean", "getUserAccBalance") .to("velocity:file:src/scripts/acc_balance.vm") .to("activemq:BalanceResults");
The messages pulled from the ActiveMQ endpoint,
activemq:BalanceQueries
, have a simple XML format that provides the user ID of a bank account. For example:
<?xml version='1.0' encoding='UTF-8'?> <Account> <BalanceQuery> <UserID>James.Strachan</UserID> </BalanceQuery> </Account>
The first processor in the route,
setProperty()
, extracts the user ID from the In message and stores it in the userid
exchange property. This is preferable to storing it in a header, because the In headers are not available after invoking the bean.
The service activation step is performed by the
beanRef()
processor, which binds the incoming message to the getUserAccBalance()
method on the Java object identified by the bankBean
bean ID. The following code shows a sample implementation of the BankBean
class:
package tutorial; import org.apache.camel.language.XPath; public class BankBean { public int getUserAccBalance(@XPath("/Account/BalanceQuery/UserID") String user) { if (user.equals("James.Strachan")) { return 1200; } else { return 0; } } }
Where the binding of message data to method parameter is enabled by the
@XPath
annotation, which injects the content of the UserID
XML element into the user
method parameter. On completion of the call, the return value is inserted into the body of the Out message which is then copied into the In message for the next step in the route. In order for the bean to be accessible to the beanRef()
processor, you must instantiate an instance in Spring XML. For example, you can add the following lines to the META-INF/spring/camel-context.xml
configuration file to instantiate the bean:
<?xml version="1.0" encoding="UTF-8"?> <beans ... > ... <bean id="bankBean" class="tutorial.BankBean"/> </beans>
Where the bean ID,
bankBean
, identifes this bean instance in the registry.
The output of the bean invocation is injected into a Velocity template, to produce a properly formatted result message. The Velocity endpoint,
velocity:file:src/scripts/acc_balance.vm
, specifies the location of a velocity script with the following contents:
<?xml version='1.0' encoding='UTF-8'?> <Account> <BalanceResult> <UserID>${exchange.getProperty("userid")}</UserID> <Balance>${body}</Balance> </BalanceResult> </Account>
The exchange instance is available as the Velocity variable,
exchange
, which enables you to retrieve the userid
exchange property, using ${exchange.getProperty("userid")}
. The body of the current In message, ${body}
, contains the result of the getUserAccBalance()
method invocation.