Introduction
There are a variety of approaches to interoperability between .NET and Java. One of the most popular approaches is to use web services and craft WSDL and XML Schema that can work in both environments. As you would expect, web services are best suited for internet based applications. If you are writing an intranet application that works on a LAN within a single department or organization then other middleware technologies become attractive. Specifically, message oriented middleware (MOM) has long been a popular choice to integrate diverse platforms within a company. Using MOM as a basis for communication between .NET and Java this article demonstrates interoperability between a .NET client and a Java middle tier in the context of a simple stock application running on a local LAN. The implementation uses the JMS support in the Spring framework, available for .NET as well as Java, to provide a common programming model across both tiers of the application.
Why Messaging?
In many ways, message oriented middleware can be considered an elder-statesman of interoperability. Vendors such as IBM and TIBCO have been providing messaging middleware for over 20 years that work on heterogeneous operating systems and have multiple language bindings. In 1999 the Java Message Service (JMS) specification breathed new life into the messaging space by defining a common set of APIs and behavior for messaging vendors to implement. Given the heritage, it is not surprising that JMS implementations run on multiple operating systems and most, but not all, provide a .NET API 1. The examples in this article use TIBCO's JMS implementation which provides Java, .NET, .NET Compact Framework, and C++ client APIs. As a side note, there are a few options available to access JMS from .NET even if your preferred vendor does not provide a .NET client. One of these is to use an interoperability product such as JNBridge 2 or Codemesh 3.
While interoperability is a compelling reason to use messaging there are a variety of other areas that make it a very attractive, if not a de facto, choice for use in an application's architecture. In short, MOM excels in providing asynchronous communication between processes, publish-subscribe (one-to-many) message delivery semantics, and high levels of reliability. If your application can benefit from these features you will quite often find a messaging solution to be a natural fit. If your company is already using messaging for some of these reasons but only within Java or C++ based applications, extending its role to include .NET clients a logical extension of the existing architecture. No matter the motivating factors, creating a JMS based solution has its own learning curve and collection of best practices. This article uses the Spring framework on .NET and Java to demonstrate how to quickly get started creating JMS applications. It also provides guidance on some of the issues that naturally arise when using messaging to communicate between .NET and Java.
Introducing Spring.NET
While most people are familiar with the Spring Framework for building Java applications many are not aware of its .NET counterpart, Spring.NET 4. Spring.NET is a .NET version of the Spring framework built "from the ground up" in C# that carries over to .NET the Spring design and approach to application development. It contains the same set of core functionality as its Java counterparts, i.e. Inversion of Control, Aspect Oriented Programming, Web Framework, RPC Exporters, Declarative Transaction Management, and an ADO.NET framework. In short, if you are already a "Spring.Java" user, you will feel right at home with Spring.NET.
Unlike Spring.Java which bundles many third party libraries in its base offing, Spring.NET separates support for third party libraries into separately downloadable modules. One of these modules provides JMS support based on TIBCO's JMS implementation. If you would like to run the article's examples you should download an evaluation version of TIBCO EMS (Enterprise Message Service) from TIBCO's web site 5.
A natural question to ask is "Why does Spring.NET just support TIBCO's JMS and not <insert provider name here>?" There is no fundamental reason why other vendors are not supported. It has just been a practical reason at this time since there isn't a de facto JMS API in .NET that each vendor is required to implement. As such, each vendor ends up creating their own .NET inspired copy of the Java JMS API. The open source project .Net Message Service API (NMS) goal is to provide such a common API and it will very likely be used for future JMS work in Spring.NET 6. Since I am very familiar with TIBCO's JMS product I selected it out of convenience as the first provider to support in Spring.NET.
Messaging with the Spring Framework
The goals of Spring's JMS support are to raise the level of abstraction when using JMS and to support messaging best practices. Spring achieves these goals by providing easy to use messaging classes that make common operations simple and creating a "plain old object" (POJO/POCO) programming model for messaging via the use of MessageConverters. MessageConverters are responsible for conversion between JMS messages and "plain old objects" and are identical in spirit to XML/Object mappers but with a JMS twist. Using message converters give you the benefit of keeping JMS artifacts constrained to the outermost layers of your application thus allowing the bulk of your application to remain technology agnostic. Should you decide to switch middleware, the refactoring required will be significantly less than if the JMS MapMessage found its way into your business processing.
Like JDBC, JMS is a low level API requiring you to create and manage multiple intermediary objects even for the most basic of JMS tasks. For sending messages, Spring's JmsTemplate manages these intermediary objects on your behalf and makes common JMS operations "one-liners". On the receiving side, Spring's MessageListenerContainer implementations allow you to easily create the infrastructure to support concurrent asynchronous consumption of messages. Both JmsTemplate and MessageListenerContainer can be associated with a MessageConverter to translate between the JMS and POJO/POCO worlds.
JmsTemplate, MessageListenerContainers, and MessageConverters are the central artifacts in Spring's JMS support. This article uses them extensively and explains them in some detail but is not a definitive reference. For more details refer to the Spring reference documentation or another Spring resource.
Hello World in Spring JMS
Before diving into the details of interoperability and Spring JMS, here is a quick example in .NET that sends a "Hello World" TextMessage to the JMS queue named "test.queue" using JmsTemplate.
ConnectionFactory factory = new ConnectionFactory("tcp://localhost:7222");
JmsTemplate template = new JmsTemplate(factory);
template.ConvertAndSend("test.queue", "Hello world!");
If you are all familiar with the JMS API, you can immediately see how simple it is to use JmsTemplate as compared to direct use of the JMS API. This is primarily because JmsTemplate is providing all the boilerplate resource management of JMS Connections, Sessions and MessageProducers that you would otherwise need to code. It also adds other conveniences such as translating checked exceptions to unchecked in Java, resolving string based JMS destinations to JMS destination objects, and delegating to a MessageConverter to convert objects to JMS messages. If you want to send messages without a conversion JmsTemplate has a simple Send method.
When calling the ConvertAndSend method, JmsTemplate uses the default MessageConverter implementation, SimpleMessageConverter, to convert the string "Hello World!" to a JMS TextMessage. SimpleMessageConverter also supports converting between a byte array and a ByteMessage and a hash table and a JMS MapMessage. Providing your own custom MessageConverter implementation lies at the heart of creating a complex Spring JMS application. The general approach is to create a converter for objects that you are already using in your application code and simply want to marhsall/unmarshall them onto JMS.
JMS Interoperability in a Nutshell
With Spring as a common framework on both .NET and Java the issues around interoperability boil down to the implementation of compatible MessageConverters and "plain old objects" that are exchanged between .NET and Java. For convenience let's call these plain old objects "business objects". They may in fact be full fledged domain objects, data transfer objects, or a UI-optimized version of your domain objects suitable for presentation. In this approach the business objects themselves essentially become the data contract between the two tiers. The fields and properties included as part of that data contract is dependent on the implementation of the converter. Note that specific data types are not explicitly shared. The data types on each side of the fence need to be compatible with each other only in as much as their associated message converter can successfully perform its job.
While this approach is certainly less prescriptive than using up front contracts as in web services there are several techniques that one can use to significantly constrain the data that is exchanged and reduce trivial mismatches. One non technological issue that helps in this regard is that in intranet or departmental applications it is often the same group (or even person) that develops both the .NET client and Java middle tier. Communication and frequent integration testing can therefore substitute to a large extent the use of up front contacts intended for consumption by disparate development groups. If you are a fan of using up front formal contracts to define the interaction between tiers, then the proposed JMS approach will probably not be to your liking. That being said, this looser and certainly less standardized approach to interoperability has been successfully used to my knowledge in several projects.
The following sections take an increasingly sophisticated approach to using MessageConverters and keeping business objects in sync between .NET and Java. The example applications starts off by using the SimpleMessageConverter included with Spring to exchange hash tables. Then we create a .NET and Java implementation of a simple business object and a corresponding pair of custom MessageConverters. Finally, we will show some techniques to reduce the duplication of effort involved in creating converters and business objects by hand on each platform through the use of a source code translator and a general "all-purpose" MessageConverter than converts arbitrary object types. The various pros and cons of each approach are discussed along the way.
Stock Trading Example
The context of the example is a simplified stock trading application. Our application will have three main pieces of functionality - the distribution of real-time market data information, the creation of new trades, and the retrieval of a portfolio of trades.
Starting with the distribution of market data, we create the JMS infrastructure to send messages from the Java middle tier to the .NET client. On the Java middle tier a JmsTemplate is created for sending messages and on the client side a SimpleMessageListenerContainer for receiving messages. Both classes use an instance of SimpleMessageConverter by default. Since market data is essentially a collection of key value pairs, i.e. PRICE=28.5, TICKER="CSCO", it is reasonable to use the default converter and exchange a simple hash table between .NET/Java.
The configuration for both JmsTemplate and SimpleMessageListenerContainer requires a JMS connection factory and the JMS destination name and type. SimpleMessageListenerContainer also requires a reference to an implementation of a JMS MessageListener callback for processing the messages. Spring provides a MessageListenerAdapter class that implements the JMS MessageListener interface and uses a MessageConverter to convert the received JMS Message to an object. The MessageListenerAdapter then uses this object to call a method on a user provided handler class that has a matching method signature.
The preceding flow is best explained with an example. If the MessageListenerAdapter is configured to use the SimpleMessageConverter, then an incoming JMS MapMessage will be converted to a .NET IDictionary and a method on the handler class with the signature "void handle(IDictionary data)" will be invoked. If the converter produced an object of type Trade, then the handler class must contain a method named handle(Trade trade). The sequence diagram below shows the flow of events.
This delegation scheme is what is commonly referred to in the Java world as "Message-Driven POJOs" or "Message-Driven objects" since it is a "plain old object" and not a JMS MessageListener that acts as the messaging callback. One advantage of this approach is that you can easily create integration style test cases that exercise the flow of the application by directly calling the handler methods. Another advantage is that the MessageListenerAdapter acts as a replacement for if/else or switch/case blocks commonly found in messaging applications. SimpleMessageListenerContainer contains many other features, such as automatic sending of a reply message based on the return value of the handler method and is intended to be subclassed for customization of the processing flow. Refer to the Spring reference documentation for more details.
The Spring configuration for these objects is shown below.
The middle tier publisher:
<bean id="jmsTemplate" class="org.springframework.jms.core.JmsTemplate">
<property name="connectionFactory"><ref bean="connectionFactory"/></property>
<property name="pubSubDomain" value="true"/>
<property name="defaultDestinationName" value="APP.STOCK"/>
</bean>
and client consumer
<object id="jmsContainer"
type="Spring.Messaging.Tibco.Ems.Listener.SimpleMessageListenerContainer, Spring.Messaging.Tibco.Ems">
<property name="ConnectionFactory" ref="connectionFactory"/>
<property name="PubSubDomain" value="true"/>
<property name="DestinationName" value="APP.STOCK"/>
<property name="ConcurrentConsumers" value="1"/>
<property name="MessageListener" ref="messageListenerAdapter"/>
</object>
<object id="messageListenerAdapter"
type="Spring.Messaging.Tibco.Ems.Listener.Adapter.MessageListenerAdapter, Spring.Messaging.Tibco.Ems">
<property name="DelegateObject">
<object type="Spring.JmsInterop.Handlers.SimpleHandler, Spring.JmsInterop"/>
</property>
<property name="DefaultListenerMethod" value="HandleObject"/>
</object>
Note that Spring.NET uses the XML element 'object' instead of 'bean' to define object definitions. The handler object has the unfortunate name from the .NET perspective of "DelegateObject". This property has nothing to do with .NET delegates.
All data sent to the client is on the JMS topic APP.STOCK and all data sent from the client to the middle tier is on the JMS queue APP.STOCK.REQUEST.
Sending a JMS message is straightforward as shown below with some contrived market data
Map marketData = new HashMap();
marketData.Add("TICKER","CSCO");
marketData.Add("PRICE", new Float(23.54));
... jmsTemplate.send(marketData);
On the receiving side the processing is done inside the handle method of StockAppHandler.
public class SimpleHandler
{
public void HandleObject(IDictionary data)
{
log.InfoFormat("Received market data. Ticker = {0}, Price = {1}", data["TICKER"], data["PRICE"]);
// forward to controller to update view
. . .
}
}
This minimal amount of infrastructure code is all that is needed to get started. However, while exchanging a hash table is easy and supported out of the box, it is only appropriate to use in the most simple of interoperability scenarios. I take simple to mean less than ~5 data exchanges each with less than ~10 key, value pairs. The reason it doesn't scale to more complex scenarios is fairly obvious, the data contract is too loose and it becomes hard to ensure there are no mismatches in key, value pairs between the two tiers. A more natural approach is to use classes to encapsulate the data. The classes themselves can ensure to a large extent that the data content is correct, for example by providing parameterized constructors, or through the use of third party validation libraries. Validation issues aside, it is simply easier to deal directly with the objects used in the middle tier for business processing as compared to introducing hash tables to meet the requirements of the MessageConverter. As such, in order to work directly with objects, custom message converters need to be created and plugged into the Spring JMS infrastructure.
Using a Custom Converter
Spring's MessageConverter interface is very simple. It contains just the two methods shown below,
public interface IMessageConverter
{
Message ToMessage(object objectToConvert, .Session session);
object FromMessage(Message message);
}
In case of failure to convert a message, a MessageConversionException is thrown.
We will create a custom message converter to send a trade creation request message from the client to the middle tier. The class TradeRequest collects the information entered by the user when filling in a create trade from and also contains a validation method. The TradeRequest class contains the following properties, Symbol, # shares, price, OrderType, AccountName, action (buy/sell), requestid, and user name. The converter implementation is straightforward to code as it simply adds these properties to corresponding fields of a JMS MapMessage. The implementation of "ToMessage" on the client is shown below.
public class TradeRequestConverter : IMessageConverter
{
public Message ToMessage(object objectToConvert, Session session)
{
TradeRequest tradeRequest = objectToConvert as TradeRequest;
if (tradeRequest == null)
{
throw new MessageConversionException("TradeRequestConverter can not convert object of type " +
objectToConvert.GetType());
}
try
{
MapMessage mm = session.CreateMapMessage();
mm.SetString("accountName", tradeRequest.AccountName);
mm.SetBoolean("buyRequest", tradeRequest.BuyRequest);
mm.SetString("orderType", tradeRequest.OrderType);
mm.SetDouble("price", tradeRequest.Price);
mm.SetLong("quantity", tradeRequest.Quantity);
mm.SetString("requestId", tradeRequest.RequestId);
mm.SetString("ticker", tradeRequest.Ticker);
mm.SetString("username", tradeRequest.UserName);
return mm;
} catch (Exception e)
{
throw new MessageConversionException("Could not convert TradeRequest to message", e);
}
}
... (FromMessage not shown)
}
The "FromMessage" implementation simply creates a TradeRequest object and sets its properties using values obtained from the message. See the code for details. Note that if you are using properties to marshall/unmarshall the data make sure there are no side effects of calling these properties in a marshalling context.
To send data from the client to the middle tier we need to create a mirror image of the previous JMS infrastructure, a JmsTemplate in the client and a SimpleMessageListenerContainer in the middle tier. The middle tier's message container is configured to use a Java version of the TradeRequestConverter and a message handler class, StockAppHandler, which has a method "void handle(TradeRequest). The client configuration is shown below.
<object name="jmsTemplate" type="Spring...JmsTemplate, Spring.Messaging.Tibco.Ems">
<property name="ConnectionFactory" ref="connectionFactory"/>
<property name="DefaultDestinationName" value="APP.STOCK.REQUEST"/>
<property name="MessageConverter">
<object type="Spring.JmsInterop.Converters.TradeRequestConverter, Spring.JmsInterop"/>
</property>
</object>
The hard coded client use of the template is shown below.
public void SendTradeRequest()
{
TradeRequest tradeRequest = new TradeRequest();
tradeRequest.AccountName = "ACCT-123";
tradeRequest.BuyRequest = true;
tradeRequest.OrderType = "MARKET";
tradeRequest.Quantity = 314000000;
tradeRequest.RequestId = "REQ-1";
tradeRequest.Ticker = "CSCO";
tradeRequest.UserName = "Joe Trader";
jmsTemplate.ConvertAndSend(tradeRequest);
}
A sequence diagram for sending a message on the client is shown below:
The middle tier configuration is shown below along with a simple implemention of the POJO message handler.
<bean id="jmsContainer" class="org.springframework.jms.listener.SimpleMessageListenerContainer">
<property name="connectionFactory" ref="connectionFactory"/>
<property name="destinationName" value="APP.STOCK.REQUEST"/>
<property name="concurrentConsumers" value="10"/>
<property name="messageListener" ref="messageListenerAdapter"/>
</bean>
<bean id="messageListenerAdapter" class="org.springframework.jms.listener.adapter.MessageListenerAdapter">
<property name="delegate">
<bean class="org.spring.jmsinterop.handlers.StockAppHandler"/>
</property>
<property name="defaultListenerMethod" value="handleObject"/>
<property name="messageConverter">
<bean class="org.spring.jmsinterop.converters.TradeRequestConverter"/>
</property>
</bean>
public class StockAppHandler {
protected final Log logger = LogFactory.getLog(getClass());
public void handleObject(TradeRequest tradeRequest)
{
logger.info("Recieved TradeRequest object");
}
}
The advantage of this approach is that client and middle tier developers can essentially "share" a class, in this case TradeRequest, which contains both data and functionality. One disadvantage to sharing classes, especially in large projects, is the duplication of effort in creating pairs of business objects and converters. If the data exchanged between tiers is relatively stable, this duplication can be considered a one time cost. However, if the data exchanged is changing on a weekly or daily basis as is commonly the case during active development, it can become a tedious task.
An effective way to deal with these issues is to create a single all-purpose MessageConverter than can handle multiple message types and to write the business objects once in Java and use a source code conversion tool to get the C# equivalent. The next section discusses this approach is more detail.
General Message Converter
As you can see in the previous listing of TradeRequestConverter the implementation is calling out *not* to be coded by hand. A solution that uses code generation or reflection can be used instead. The example code includes an 'XStream' inspired reflection based converter, ReflectionMessageConverter, which can convert a wide range of objects. This converter has the following features and limitations.
- All objects are marshaled by value via reflection of all field members. This choice was taken to avoid any side effects due to additional code that may reside in property setter/getters. (Adding support to control which fields are included/excluded either through external configuration or attributes/annotations similar to WCF's DataContract/DataMember attributes would be a nice enhancement.)
- The field types supported are: primitives (int, string, etc), Date, Timestamp, as well as objects composed of primitives, hashmaps, and collections (non-generic) of objects. Circular references are detected.
- The wire representation is a JMS MapMessage with an easy to understand format. This allows for other processes that do not use this converter to easily participate in the JMS message exchanges.
- The JMS provider must support nested map messages to convert collection classes.
- Provides the ability to register additional converters for arbitrary object types.
- Reduced coupling between .NET/Java since a converter does not need to know their alter-ego type. A type identifier is placed in the message that indicates which type to create. On each side the type identifier is independently mapped to a specific data type. As a convenience the case where all business objects are named similarly but only differ in terms of the namespace/package is handled with minimal configuration
The converter should be considered a work in progress and updates to it will be available on the Spring.NET website.
Some other alternative approaches to a single all purpose MessageConverter is to delegate the hard work to an existing marshalling technology. For example, you can delegate an XML/Object converter and send the XML string as the payload of the JMS Message. Recently Tangosol introduced a platform and language-neutral Portable Object Format (POF) as part of Coherence that could also be used for this same purpose.
The example application uses the ReflectionMessageConverter to send a Trade object to the client in response to the TradeRequest. As an example of sending a more complex object the client sends a PortfolioRequest and will receive a Portfolio object that contains a User and a list of Trade objects in response. The configuration of the converters is shown below.
<object name="reflectionMessageConverter"
type="Spring.JmsInterop.Converters.ReflectionMessageConverter, Spring.JmsInterop">
property name="ConversionContext" ref="conversionContext"/>
</object>
<object name="conversionContext" type="Spring.JmsInterop.Converters.ConversionContext, Spring.JmsInterop">
<property name="TypeMapper" ref="typeMapper"/>
</object>
<object name="typeMapper" type="Spring.JmsInterop.Converters.SimpleTypeMapper, Spring.JmsInterop">
<!-- use simple configuation style -->
<property name="DefaultNamespace" value="Spring.JmsInterop.Bo"/>
<property name="DefaultAssemblyName" value="Spring.JmsInterop"/>
<!--
<property name="IdTypeMapping">
<dictionary>
<entry key="1" value="Spring.JmsInterop.Bo.Trade, Spring.JmsInterop"/>
</dictionary>
</property>
-->
</object>
The simple configuration style of the TypeMapper used above uses the last part of the fully qualified type name as the type identifier that is placed into the message on the wire when marshalling. The DefaultNamespace and DefaultAssemblyName properties are used to construct the fully qualified type name when unmarshalling. The corresponding definition of the mapper on the Java side is shown below.
<bean id="classMapper" class="org.spring.jmsinterop.converters.SimpleClassMapper">
<property name="defaultPackage" value="org.spring.jmsinterop.bo"/>
<!--
<property name="idClassMapping">
<map>
<entry key="1" value="org.spring.jmsinterop.bo.Trade"/>
</map>
</property>
-->
</bean>
The IdTypeMapping/IdClassMapping properties (commented out) show how you can avoid the use of class names completely and use an arbitrary identifier to specify the type.
Sharing Business Objects
One technique that can reduce the effort in keeping business objects in sync is to use the Java Language Converter (JLCA) to automatically convert Java classes to C# 7. While this tool is intended for use as a "one-time" translation of a Java code base it can be incorporated into an automated build process and used to synchronize your business objects between Java and .NET. Business objects are actually a very good candidate for this converter since they do not contain technology specific APIs such as those for data access or web programming which are difficult for the convert correctly without subsequent manual tweaking.
However, the JLCA is not without its warts. There are a variety of limitations and quirks but you can nevertheless create complex C# classes that convert successfully to Java without manual intervention. One of the most noticeable quirks is that method names are left as lowercase but on the plus side, JavaBean get/set methods are converted to .NET properties. Other limitations are that annotations are not converted to attributes and there is no support for generics. Namespaces are left as the java package name but a simple regex post processing fixes that easily. The converter also creates C# implementations of some supporting classes if needed, for example a C# equivalent to java.util.Set. With a little experimenting you will see how far you can use this technology on your own project. A "cheat sheet" summarizing the abilities of the converter are available on Gaurav Seth's blog 8. One last tidbit of JLCA information is that the company behind the JLCA, ArtinSoft also sells a product, JLCA Companion, which allows you to change or add the transformation rules 9.
Running the JLCA on the Java classes in this example works very well. You can toggle the use of the hand coded C# business objects and the JLCA generated ones by including/excluding the "Bo" and "Jlca" directories in the .NET solution. In particular look/modify the validation method in the TradeRequest class which uses simple conditional logic and collection class manipulation. An ant script is provided to run the JLCA on the Java business objects and change the package name to the correct .NET namespace.
A screenshot of the client after having received a few market data events and sending both a TradeRequest and PortfolioRequest is shown below.
Conclusion
If you are already in a messaging environment or are drawn to the features of messaging such as asynchronous communication and publish/subscribe delivery, using Spring's JMS support in both Java and .NET provides you with a powerful starting point to create interop solutions. Spring isn't very prescriptive in terms of the contracts used to ensure compatibility between JMS producers and consumers but it does provide a simple extension point, the MessageConverter, for you to introduce a contract of your own devising. The sophistication of the converters and associated objects should match the complexity of your application. The stock trading application and ReflectionMessageConverter lay the foundation for easy experimentation of your own.
To reiterate a commonly mentioned description of the Spring framework - "it makes the easy stuff easy and the hard stuff possible". I hope that you will agree that this also applies to Spring's JMS support in a mixed .NET/Java environment. At the end of the day, no matter what route you choose for interoperability, you will find that using Spring on both .NET and Java is beneficial since the same programming model and best practices can easily be shared between the two worlds.
The source code accompanying this article can be downloaded HERE.
2 http://www.jnbridge.com/, JMS Example: http://www.jnbridge.com/blog/?p=6
3 http://www.codemesh.com/, JMS Example: http://codemesh.com/products/juggernet/examples/jms.html
4 www.springframework.net
5 http://www.tibco.com/software/messaging/enterprise_messaging_service/default.jsp
6 http://incubator.apache.org/activemq/nms.html
7 Main site: http://msdn2.microsoft.com/en-us/vjsharp/aa718346.aspx, Training: http://msdn.microsoft.com/vstudio/java/migrate/workshop/
8 http://blogs.msdn.com/gauravseth/default.aspx
9 http://www.artinsoft.com/pr_jlca.aspx