Java EE and .NET Interoperability: Integration Strategies, Patterns, and Best Practices

Individual .NET and Java EE applications can transparently exchange messages by leveraging a bridging solution, which transforms messages into the corresponding format and moves them between MSMQ and the JMS message queues.

Scope

There are a number of companies invested into an architecture featuring the MSMQ messaging backbone to asynchronously connect various legacy applications. It is often the case that newer services are developed in Java and rely on a JMS based messaging solution. At some point in time, a growing number of businesses require seamless integration of these two sets of applications and the corresponding messaging solutions. Redesigning and reimplementing existing infrastructures may not be an option due to time and cost constraints. One of the ways to integrate existing message queues is by means of a bridge, which allows two heterogeneous sets of applications to remain unchanged and facilitates seamless integration across messaging backbones.

Solution

A bridging strategy enables communication between two message queues: in this case MSMQ and a JMS messaging infrastructure. It is typically used to integrate disparate solutions deployed over the Internet, but can also be used to achieve interoperability within a company network.

Figure 9-2 highlights a bridging solution to integrate a JMS provider with a MSMQ:

Figure 9-2. Bridging between a JMS provider and MSMQ

In the diagram, a Payroll application developed in Java EE has to interface with a MSMQ to pass information to a Banking system implemented in .NET. To address Java EE and .NET technology variances, a bridging strategy is applied. This bridging component assures reliable information transfer that is important for sensitive financial data. This architecture streamlines and automates the B2B payroll process that may have been accomplished manually in the past.

Bridging solutions do not restrict the data type exchanged between applications. JMS supported message types including Stream, Text, Bytes, and Objects can be freely sent out using a bridging solution. A bridge simply moves messages from one queue to another. Developers, however, have to ensure that messages are processed correctly between Java and .NET platforms. This can be done by either creating custom serializers or by sending SOAP messages that adhere to the agreed WSDL. Alternatively, applications may exchange XML messages that are consistent across Java EE and .NET application.

Commercial bridging solutions are available from various vendors such as SpiritSoft and Fiorano that offer integration between MSMQ and specific JMS providers. For details, please refer to [SpiritSoft] and [FioranoMQ-MSMQ]. Fiorano Bridge also offers integration with Tibco Rendezvous among other proprietary messaging infrastructures. The Microsoft Host Integration server ships with the MSMQ-MQ Series Bridge, [MSMQ-MQSeries]. IBM's proprietary WebSphere MQ ships with the .NET Assembly classes to allow Java EE-.NET interoperability, [WebSphereMQInterop]. The WebSphere MQ includes JMS support to allow a standards-based integration for Java EE applications.

Replenish Stock Use Case

To demonstrate how to implement asynchronous communication using a bridging solution, recall the Replenish Stock use case. Assume that a Retailer system that encompasses Warehouse and WarehouseCallback is implemented in Java, and the Manufacturer system is built in .NET. The UML diagram in Figure 9-3 outlines the sequence of calls for the Replenish Stock use case.

Figure 9-3. Replenish Stock sequence diagram

In the figure, the Warehouse service creates a Purchase Order (PO) request and sends the PO message using JMS APIs to a JMS provider. The message queue invokes the bridging solution to transform and forward the message to the MSMQ. Manufacturer then retrieves the document from the PO queue. Once the Purchase Order is received, the POValidator asynchronously processes it and generates the Shipping Notice (SN). The ShippingNotice is then sent to the MSMQ, which forwards the message to the bridging solution. The bridge translates the content of the SN document into a JMS-supported format. The bridging solution then passes the message to the Message queue. Upon the message's arrival to the Shipping Notice queue, an event notification is sent to the SN listener, i.e. WarehouseCallback, and the onMessage method is invoked. The WarehouseCallback can register with the queue and explicitly call the receive method to retrieve ShippingNotice.

Benefits

When selecting one messaging integration strategy over another, reliability is not the only factor to consider. It is also helpful to evaluate other systemic qualities such as manageability, security, and performance. These characteristics are discussed next to help making an optimal decision for your architecture. Most of the JMS providers offer a failover mechanism, dynamic routing, and clustering to ensure high availability and scalability of enterprise systems.

Manageability

Managing distributed transactions that travel across messaging hubs, particularly between MSMQ and JMS-based, is not trivial. Because a bridging solution is tightly integrated with a JMS provider, distributed messaging administration and configuration can be achieved with the JMS Provider administration tool. For instance the administration tool for Fiorano Bridge provides a way to manage messages sent to and from MSMQ, IBM MQSeries, or Tibco Rendezvous servers. Management with bridging is important particularly when transactions, session propagation, and multi-part messaging are involved.

