Chapter 1. JMS

1.1. Introduction

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 C# object" (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.

JMS can be roughly divided into two areas of functionality, namely the production and consumption of messages. The JmsTemplate class is used for message production and synchronous message reception. For asynchronous reception Spring provides a message listener container, SimpleMessageListenerContainer, that can be used to create Message-Driven POCOs (MDPs).

Note that JMS support is currently provided only for TIBCO's JMS implementation named TIBCO Enterprise Message Service (EMS). 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.

The namespace Spring.Messaging.Tibco.Ems provides the core functionality for using JMS. It contains JMS template classes that simplifies the use of the JMS by handling the creation and release of resources, much like the AdoTemplate does for ADO.NET. The design principle common to Spring template classes is to provide helper methods to perform common operations and for more sophisticated usage, delegate the essence of the processing task to user implemented callback interfaces. The JMS template follows the same design. The classes offer various convenience methods for the sending of messages, consuming a message synchronously, and exposing the JMS session and message producer to the user.

The namespace Spring.Messaging.Tibco.Ems.Support.Converter provides a MessageConverter abstraction to convert between .NET objects and JMS messages. The namespace Spring.Messaging.Tibco.Ems.Support.Destinations provides various strategies for managing JMS destinations, such as providing a service locator for destinations stored in a directory service.

1.2. Using Spring JMS

1.2.1. JmsTemplate

Code that uses the JmsTemplate only needs to implement callback interfaces giving them a clearly defined contract. The IMessageCreator callback interface creates a message given a Session provided by the calling code in JmsTemplate. In order to allow for more complex usage of the JMS API, the callback ISessionCallback provides the user with the JMS session and the callback IProducerCallback exposes a Session and MessageProducer pair.

The JMS API exposes two types of send methods, one that takes delivery mode, priority, and time-to-live as quality of service (QOS) parameters and one that takes no QOS parameters which uses default values. Since there are many send methods in JmsTemplate, the setting of the QOS parameters have been exposed as bean properties to avoid duplication in the number of send methods. Similarly, the timeout value for synchronous receive calls is set using the property ReceiveTimeout.

1.2.2. Connections

The JmsTemplate requires a reference to a ConnectionFactory. The ConnectionFactory is part of the JMS specification and serves as the entry point for working with JMS. It is used by the client application as a factory to create connections with the JMS provider and encapsulates various configuration parameters, many of which are vendor specific such as SSL configuration options.

