Applying Java Business Integration

!!A Channel Service Instance Binding Component ''by Hossam Karim'' !Abstract [JBI|http://java.sun.com/integration/], the Java Business Integration Standard (JSR 208) is gaining more potential as a [JCP|http://jcp.org/] approved standard that defines the core component architecture for SOA. This article tries to provide a generic integration use case and its implementation using the JBI infrastructure and API. The article assumes familiarity with the JBI [specification|http://jcp.org/en/jsr/detail?id=208] in general, [Spring framework|http://springframework.org/], UML and Java 5.0. !An Integration Use Case Suppose we need to build a generic messaging solution based on XML with the following general requirements:\\ * The solution will integrate with internal systems using instances of a defined XML schema as its unique communication protocol. * The solution will integrate with external systems using native protocols. * The solution will accept only XML messages from internal systems, but will support different XML transport protocols. * The solution will allow the creation, configuration and management of external or internal endpoints. * The solution will transparently provide a two-way conversion between internal XML messages and external native protocols. The following diagram simplifies our use case using some specific technologies.\\ (This diagram is published under permission from 'GAIA for Technology Innovation') |> To make our case more simple, we will try to implement the 'Email Channel Container/Factory' node. As this node suggests, a listener to a JMS queue will consume messages with XML content, convert it to MIME format, and send it as an email to the corresponding 'out port'. The node will also receive emails from 'in port', extract the MIME format, convert it to XML and submit it to a JMS queue. The other nodes in the diagram do the same but with different protocols. What the diagram really tries to suggest, is that all these nodes should be different instances of just one entity. Since a node actually represents a channel and is also a service; an instance of a node is a 'Channel Service Instance'. In the next sections we will try to implement this concept using the -feature rich- JBI standard, but let us first explain our understanding using the JBI language. !Introducing the Channel Service Instance While all JBI components communicate inside the JBI container through the Normalized Message Router (NMR), a JBI Binding Component (BC) provides the means to communicate with the outside world protocols and services. By providing a protocol specific endpoint, a BC can , for example, consume a JMS message, publish a web service, write to a file system or send an email. The JBI specification does not restrict or define how the external communication can be done, it specifies how different components can communicate inside the container. A simple approach to accomplish external communication, is to build a binding component for the protocol you need to communicate over. A more advanced approach, is to build a single component that can be configured to publish a protocol specific end point upon a system trigger. Moreover, this end point should be manageable, you will need to start, stop, shutdown or remove this configuration. You might also need to publish multiple end points for a single protocol, for example, you might need to consume multiple JMS queues. Because this approach provides instances of multiple channel services, We can call the binding component a Channel Service Instance or CSI. The JBI specification defines the concepts of a ServiceUnit (SU) and a ServiceAssembly (SA). In brief, a SA is a container of one or more SUs that can be deployed to the container targeting a single or multiple components. Except for the deployment descriptor, the specification does not define the actual contents of a SU, but defines how its life cycle can be controlled through the ServiceUnitManager interface. This is an excellent solution to our configuration problem, to publish a protocol specific end point we need to deploy the correct SU. It is our task now to construct the framework that can manage the deployed SUs, read the configuration, and publish the protocol communicator. This task also includes defining how the CSI component would handle the incomings of its communicators (instances) and how to allow other components to consume them. The following diagram shows what our use case solution might look like upon deployment to a JBI container:\\ |> The diagram suggests that a service assembly would be deployed to the CSI component. The contained service units of this assembly will activate the proper instances of the CSI endpoints and configure their targets. Upon receiving an external message on the activated endpoints, the message will be routed to the configured target, which in turn submits a message exchange to the NMR. Finally, the NMR will do its job and deliver the exchange to the addressed component. To implement the solution, we will start by suggesting a simple generic framework over the JBI contracts, which simplifies our next tasks. You might find this framework helpful for building other JBI components as well. Next, we will suggest a design for the CSI component using this framework. !JBI helper framework To build a JBI component, you have to implement specific API interfaces.\\ This helper framework tries to simplify the task of building a JBI component by providing a less abstraction layer\\ than the API standard interfaces. In general, the framework tries to:\\ * Provide Adapters for base JBI interfaces when possible, performing common and repeatable tasks * Provide a component central configuration context using the Spring framework * Introduce contracts that are common to our use case needs * Provide an abstract factory based on the introduced contracts The following class diagram illustrates the framework classes and interfaces, members are omitted for clarity |> Here are the elements of concern in the diagram: * AbstractBootstrapAdapter: Simple adapter for the Bootstrap interface * ComponentApplicationContext: Provides a Spring based Application context for this component, the context beans should be loaded upon component initialization. An instance of this class will be available to all classes or interfaces that extend the ApplicationContextAware Spring interface. All classes and interfaces on the previous diagram that associate a ComponentApplicationContext instance (applicationContext) implement the ApplicationContextAware interface. One exception is the AbstractComponentAdapter class, because it actually creates the instance. * AbstractComponentAdapter: Adapter for the component and lifecycle contracts. Upon initialization, this class creates a ComponentApplicationContext instance, an uses the beans to configure itself, the class recognizes other framework contracts and able to communicate with them. * AbstractMessageListener: Consumes this component's DeliveryChannel message exchanges, and fires a handling trigger. * DeployedEndpoint: Represents an endpoint that was deployed using a SU * DeployedEndpointConfiguration: Holds the configuration needed by a DeployedEndpoint instance * DeployedEndpointTarget: Represents the target JBI specific address that a DeployedEndpoint instance should route the messages to. * GenericMessageListener: Handles the trigger fired by the AbstractMessageListener, delegating the work to an instance of the DeployedEndpoint interface * GenericServiceUnitManager: Handles GenericServiceUnit instances * GenericServiceUnit: Acts as a proxy and encapsulates a DeployedEndpoint instance * AbstractDeployedEndpointFactory: An abstract factory for creating DeployedEndpoint instances using DeployedEndpointConfiguration instances Our next step is to make use of this simple framework to define the CSI component. !The Channel Service Instance Binding Component The CSI implementation is straight forward, we need to implement self provided contracts and extend the abstract adapters. |> At this point, not all JBI helper framework interfaces have to be implemented.\\ We might only need to specialize an interface to our Csi case.\\ An example of this is the CsiConfiguration interface, which provides a more specialized version of the DeployedEndpointConfiguration interface to fit its specific needs. Notice that the interface Csi extends the DeployedEndpoint interface, thus, we moved the logic handling from the component to the classes that implement Csi interface, this is because, as mentioned earlier, the GenericMessageListener will route message exchanges received through the NMR to an instance of DeployedEndpoint. A specialized GenericMessageListener, which is CsiMessageListener class, will route an exchange to an instance of the Csi interface. CsiMessageListener will typically query CsiServiceUnitManager for a CsiServiceUnit instance that can handle this exchange. Since, a CsiServiceUnit instance acts as a proxy for another Csi instance, the encapsulated Csi instance uses its configuration to reject or accept the handling request.This might sound like the NMR functionality for delivering exchanges on the component level. To package and install the component into the JBI container, we need the following XML listing which shows the jbi.xml code for the CSI component. {{{ com.gaiati.jbi.component.csi.CsiBinding Channel Service Instance Binding Component com.gaiati.jbi.component.csi.CsiBinding . com.gaiati.jbi.component.csi.CsiBootstrap . }}} To configure the component, an instance of ComponentApplicationContext will be created by the component upon initialization.\\ This instance will be the source of Spring beans read from file. We can either use the deployment descriptor to define the beans, as the JBI schema allows us to do so, or use an external XML file. A sample Spring beans XML document might look like the following:\\ {{{ }}} Notice that a bean like "serviceDescriptor" might contain more or less properties as needed by the component,\\ a WSDL document location is an example of an added property. !Sample endpoint, JMS instance Now that we have built the component itself, we need to prove our CSI concepts after all. We will try to build a CSI for JMS. What we need to do on the modeling level, is to implement all the abstract classes or interfaces we left unimplemented on the component level. A typical CSI for JMS model would look like the following:\\ |> Here are the elements of concern in the diagram:\\ * JmsCsi: Adds the possibility of different implementations for this CSI for JMS * JmsCsiConfiguration: Adds the possibility of different implementations of the configuration contract * DefaultJmsCsi: A default implementation of the endpoint. This class should actually do the work, like consuming a JMS queue * DefaultJmsCsiConfiguration: A default implementation of the configuration contract, used by the default endpoint implementation * DefaultJmsCsiFactory: A specialized version of the AbstractDeployedEndpointFactory for our current implementation The last piece of this puzzle is the target. What should the CSI for JMS do after consuming a JMS message? The answer is to deliver the message contents to its target. This means that class DefaultJmsCsi will normalize the JMS message and extract the XML content or possibly any kind of format it understands, then creates a javax.xml.transform.Source instance from this content, and finally deliver this Source instance to associated target. The target should create an exchange using the Source instance, and use the NMR to deliver the exchange to its associated component. Let's see an example:\\ Since DefaultJmsCsi is a JMS consumer, it is probably a javax.jms.MessageListener instance {{{ public void onMessage(Message message){ // get an XML source instance from the message Source source = normalize(message); if(source == null){ log.fatal("Can't normalize JMS message: " + message); return; } // get the target and ask it to deliver this source configuration.getTarget().deliver(source); if(consumer.transacted){ try{ consumer.getQueueSession().commit(); } catch(Exception ex){ ex.printStackTrace(); } } } }}} The deliver method of the target could look like this:\\ {{{ public void deliver(Source source){ // get the delivery channel DeliveryChannel channel = getDeliveryChannel(); // create the exchange factory using the default or specific addressing MessageExchangeFactory mef = createMessageExchangeFactory(); if(mef == null){ log.fatal("Unable to resolve a delivery endpoint, message will not be send"); return; } MessageExchange mx = getMessageExchange(mef); // create and send the message // to the associated JBI component // or components if more than one is associated NormalizedMessage message; try{ message = mx.createMessage(); message.setContent(source); mx.setMessage(message, "in"); mx.setOperation(getOperationName()); channel.send(mx); } catch(MessagingException e){ log.fatal(e.getMessage()); } } }}} One important thing to notice about having a target, is the ability to deliver messages to more than one address where the addressed components are expecting different forms of content. If your messages are going to be delivered to only one target, JBI provides a much simpler way that can be used, we will get to this shortly. In order to create a CSI for JMS instance, we need to deploy a SA to the component, the jbi.xml file content of this SA is shown below:\\ {{{ csi-sa Channel Service Instance Service Assembly email-su EMail service unit email-su.zip com.gaiati.jbi.component.csi.CsiBinding }}} The SU deployment descriptor is listed below:\\ {{{ }}} Notice that we called the SU email-su, this implies that the SU has something to do with an E-mail service. This is because this SU's target is associated with a component or a ServiceEngine that actually sends emails. The "target" bean in the next Spring XML listing shows its properties. {{{ org.jnp.interfaces.NamingContextFactory org.jboss.naming:org.jnp.interfaces jnp://localhost:1099 }}} Now, back to our single target issue mentioned earlier, a much simpler approach can be achieved through what JBI calls connections. A connection is a logical wiring between a provider and a consumer defined through the deployment of a service assembly, in our case, to wire JMS listener with the email sender service we need a deployment descriptor like the following: {{{ sa-wiring Wiring Service Assembly }}} This way, the JBI runtime will provide a default distenation routing based on the connection definition if the consumer endpoint did not address any. The routing behaviour is related to the addressing type a service unit defined in its deployment descriptor. To get the full details on this subject, it is advisable to return to the specification. The Spring XML beans is the actual SU content. Upon the deployment of this SU, the CsiServiceUnitManager will create a CsiServiceUnit instance and ask it to deploy this SU. The CsiServiceUnit instance will load the Spring beans and instantiates its encapsulated Csi member. Since the beans use a DefaultJmsCsiFactory, the instantiated Csi member will be an instance of whatever the factory creates, which is an instance of the DefaultJmsCsi class in our case. Let's have a look at a sample implementation of the GenericServiceUnit class, after the deploy method completes, the member 'proxy' should be an instance of the DefaultJmsCsi class based on the supplied Spring XML beans: {{{ public class GenericServiceUnit implements ApplicationContextAware{ protected T proxy; protected ComponentApplicationContext applicationContext; protected final Log log = LogFactory.getLog(getClass()); public void init(String serviceUnitRootPath){ } @SuppressWarnings (value = {"unchecked"} ) public String deploy(String serviceUnitRootPath){ try{ String xmlbeans = serviceUnitRootPath + '/' + "beans.xml"; FileSystemResource xml = new FileSystemResource(xmlbeans); XmlBeanFactory beanFactory = new XmlBeanFactory(xml); DeployedEndpointConfiguration configuration = (DeployedEndpointConfiguration) beanFactory.getBean("configuration"); configuration.getTarget().setApplicationContext(applicationContext); AbstractDeployedEndpointFactory endpointFactory = (AbstractDeployedEndpointFactory) beanFactory.getBean("endpointFactory"); proxy = endpointFactory.createDeployedEndpoint(configuration); proxy.setApplicationContext(applicationContext); } catch(Exception e){ log.fatal(e.getMessage()); return DeploymentResultMessage.DEPLOY_FAILED; } return DeploymentResultMessage.DEPLOY_SUCCESS; } public String undeploy(String serviceUnitRootPath){ //clean up proxy = null; return DeploymentResultMessage.UNDEPLOY_SUCCESS; } public void start(){ proxy.start(); } public void stop(){ proxy.stop(); } public void shutDown(){ proxy.shutdown(); } public T getProxy(){ return proxy; } public void setApplicationContext(ApplicationContext applicationContext) throws BeansException{ this.applicationContext = (ComponentApplicationContext) applicationContext; } } }}} !A Simple MDA Approach for JBI Building JBI components might not be an easy task when systems are getting more complex. To ease some parts of JBI component development, one can use UML. It is possible to model, build and deploy JBI components if you do some basic effort with a good UML tool. You can start be modeling a UML profile for JBI to use it on your class and deployment diagrams. Hopefully, we will see a standard UML profile for JBI available or a specification request for it, but until then, we can suggest a basic profile for JBI. The following diagram suggests a JBI deployment profile. After implementing this profile, we should be able to generate JBI deployment descriptors from a deployment diagram. |> In a deployment diagram, either 'binding-component' or 'service-engine' stereotypes can be applied to a JBI component. We should be able then to fill in the tag values for the applied stereotypes. With a good UML tool, we should be able also to lookup a tag value based on its meta class, the 'component-class-name' tag is an example. The stereotypes 'shared-library', 'service-assembly' and 'service-unit' are available for artifact elements on a deployment diagram. It is possible to model service units as nested artifacts inside a service assembly artifact to indicate the packaging of this service assembly. Manifestations should be used to indicate the deployment of an artifact to a JBI component, and might not need custom stereotypes. The stereotypes 'provides', 'consumes' and 'service-connection' can define service connections and service link types defined by the JBI specifications. The data type 'addressing-type' can be used as a classifier of JBI address instances. Finally, 'service-connection-type' can be used as a classifier for service connection instances, encapsulating a consumer and a provider. The following diagram is a sample of applying the proposed JBI profile. Code generation can be achieved in many different ways, a simple XQuery was a personal preference. |> !Other Solutions There could be more than one solution to our use case. Another solution could be achieved using ServiceMix open source JBI implementation. ServiceMix allows you to utilize the Spring framework to achieve faster and specific JBI practice. The only problem is that this extension is not a JBI standard, and you probably might not find it in any other implementation. But still, you can benefit from the rich component library ServiceMix offers and continue writing your components following only the JBI standards as ServiceMix implementation supports them. !Conclusion In this article, we explored a generic integration use case, suggested a solution design and implementation. You might find the CSI concept helpful when building complex JBI based integration solutions. ''About the author''\\ Hossam Karim is the R&D Director at GAIA-TI, http://www.gaiati.com
AttachmentSize
framework.png279.25 KB
impl.png83.85 KB
jbi-profile.png259.95 KB
jms-csi.png116.46 KB
sample-mda.png212.35 KB
soln-arch.png126.7 KB