Wednesday, August 15, 2007

Using Embedded ServiceMix Client

One of the options for integrating ServiceMix with BPM solutions such as jBPM is to simply instantiate an 'on-the-fly' Servicemix client, then pass an XML message directly to a ServiceMix component that has been configured to receive it. An example of where this approach maybe warranted is if you had several ESB-type tasks that you wanted to perform in sequence, such as logging jBPM results to a database, then sending them via email to a predefined audience. Obviously, these are tasks that ServiceMix handles with aplomb, since it has prebuilt components for such things.

In the example presented in this article, I'm going to provide an example of calling ServiceMix File and XSTL components. The SM file component example is InOnly (i.e., fire-and-forget), whereas the XSLT example is asynchronous (block-and-wait for response).

We'll first look at the setup.xml file, which sets up the component definitions and ServiceMix JBI container.

setup.xml

<beans xmlns:sm="http://servicemix.apache.org/config/1.0"
xmlns:foo="http://servicemix.org/cheese"
xmlns:eip="http://servicemix.apache.org/eip/1.0">

<!-- the JBI container -->
<sm:container id="jbi" embedded="true">
<sm:activationSpecs>

<!-- returns dummy xml for testing -->
<sm:activationSpec componentName="test" service="foo:test" >
<sm:component>
<bean class="org.apache.servicemix.components.xslt.XsltComponent">
<property name="xsltResource" value="classpath:resources/test.xslt"/>
</bean>
</sm:component>
</sm:activationSpec>

<sm:activationSpec componentName="receiver"
service="foo:receiver">
<sm:component>
<bean
class="org.apache.servicemix.tck.ReceiverComponent" />

</sm:component>
</sm:activationSpec>

<sm:activationSpec componentName="fileSender"
service="foo:fileSender">
<sm:component>
<bean
class="org.apache.servicemix.components.file.FileWriter">

<property name="directory" value="/tmp/test" />

<property name="marshaler">
<bean
class="org.apache.servicemix.components.util.DefaultFileMarshaler">
<property name="fileName">
<bean
class="org.apache.servicemix.expression.JaxenStringXPathExpression">
<constructor-arg
value="'test.xml'" />
</bean>
</property>

</bean>
</property>
</bean>
</sm:component>
</sm:activationSpec>

</sm:activationSpecs>
</sm:container>


<bean id="client"
class="org.apache.servicemix.client.DefaultServiceMixClient">
<constructor-arg ref="jbi" />
</bean>
</beans>

In the above, you can see that I've setup two components -- the XSLT service named "test" and the file writer component named "fileSender". In the case of the XSLT component, the test.xslt file associated with it simply returns the following xml:

<hello>
<text>Hi!</text>
</hello>

In the case of the fileSender, it is configured to write a file to /tmp/test called "test.xml" with the contents of what was passed when it was invoked.

Next, we'll look at a jUnit test that illustrates how a ServiceMix instance can be instantiated and run through a Java client.

ServiceMixClientTest.java
package company.cop.check.test;
import javax.xml.namespace.QName;
import junit.framework.TestCase;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.servicemix.client.ServiceMixClient;
import org.apache.servicemix.jbi.container.SpringJBIContainer;
import org.apache.servicemix.jbi.jaxp.SourceTransformer;
import org.apache.servicemix.jbi.resolver.EndpointResolver;
import org.apache.servicemix.tck.Receiver;
import org.apache.xbean.spring.context.ClassPathXmlApplicationContext;
import org.springframework.context.support.AbstractXmlApplicationContext;
import org.w3c.dom.Document;

/**
* @version $Revision: 1.2 $
*/
public class ServiceMixClientTest extends TestCase {

private static final transient Log LOG = LogFactory.getLog(ServiceMixClientTest.class);

protected AbstractXmlApplicationContext context;
protected ServiceMixClient client;
protected Receiver receiver;

protected SourceTransformer transformer = new SourceTransformer();

public void testCallingComponent() throws Exception {
QName service = new QName("http://servicemix.org/cheese", "fileSender");
EndpointResolver resolver = client.createResolverForService(service);
client.send(resolver, null, null, "world");

}

public void testXslt() throws Exception {
QName service = new QName("http://servicemix.org/cheese", "test");
EndpointResolver resolver = client.createResolverForService(service);
Document response = (Document) client.request(resolver, null, null, "world");
assertEquals(response.getChildNodes().item(0).getTextContent(), "Hi!");
System.out.println("Response: " + response.getChildNodes().item(0).getTextContent());
}

protected void setUp() throws Exception {
context = createBeanFactory();
client = getClient();
SpringJBIContainer jbi = (SpringJBIContainer) getBean("jbi");
}

protected ServiceMixClient getClient() throws Exception {
return (ServiceMixClient) getBean("client");
}

protected void tearDown() throws Exception {
super.tearDown();

if (context != null) {
context.close();
}
}

protected Object getBean(String name) {
Object answer = context.getBean(name);
assertNotNull("Could not find object in Spring for key: " + name, answer);
return answer;
}

protected AbstractXmlApplicationContext createBeanFactory() {
return new ClassPathXmlApplicationContext("resources/setup.xml");
}
}

The setup() method, called automatically by jUnit prior to each test, establishes the JBI ServiceMix container/server. Then, each of the test methods are called. The testCallingComponent() method sends a message to the "fileSender" ServiceMix component/service. It identifies the target service by QName, which is the namespace and name provided the service in the setup.xml file. The client.send() method sends a message using the provided parameters, notice the last of which is the XML packet. When run, this should result in a file called "test.xml" being written. The contents should be what was passed in the client.send operation, namely "world".

The testXslt() method is used to invoke the test XSLT service defined in setup.xml. Unlike the fileWriter, this is a synchronous call, as we anticipate the transformed XML being sent back to us. To do so, the client.request() method is used, and it will return back the transformed XML in the form of a DOM Document. That Document object is then traversed to printout the text contents of the "text" node. This should be "Hi!", as defined in the XSLT document.

Final Thoughts

As you can see, it's pretty straightforward to invoke ServiceMix within a standalone Java client, without requiring ServiceMix to be running as a stand-alone instance. Doing this, however, does impose some performance penalties, as instantiating the JBI container does consume some overhead.

We'll explore calling remote services from a running ServiceMix instance in another article. Another intriguing option would be to use a stateful EJB to maintain a ServiceMix context, but there could be some tricky concurrency issues with that approach.

Add to Technorati Favorites

5 comments:

Saycat said...

Do you think this example needs some modifications/additions to run in servicemix 3.3? Also what about classpath? Would your sample class need the entire servicemix lib folder in its classpath? How about the components in the hotdeploy folder?
Thanks.

Anonymous said...
This comment has been removed by a blog administrator.
Adi said...
This comment has been removed by a blog administrator.
Adi said...
This comment has been removed by a blog administrator.
Anonymous said...
This comment has been removed by a blog administrator.