Note: The TIBCO implementation is not interface based and its methods are not virtual so no additional functionality that may otherewise be part of a ConnectionFactory 'Wrapper' are provided. This type of functionality wil be available when Spring.NET uses the implementation neutral NMS AP(s.

1.2.3. Destination Management

In Java implementations of JMS, Connections and Destinations are 'administered objects' accessible though JNDI. In .NET each vendor has selected a different approach, generally JNDI inspired, to retrieve Connections and Destinations that were configured administratively. You can use these vendor specific APIs to perform dependency injection on references to JMS Destination objects in Sprng's XML configuration file by creating am implementation of IObjectFactory.

However, this approach of administerd objects can be quite cumbersome if there are a large number of destinations in the application or if there are advanced destination management features unique to the JMS provider. Examples of such advanced destination management would be the creation of dynamic destinations or support for a hierarchical namespace of destinations. The JmsTemplate delegates the resolution of a destination name to a JMS destination object to an implementation of the interface IDestinationResolver. DynamicDestinationResolver is the default implementation used by JmsTemplate and accommodates resolving dynamic destinations. A JndiDestinationResolver is also provided that acts as a service locator for destinations contained in JNDI and optionally falls back to the behavior contained in DynamicDestinationResolver.

Quite often the destinations used in a JMS application are only known at runtime and therefore cannot be administratively created when the application is deployed. This is often because there is shared application logic between interacting system components that create destinations at runtime according to a well-known naming convention. Even though the creation of dynamic destinations are not part of the JMS specification, most vendors have provided this functionality. Dynamic destinations are created with a name defined by the user which differentiates them from temporary destinations and are often not registered in a JNDI-like directory.. The API used to create dynamic destinations varies from provider to provider since the properties associated with the destination are vendor specific. However, a simple implementation choice that is sometimes made by vendors is to disregard the warnings in the JMS specification and to use the TopicSession method createTopic(String topicName) or the QueueSession method createQueue(String queueName) to create a new destination with default destination properties. Depending on the vendor implementation, DynamicDestinationResolver may then also create a physical destination instead of only resolving one.

The boolean property PubSubDomain determines the behavior of dynamic destination resolution via implementations of the DestinationResolver interface.

You can also configure the JmsTemplate with a default destination via the property defaultDestination. The default destination will be used with send and receive operations that do not refer to a specific destination.

1.2.4. Message Listener Containers

One of the most common uses of JMS is to concurrently process messages delivered asynchronously.

A subclass of AbstractMessageListenerContainer is used to receive messages from JMS and drive the Message-Driven POCOs (MDPs) that are injected into it. The AbstractMessageListenerContainer is responsible for all threading of message reception and dispatch into the MDPs for processing. A message listener container is the intermediary between an MDP and a messaging provider, and takes care of registering to receive messages, participating in transactions, resource acquisition and release, exception conversion and suchlike. This allows you as an application developer to write the (posssibly complex) business logic associated with receiving a message (and possibly responding to it), and delegates boilerplate JMS infrastructure concerns to the framework. There are one subclasses of AbstractMessageListenerContainer packaged with Spring - SimpleMessageListenerContainer.

SimpleMessageListenerContainer creates a fixed number of JMS sessions at startup and uses them throughout the lifespan of the container. This subclass doesn't allow for dynamic adaption to runtime demands or participate in transactional reception of messages.

Spring.Java provides two other subclasses, one to support distributed transactions and the other to provide a dynamic session management to optimize concurrent processing. Distributed transaction support is not provided by .NET C# vendors (AFAIK) and neither is the dynamic session management support which is based on the ServerSessionPool SPI - an optional part of the JMS specification.

1.2.5. Transaction Management

TBD. This relates to integration with Spring's transaction management features, the ability to have transacted JMS sessions is supported.

1.3. Sending a Message

The JmsTemplate contains three convenience methods to send a message. The methods are listed below.

  • void Send(Destination destination, IMessageCreator messageCreator)

  • void Send(string destinationName, IMessageCreator messageCreator)

  • void Send(IMessageCreator messageCreator)

Which differ in how the destination is specified. In first case the JMS Destination object is specified directly. The second case specifies the destination using a string that is then resolved to a JMS JMS Destination object using the DestinationResolver associated with the template. The last method sends the message to the destination specified by JmsTemplates DefaultDestination property.

All methods take as an argument an instance of IMessageCreator which defines the API contract for you to create the JMS message. The interface is show below

public interface IMessageCreator {        
  Message CreateMessage(Session session);
}

Intermediate JMS Sessions and MessageProducers needed to send the message are managed by JmsTemplate. The session passed in to the method is never null. There is a similar set methods that use a delegate instead of the interface, which can be convenientwhen writing small implementaitons in .NET 2.0 using anonymous delegates. Larger, more complex implementations of the method 'CreateMessage' are better suited to an interface based implementation.

  • void SendWithDelegate(Destination destination, MessageCreatorDelegate messageCreatorDelegate)

  • void SendWithDelegate(string destinationName, MessageCreatorDelegate messageCreatorDelegate)

  • void SendWithDelegate(MessageCreatorDelegate messageCreatorDelegate)

The declaration of the delegate is

public delegate Message MessageCreatorDelegate(Session session);

The following class shows how to use the API with an anonymous delegate, making for very terse syntax and easy access to local variables. A more realistic example would create the JmsTemplate via dependency injection, allowing for easy configuration of the connection string. A convenience class, JmsGatewaySupport, already contains a property of type JmsTemplate for you to use in

    public class SimplePublisher
    {
        private JmsTemplate template;
        
        public SimplePublisher()
        {
            template = new JmsTemplate(new ConnectionFactory("tcp://localhost:7222"));
            template.PubSubDomain = true;
        }
        
        public void Publish(string ticker, double price)
        {
            template.SendWithDelegate("APP.STOCK",
                          delegate(Session session)
                          {
                              MapMessage message = session.CreateMapMessage();
                              message.SetString("TICKER", ticker);
                              message.SetDouble("PRICE", price);
                              message.Priority = 2;
                              return message;
                          });
        }
    }


1.3.1. Using MessageConverters

In order to facilitate the sending of domain model objects, the JmsTemplate has various send methods that take a .NET object as an argument for a message's data content. The overloaded methods ConvertAndSend and ReceiveAndConvert in JmsTemplate delegate the conversion process to an instance of the MessageConverter interface. This interface defines a simple contract to convert between .NET objects and JMS messages. The default implementation SimpleMessageConverter supports conversion between String and TextMessage, byte[] and BytesMesssage, and System.Collections.IDictionary and MapMessage. By using the converter, you and your application code can focus on the business object that is being sent or received via JMS and not be concerned with the details of how it is represented as a JMS message.

The family of ConvertAndSend messages are similar to that of the Send method with the additional argument of type IMessagePostProcessor. These methods are listed below.

  • void ConvertAndSend(object message)

  • void ConvertAndSend(object message, IMessagePostProcessor postProcessor)

  • void ConvertAndSend(string destinationName, object message)

  • void ConvertAndSend(string destinationName, object message, IMessagePostProcessor postProcessor);

  • void ConvertAndSend(Destination destination, object message)

  • void ConvertAndSend(Destination destination, object message, IMessagePostProcessor postProcessor)

In the previous example the message priority was set inside the callback. Generally speaking, converters should not be responsible for setting Quality of Service parameters since they are not aware of the context in which they are being called. The following code show this in action.

public void PublishUsingDict(string ticker, double price)
{
  IDictionary marketData = new Hashtable();
  marketData.Add("TICKER", ticker);
  marketData.Add("PRICE", price);
  template.ConvertAndSend("APP.STOCK", marketData);
}

A reflection based converter that can converter arbitrary objects is available as a seperate project.

1.4. Session and Producer Callback

While the send operations cover many common usage scenarios, there are cases when you want to perform multiple operations on a JMS Session or MessageProducer. The SessionCallback and ProducerCallback expose the JMS Session and Session / MessageProducer pair respectfully. The Execute() methods on JmsTemplate execute these callback methods.

  • public object Execute(IProducerCallback action)

  • public object Execute(ISessionCallback action)

Where ISessionCallback and IProducerCallback are

public interface IProducerCallback
{
    object DoInJms(Session session, MessageProducer producer);
}

and

public interface ISessionCallback
{
    object DoInJms(Session session);
}

1.5. Receiving a message

1.5.1. Synchronous Reception

While JMS is typically associated with asynchronous processing, it is possible to consume messages synchronously. The overloaded Receive(..) methods provide this functionality. During a synchronous receive, the calling thread blocks until a message becomes available. This can be a dangerous operation since the calling thread can potentially be blocked indefinitely. The property ReceiveTimeout specifies how long the receiver should wait before giving up waiting for a message.

The Receive methods are listed below

  • public Message Receive()

  • public Message Receive(Destination destination)

  • public Message Receive(string destinationName)

  • public Message ReceiveSelected(string messageSelector)

  • public Message ReceiveSelected(string destinationName, string messageSelector)

  • public Message ReceiveSelected(Destination destination, string messageSelector)

The Recieve method without arguments used the DefaultDestination. The RecieveSelected methods apply the provided JMS message selector string to the MessageConsumer that is created.

The ReceiveAndConvert methods apply the templates message converter when receiving a message. These methods are listed below.

  • public object ReceiveAndConvert()

  • public object ReceiveAndConvert(Destination destination)

  • public object ReceiveAndConvert(string destinationName)

  • public object ReceiveSelectedAndConvert(string messageSelector)

  • public object ReceiveSelectedAndConvert(string destinationName, string messageSelector)

  • public object ReceiveSelectedAndConvert(Destination destination, string messageSelector)

1.5.2. Asynchronous Reception

You can register a class that implements the IMessageListener interface. In the case of TIBCO EMS this interface is defined as

public interface IMessageListener
{
      void OnMessage(Message message);
}

Other vendors may provide a delegate based version of this interface.

You register you listener with a message listener container that specifies JMS configuration parameters and the number of concurrent consumers to create. There is an abstract base class for message listener containers, AbstractMessageListenerContainer, and one concrete implementation, SimpleMessageListenerContainer. SimpleMessageListenerContainer creates a fixed number of JMS Sessions/MessageConsumer pairs as set by the property ConcurrentConsumers. Here is a sample configuration

      <object id="connectionFactory" type="TIBCO.EMS.ConnectionFactory, TIBCO.EMS">
        <constructor-arg index="0" value="tcp://localhost:7222"/>
      </object>

      <object id="messageListener" type="MyApp.MyMessageListener, MyApp"/>

      <object id="jmsContainer" type="Spring.Messaging.Tibco.Ems.Listener.SimpleMessageListenerContainer, Spring.Messaging.Tibco.Ems">
        <property name="ConnectionFactory" ref="connectionFactory"/>
        <property name="DestinationName" value="APP.REQUEST"/>
        <property name="ConcurrentConsumers" value="10"/>
        <property name="MessageListener" ref="messageListener"/>
      </object>

The property PubSubDomain is by defalt false, meaning point-to-point/Queue delivery semantics. The above configuration will create 10 threads that process messages off of the queue named "APP.REQUEST". The threads are those owned by the JMS provider as a result of creating a JMS MessageConsumer. Other important properties are ClientID, used to set the ClientID of the JMS Connection and MessageSelector to specify the JMS 'sql-like' message selector string. Durable subscriptions are supported via the properties SubscriptionDurable and DurableSubscriptionName. You may also register a listener using the property ExceptionListener.

1.5.2.1. MessageListenerAdapater

The MessageListenerAdapter allows methods of a class that does not implement the IMessageListener interface to be invoked upon message delivery. Lets call this class the 'message handler' class. To achive this goal the MessageListenerAdapter implements the standard IMessageListener interface to recieve a message and then delegates the processing to the message handler class. Since the message handler class does not contain methods that refer to JMS artifacts such as Message,TextMessage etc, the MessageListenerAdapter uses a MessageConverter to bridge the JMS and 'plain object' worlds. As a reminder, the provided SimpleMessageConverter converts from TextMessage to string, BytesMessage to byte[], and MapMessage to IDictionary. Once the incoming message is converted to an IDictionary (for example) a method with the name 'Handle' is invoked via reflection passing in the IDictionary as an argument.

Using the SimpleMessageConverter, your "plain old object' messaging callback implementation would look like this.

public class SimpleMessageHandler
{
    public void HandleObject(IDictionary dict)
    {
       ...
    }

    public void HandleObject(string text)
    {
      ...
    }

    public void HandleObject(byte[] data)
    {
      ...
    }
     
}

Notice how the various message handling methods are strongly typed according to the contents of the various Message types that they can receive and handle. The following configuration shows how to hook up this class to process incoming JMS messages.

      <object name="simpleMessageHandler, type="MyApp.SimpleMessageHandler, MyApp"/>
   
      <object name="simpleMessageConverter" 
              type="Spring.Messaging.Tibco.Ems.Support.Converter.SimpleMessageConverter, Spring.Messaging.Tibco.Ems"/>

      <object id="messageListenerAdapter" type="Spring.Messaging.Tibco.Ems.Listener.Adapter.MessageListenerAdapter, ">
        <property name="DelegateObject" ref="simpleMessageHandler"/>
        <property name="DefaultListenerMethod" value="HandleObject"/>
        <property name="MessageConverter" ref="simpleMessageConverter"/>
      </object>


      <object id="connectionFactory" type="TIBCO.EMS.ConnectionFactory, TIBCO.EMS">
        <constructor-arg index="0" value="tcp://localhost:7222"/>
      </object>

      <object id="jmsContainer" type="Spring.Messaging.Tibco.Ems.Listener.SimpleMessageListenerContainer, Spring.Messaging.Tibco.Ems">
        <property name="ConnectionFactory" ref="connectionFactory"/>
        <property name="DestinationName" value="APP.REQUEST"/>
        <property name="ConcurrentConsumers" value="10"/>
        <property name="MessageListener" ref="messageListener"/>
      </object>

Another of the capabilities of the MessageListenerAdapter class is the ability to automatically send back a response Message if a handler method returns a non-void value. Any non-null value that is returned from the execution of the handler method will (in the default configuration) be converted into a TextMessage. The resulting TextMessage will then be sent to the Destination (if one exists) defined in the JMS Reply-To property of the original Message, or the default Destination set on the MessageListenerAdapter (if one has been configured); if no Destination is found then an InvalidDestinationException will be thrown (and please note that this exception will not be swallowed and will propagate up the call stack).

1.6. TIBCO Specific Details

Caching of JMS resources is usually done by a wrapping the 'raw provider' JMS implementation with an implementation that will cache JMS resources. The resources that are candidates for caching are the JMS Connection, Session, and MessageProducer. The JMS specification requires that the Connection be thread safe. The Session and MessageProducer are not required to be thread safe but they are in TIBCO's implementation. The most important resource to cache is the JMS Connection since the flow of events in JmsTemplate is to create/close a connection on each operation and this is an expensive operation. In the Java version of JmsTemplate a class, SingleConnectionFactory is provided in which the same Connection is returned on calls to createConnection() and all calls to .close() on the returned Connection are ignored. Since TIBCO's connection class does not have an interface nor virtual methods this strategy is not possible. An alternative strategy is to 'hard-code' the caching of these resources within JmsTemplate and SimpleMessageContainer. This functionality is controlled by the property CacheJmsResources and is set to true by default, resulting in caching of Connection, Session, and MessageProducer. When integration with NMS (a set of common interfaces for .NET JMS providers) is completed this will not be necessary and we can use a wrapper implementation of the NMS API that performs caching of JMS resources in an appropriate manner for each vendor.