Security and Performance

Because MSMQ and JMS provider integration may involve firewalls and the Internet, secure communication is essential for bridging. Individual JMS vendors offer advanced security features, such as Access Control Lists (ACLs), encryption, and HTTPS as a transport protocol. To prevent a denial of service attack, Microsoft MSMQ can be configured with a quota on a size of message storage and exposure to unknown networks.

The bridging solution itself is fairly lightweight. It does not consume many resources and does not require a dedicated software or hardware container on which to be deployed.

To alleviate performance overhead associated with a large message size and to maximize the data throughput, leverage message compression. Various JMS providers support compression. On the MSMQ side, a custom message formatter that extends the IMessageFormatter interface can be used to implement compression. The custom build MSMQ data formatter can perform encryption and compression that is compliant with those offered on the JMS side to allow full interoperability.

Limitations

Along with benefits come limitations of this strategy. Because a bridging solution is the main mediator across heterogonous messaging servers, it may become a single point of failure. Therefore, failover should be considered when deploying this solution.

Interoperability

While a bridging solution offers interoperability with MSMQ, the actual bridge is not interoperable across different JMS providers. For instance, Microsoft Bridge only works with WebSphereMQ, while the Fiorano Bridge only works with the FioranoMQ. Therefore, a third-party bridging solution cannot be integrated practically with any JMS message queue.

Manageability

In terms of interoperability, a bridging solution has to be kept up to date with both JMS and non-JMS-based MQ solutions. Should a company upgrade MSMQ server with a new version offering new features, the Bridge has to be managed accordingly for developers to leverage new MSMQ features.

A bridge only transfers messages from a queue of one server to a queue on another server. It is important to notice that a bridge should be the only queue listener since messages are sent as a point-to-point mechanism. Technically, and by definition, the same cannot be provided for publish-subscribe topics. To handle this situation, JMS vendors like Fiorano provide repeaters that help replicate messages across two different messaging servers.

Performance and Scalability

It may already be clear that having two messaging infrastructures and a bridging solution on top of one of them does not produce the most optimal integration solution from a performance standpoint.

While commonly used XML messages exchanged between systems help to enhance interoperability between Java and .NET systems, XML processing significantly impacts overall performance. To address the performance overhead, in addition to the already discussed message compression, XML hardware accelerators can be deployed on both ends of the communication channel. In the financial sector, a high volume of transaction processing results in companies building custom data serializers. When developing data serializers, there should be compatibility between heterogeneous Java and .NET applications to allow a common data format to be transmitted over the wire. Custom serializers address performance but complicate manageability of the integrated environment. Any changes to the underlying data structures require changes of serializers on both .NET and Java EE ends.

Aside from the XML serialization, it is also important to take into account any data transformation requirements. In comparison to the bridging solution, discussed later, an Enterprise Service Bus solution typically supports an independent scalability model, where individual services can be scaled up to handle large volumes of XML data manipulation such as message transformation services.

Related Patterns and Best Practices

Gregor Hohpe and Bobby Woolf defined messaging best practices very well in their Enterprise Integration Patterns book, [EIP]. Both JMS and .NET implementations are provided for individual patterns such as Request-Reply. These patterns can be applied in this case as well. Depending on the application requirements, one's design may leverage patterns such as Aggregator, Splitter, Routing Slip, and Process Manager. As will be seen later in this chapter, these patterns become higher-level service abstractions that are implemented as part of an Enterprise Service Bus offering.

General best practices that were already used in the preceding example are to use the ServiceLocator and ServiceLocatorException to look up a message queue. Additionally, should any complex logic or processing occur within a component that directly accesses the message queue, this logic should be factored out of that class. Keeping the design simple helps troubleshooting and understanding heterogeneous integration environments.

When exchanging documents between Java and .NET systems, it is recommended to agree on a canonical message format. If XML documents are sent, the WSDL of that document should be defined and agreed on by both Java and .NET sides. This will allow a higher degree of interoperability.

Example

From an implementation standpoint, a bridging solution offers a transparent way to communicate messages across a JMS provider and MSMQ messaging systems. The beginning of the chapter walked through the step-by-step process of sending and receiving messages using both the JMS standard and the .NET-based APIs, which are already halfway through completing these undertakings. The only missing piece of the Replenish Stock scenario is definition of data structures in the form of a WSDL that will be passed across two messaging systems. The PurchaseOrder XML document will be sent from the Warehouse to the .NET Manufacturer's POValidator, while the ShippingNotice (SN) XML message will be sent to the Java WarehouseCallback component.

Building JMS-Based Warehouse System

