Tuesday, April 20, 2010

Mule & CXF: Multiple Operations and Groovy Components

When I picture a web service in Mule, there is usually a component backing the endpoint. Here I have a case where I would want the service fielding the web service request to delegate its work to another service. To the client, the fact that Mule will pass the actual workload for a request to another service will be hidden.

In this example, we have a mock "warehouse", where a client can store and retrieve a box. A box is uniquely identified by an ID (a String). Each operation will result in the invocation of an additional, but separate, service to do the actual work.

Here's the interface defined for our warehouse:

package prystasj.warehouse;

import javax.jws.WebParam;
import javax.jws.WebResult;
import javax.jws.WebService;

@WebService
public interface Warehouse {

@WebResult(name="receipt")
List<String> retrieve(@WebParam(name="boxId") String boxId);

@WebResult(name="box")
String store(@WebParam(name="boxId") String boxId);
}

The inteface defines both the operations. To determine which operation the client wishes to invoke, we can inspect the cxf_property of the Mule message created by the CXF Transport.

A request indicating the store operation will be routed to a storageService, while a retrieve operation will be routed to a retrievalService:

<model name="warehouse">
<service name="warehouseService">
<inbound>
<cxf:inbound-endpoint address="http://localhost:8080/warehouse"
serviceClass="prystasj.warehouse.Warehouse"/>
<inbound>
<outbound>
<filtering-router>
<vm:outbound-endpoint path="retrieval" synchronous="true"/>
<expression-filter evaluator="groovy"
expression="message.getProperty('cxf_operation').getLocalPart() == 'retrieve'"/>
<filtering-router>
<filtering-router>
<vm:outbound-endpoint path="storage" synchronous="true"/>
<expression-filter evaluator="groovy"
expression="message.getProperty('cxf_operation').getLocalPart() == 'store'"/>
<filtering-router>
<outbound>
<service>
</model>

To test the processing flow out, we can script a couple of components with Groovy. A couple of println's will help us pick out that the operations were invoked when viewing the Mule log. With a scripting component, we'll have access to a variable payload, which in each operation will contain the web parameters (a boxId in both cases) as defined in the Warehouse interface.

    <service name="retrievalService">
<inbound>
<vm:inbound-endpoint path="retrieval"/>
</inbound>
<script:component>
<script:script engine="groovy">
println "Operation: retrieve; Box ID: $payload"
new Box(payload)
<script:script>
<script:component>
<service>
<service name="storageService">
<inbound>
<vm:inbound-endpoint path="storage"/>
</inbound>
<script:component>
<script:script engine="groovy">
println "Operation: store; Box ID: $payload"
new Receipt(payload)
<script:script>
<script:component>
<service>

The store operation returns an instance of a Box and the retrieve operation returns a Receipt. As both services are invoked synchronously, the result of the component invocations will be returned to the calling Warehouse service, which will pass them through to the client.