First, a Purchase Order structure needs to be created that encapsulates a list of items, quantity, and has an order number. For simplicity and interoperability, XML is used to represent a Purchase Order. Both .NET and Java applications would share the Purchase Order XML Schema Definition File, i.e. PurchaseOrder.xsd. Listing 9-7 lists the Purchase Order XML file exchanged between Java and .NET applications:

Listing 9-7. Purchase Order XML Schema

<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" targetNamespace="http://j2eedotnet.org/PurchaseOrder.xsd" xmlns="http://j2eedotnet.org/po.xsd" elementFormDefault="quali- fied"> <xs:element name="purchaseOrder"> <xs:complexType> <xs:sequence> <xs:element name="names" type="xs:string" maxOccurs="unbounded"/> <xs:element name="quantity" type="xs:int" maxOccurs="unbounded"/> </xs:sequence> <xs:attribute name="id" type="xs:string"/> </xs:complexType> </xs:element> </xs:schema>

Similarly, the ShippingNotice schema is created to allow both Java and .NET classes to exchange information that adheres to the same format. Once both XSD files are ready, one creates Java classes using JAXB tool, jaxb.bat, which is available with the Java Web Services Developer Pack. For more details on JAXB, refer to [JAXB]. Listing 9-8 shows the corresponding ant target to create Java classes from XML schema.

Listing 9-8. Purchase Order XML Schema

<!-- Create stub classes from XSD--> <target name="createJavaClassesFromSchema"> <echo message="--- creating java file of given schema---"/> <mkdir dir="${xsdclasses}"/> <exec executable="${wsdp.home}/jaxb/bin/xjc.bat"> <arg value="-d"/> <arg value="${xsdclasses}"/> <arg value="${schema.dir}/PurchaseOrder.xsd"/> <arg value="${schema.dir}/ShippingNotice.xsd"/> </exec> </target>

Before populating the Purchase Order and sending the message to the JMS Queue, it's necessary to initialize all needed JMS components. Service Locator pattern is used to look up the ConnectionFactory and JMS Topic name. Listing 9-9 lists components used throughout the Warehouse class:

Listing 9-9. Queue-Related Components

QueueConnectionFactory connectionFactory = null; QueueConnection connection = null; QueueSession session = null; Queue queue = null; QueueSender sender = null; private static final String Queue_CONNECTION_FACTORY = "primaryQCF";

The next sample code, Listing 9-10, lists the init method of the Warehouse class, which creates queue-related components:

Listing 9-10. Initializing JMS Topic and TopicSession

public void init(String QueueName) { try { ServiceLocator servicelocator = new ServiceLocator(); connectionFactory = servicelocator.getQueueConnectionFactory( Queue_CONNECTION_FACTORY); connection = connectionFactory.createQueueConnection(); session = connection.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); try { queue = servicelocator.getQueue(QueueName); } catch(Exception e) { queue = session.createQueue(QueueName); } sender = session.createSender(queue); } catch(JMSException exe) { exe.printStackTrace(); } catch(ServiceLocatorException e) { e.printStackTrace(); } }

This init method is fairly straightforward and is in line with JMS Queue initialization discussed at the beginning of the chapter.

Next an instance of the Purchase Order class needs to be created and populated with data, marshaled into the XML format, and passed to the message queue. Populating the PO object does not present any complexity, thus this only elaborates on how to marshal Java objects into XML. Listing 9-11 outlines the usage of the Java Architecture for XML Binding (JAXB) APIs for marshaling objects. These APIs, defined as part of the javax.xml.bind package, are used to manage XML/Java binding. The main marshaling operation, marshal, uses the Marshaller object, which is created from the JAXBContext context. The JAXBContext is an entry point to the JAXB APIs and in our case is used to obtain the Marshaller object, as shown in Listing 9-11:

Listing 9-11. Marshaling Purchase Order Object into XML Format

JAXBContext context = JAXBContext.newInstance("org.j2eedotnet.po"); // Marshal the PO object into outputStream StringWriter writer = new StringWriter(); Marshaller marshaller = context.createMarshaller(); marshaller.marshal(pOrder, writer);

Listing 9-12 provides an example of the getPO method that creates the Purchase Order instance, populates it with data, and marshals the object into an output stream:

Listing 9-12. Warehouse getPO() Method

private String getPO() throws javax.xml.bind.JAXBException { ObjectFactory factory = new ObjectFactory(); PurchaseOrder pOrder = factory.createPurchaseOrder(); pOrder.setId("1234"); pOrder.getNames().add("GreenTea_847"); pOrder.getNames().add("OolongTea_332"); pOrder.getQuantity().add(new Integer(5)); pOrder.getQuantity().add(new Integer(7)); JAXBContext context = JAXBContext.newInstance("org.j2eedotnet.po"); // Marshal to outputStream StringWriter writer = new StringWriter(); Marshaller marshaller = context.createMarshaller(); marshaller.marshal(pOrder, writer); return writer.toString(); }

The XML PO is now ready to be sent over to the JMS message queue. Listing 9-13 shows the submitPO method that takes the PO XML strings and returns a boolean status, indicating success or failure transmitting the PO message:

Listing 9-13. Publishing Purchase Order XML Message

public boolean submitPO(String msg){ System.out.println("Warehouse.submitPO(): Sending PO"); boolean status = false; try { Message jmsMsg = session.createTextMessage(msg); sender.send(jmsMsg); System.out.println("Warehouse.submitPO(): PO sent"); session.close(); connection.close(); status = true; } catch(Exception exe) { System.out.println("Warehouse: PO could not be submitted."); status=false; } finally { return status; } }

The main() method of the Warehouse class invokes getPO and submitPO, discussed previously, to send the Purchase Order out.

The chapter continues examining the Java Retailer system by implementing the WarehouseCallback class. The WarehouseCallback receives the ShippingNotice message from the .NET Manufacturer system once Manufacturer validates the order. WarehouseCallback uses the JMS QueueReceiver to receive a message from the queue:

TextMessage shipNote =(TextMessage) receiver.receive();

Listing 9-14 shows the complete receiveSN() message that receives ShippingNotice from JMS. As can be seen from this snippet, the bridging solution pushes messages to and from JMS, and MSMQ message queues are completely transparent to the programmer.

Listing 9-14. Receiving Shipping Notice

public void receiveSN() { System.out.println("Ready to receive a Shiping Notice"); String shippingNotice = null; try { TextMessage shipNote =(TextMessage) receiver.receive(); shippingNotice = shipNote.getText(); System.out.println("Received shipnotice:" + shippingNotice); session.close(); connection.close(); } catch(Exception exe) { System.out.println("Error receiving ShippingNotice " + exe.getMessage()); } return shippingNotice; }

After receiving ShippingNotice, JAXB APIs are once again leveraged to unmarshal the XML message into the Java object. The WarehouseCallback's getSN() method uses java.io.StringReader to read the XML ShippingNotice and then JAXB's Unmarshaller to unmarshal XML into the ShippingNotice object. This logic is shown in Listing 9-15:

Listing 9-15. Unmarshaling XML Shipping Notice

JAXBContext context = JAXBContext.newInstance("org.j2eedotnet.sn.xsd"); Unmarshaller unmarshaller = context.createUnmarshaller(); ShippingNotice sNotice =(ShippingNotice) unmarshaller.unmarshal (new StreamSource(new StringReader(shipNotice)));

The WarehouseCallback can be implemented as a message-driven bean or as a POJO class, which is demonstrated in this example.

To run samples, the FioranoMQ-MSMQ Bridge must be running along with FioranoMQ and MSMQ servers. The bridge replicates messages from replenishstock queue of FioranoMQ to the ./privatequeue/replenishstock queue of MSMQ. Similarly, the bridge replicates messages from the ./private/shippingnotice queue of MSMQ to the shippingnotice queue of FioranoMQ. A sample configuration file, bridge.xml, is provided along with the code samples.

Additionally, it's important to make certain that the build.property file is defined in accordance with the environment.

Running the Retailer system requires executing the following ant command:

> ant retailer

This chapter is now going to switch to.NET content and look into implementation details of the Manufacturer system.

Building .NET-Based Manufacturer System

On the .NET side, the POValidator is the class that checks for availability of Purchase Order message in the queue and retrieves the message. Before looking at the message retrieval code, corresponding C# classes need to be prepared for PurchaseOrder and ShippingNotice. As was already shown on the Java side, both Retailer and Manufacturer systems share common XML schemas, i.e. ShippingNotice.xsd and PurchaseOrder.xsd. On the .NET side, C# classes are generated from XSD schemas. Listing 9-16 shows the createC#classesFromSchema target that invokes .NET Framework xsd.exe tool to generate corresponding classes.

Listing 9-16. ShippingNotice class in C#

<!-- Create stub C#classes from XSD--> <target name="createC#ClassesFromSchema"> <echo message="--- creating C# classes of given schema---"/> <mkdir dir="${build.classes}/schemaCsclasses"/> <exec executable="xsd.exe"> <arg value="${schema.dir}/PurchaseOrder.xsd"/> <arg value="/classes"/> <arg value="/out:${build.classes}/schemaCsclasses"/> </exec> <exec executable="xsd.exe"> <arg value="${schema.dir}/ShippingNotice.xsd"/> <arg value="/classes"/> <arg value="/out:${build.classes}/schemaCsclasses"/> </exec> </target>

Receiving a PurchaseOrder message involves the following steps. First the queue needs to be initialized:

msgQueue = new MessageQueue(@".\private$\ReplenishStock");

Next, the receivePO() method retrieves a message from the MSMQ:

Message message = this.msgQueue.Receive(); message.Formatter = new ActiveXMessageFormatter(); string purchaseOrder = message.Body.ToString();

The ActiveXMessageFormatter, from .NET Framework System.Messaging library, is used to serialize or deserialize objects, such as PurchaseOrder or primitives, from a message compatible with an MSMQ ActiveX Component.

After the PO has been received, the processesPO() method is invoked to convert string PO into the object format. To accomplish this task XmlSerializer from .NET Framework System.Xml.Serialization library is used:

StringReader reader = new StringReader(receivedPO); XmlSerializer serializer = new XmlSerializer(typeof(purchaseOrder)); PO =(purchaseOrder)serializer.Deserialize(reader);

Assuming that the PO has been successfully validated, it's now possible to create a ShippingNotice and send it out to the MSMQ. This logic resides in the sendShippingNotice() method of the Manufacturer class:

this.shipNotice = new ShipNotice(); string SN = this.shipNotice.prepareShippingNotice(POID); shipNotice.sendShippingNotice(SN);

The prepareShippingNotice() method, which takes the Purchase Order id as an input parameter and returns an XML string representation of the ShippingNotice object, is shown in Listing 9-17.

Listing 9-17. Populate and Serialize ShippingNotice

public string prepareShippingNotice(string POID) { // Create and populate ShippingNotice shippingNotice sNotice = new shippingNotice(); sNotice.PONumber=111; sNotice.ShipmentID=222; sNotice.Description=POID; // Serialize ShippingNotice into XML format and // write it to a string XmlSerializer serializer = new XmlSerializer(typeof(shippingNotice)); StringWriter writer = new StringWriter(); serializer.Serialize(writer,sNotice); string shipNoticeXMLMessage = writer.ToString(); return shipNoticeXMLMessage; }

The first part of this method populates ShippingNotice, and the second part is the actual XML serialization of the object. The StringWriter .NET Framework class is used to write XML ShippingNotice to a string. The StringWriter is available as part of the System.IO library. The string is returned by the method.

The next logical step in this sequence corresponds to submiting ShippingNotice to the MSMQ. The ShippingNotice's sendShippingNotice() method performs this task. Because an XML string is being sent out, rather than an instance of the System.Messaging .Message, it's necessary to specify the default property values for the message. The System.Messaging.DefaultPropertiesToSend is used for that purpose:

this.msgQueue.DefaultPropertiesToSend.Recoverable = true;

The Recoverable property is set to true, indicating that the ShippingNotice message is guaranteed to be delivered in case there is a network or other failure during the send operation. ActiveXMessageFormatter, which has already been shown, is in this case set as the message queue formatter:

this.msgQueue.Formatter = new ActiveXMessageFormatter();

This formatter is used to serialize the ShippingNotice into the body of a message that is written to the queue. After the formatter has been specified, it's time to send the ShippingNotice to the queue:

this.msgQueue.Send(messageStr);

Listing 9-18 lists the sendShippingNotice() method.

Listing 9-18. Sending Shipping Notice to MSMQ

public void sendShippingNotice(string messageStr) { this.msgQueue.DefaultPropertiesToSend.Recoverable = true; this.msgQueue.Formatter = new ActiveXMessageFormatter(); try { this.msgQueue.Send(messageStr); Console.WriteLine("sent the message:"+messageStr); } catch(Exception ex) { Console.WriteLine(ex.Message); } }

As might have already been noticed, the bridging strategy offers a flexibility of using Java and .NET components "as is" to achieve asynchronous integration via messaging. As is seen later, sending SOAP messages is also very straightforward. When exchanging SOAP messages, which are in essence XML, transactional and security context can be passed as part of the SOAP header. The bridging strategy allows leverage of Java and .NET skills that may already exist within your company, which contributes to rapid application delivery.

While a bridging strategy provides a means to integrate two heterogeneous messaging infrastructures, a .NET adapter strategy, discussed next, enables a Java application to communicate with .NET application using a JMS-based messaging infrastructure. There is no need to integrate a MSMQ as an intermediate solution. Additional resources on messaging with Java and .NET can be found in the "References" section.

Категории