The JMS model

JMS defines a generic view of a message service. It is important to understand this view, and how it maps onto the underlying WebSphere MQ Everyplace system. The generic JMS model is based around the following interfaces that are defined in Sun's javax.jms package:

Connection
This provides a connection to the underlying messaging service and is used to create Sessions.

Session
This provides a context for producing and consuming messages, including the methods used to create MessageProducers and MessageConsumers.

MessageProducer
This is used to send messages.

MessageConsumer
This is used to receive messages.

Destination
This represents a message destination.
Note:
A connection is thread safe, but sessions, message producers, and message consumers are not. While the JMS specification allows a Session to be used by more than one thread, it is up to the user to ensure that Session resources are not concurrently used by multiple threads. The recommended strategy is to use one Session per application thread.

Therefore, in WebSphere MQ Everyplace terms:

Connection
This provides a connection to a WebSphere MQ Everyplace queue manager. All the Connections in a JVM must connect to the same queue manager, because WebSphere MQ Everyplace supports a single queue manager per JVM. The first connection created by an application will try and connect to an already running queue manager, and if that fails will attempt to start a queue manager itself. Subsequent connections will connect to the same queue manager as the first connection.

Session
This does not have an equivalent in WebSphere MQ Everyplace

Message producer and message consumer
These do not have direct equivalents in WebSphere MQ Everyplace. The MessageProducer invokes the putMessage() method on the queue manager. The MessageConsumer invokes the getMessage() method on the queue manager.

Destination
This represents a WebSphere MQ Everyplace queue.
Note:
WebSphere MQ Everyplace JMS can put messages to a local queue or an asynchronous remote queue and it can receive messages from a local queue. It cannot put messages to or receive messages from a synchronous remote queue.

The generic JMS interfaces are subclassed into more specific versions for Point-to-point and Publish or Subscribe behaviour. WebSphere MQ Everyplace implements the Point-to-point subclasses of JMS. The Point-to-point subclasses are:

QueueConnection
Extends Connection

QueueSession
Extends Session

QueueSender
Extends MessageProducer

QueueReceiver
Extends MessageConsumer

Queue
Extends destination

We recommend writing application programs that use only references to the interfaces in javax.jms. All vendor-specific information is encapsulated in implementations of:

These are known as "administered objects", that is, objects that can be administered and stored in a JNDI namespace. A JMS application can retrieve these objects from the namespace and use them without needing to know which vendor provided the implementation. However, on small devices looking up objects in a JNDI namespace may be impractical or represent an unnecessary overhead. We, therefore, provide two versions of the QueueConnectionFactory and Queue classes. The parent classes, MQeQueueConnectionFactory.class, MQeJMSQueue.class, provide the base JMS functionality but cannot be stored in JNDI, while subclasses, MQeJNDIQueueConnectionFactory.class, and the MQeJMSJNDIQueue.class, add the necessary functionality for them to be stored and retrieved from JNDI.

Building a connection

You normally build connections indirectly using a connection factory. A JNDI namespace can store a configured factory, therefore insulating the JMS application from provider-specific information. See the section Using JNDI, below, for details on how to store and retrieve objects using JNDI.

If a JNDI namespace is not available, you can create factory objects at runtime. However, this reduces the portability of the JMS application because it requires references to WebSphere MQ Everyplace specific classes. The following code creates a QueueConnectionFactory. The factory uses a WebSphere MQ Everyplace queue manager that is configured with an initialisation (ini) file:

QueueConnectionFactory factory; 
factory = new com.ibm.mqe.jms.MQeJNDIQueueConnectionFactory(); 
((com.ibm.mqe.jms.MQeJNDIQueueConnectionFactory)factory).
setIniFileName(<initialisation file>)

The section on Configuring WebSphere MQ Everyplace JMS objects provides more information about configuring connection factories.

Using the factory to create a connection

Use the createQueueConnection() to create a QueueConnection:

QueueConnection connection;
connection = factory.createQueueConnection();

Starting the connection

Under the JMS specification, connections are not active upon creation. Until the connection starts, MessageConsumers that are associated with the connection cannot receive any messages. Use the following command to start the connection:

connection.start();

Obtaining a session

Once a connection has been created, you can use the createQueueSession() method on the QueueConnection to obtain a session. The method takes two parameters:

  1. A boolean that determines whether the session is "transacted" or "non-transacted".
  2. A parameter that determines the "acknowledge" mode. This is used when the session is "non-transacted".

The simplest case is that where acknowledgements are used and are handled by JMS itself with AUTO_ACKNOWLEDGE, as shown in the following code fragment:

QueueSession session;
boolean transacted = false;
session = connection.createQueueSession(transacted, Session.AUTO_ACKNOWLEDGE);

Figure 7. Obtaining a session once a connection is created

Diagram showing how to use createQueueSession() methon on the QueueConnection to obtain a session, once a QueueConnection has been created. This implements the createSender() and createReceiver() methods on QueueSession.

Sending a message

Messages are sent using a MessageProducer. For point-to-point this is a QueueSender that is created using the createSender() method on QueueSession. A QueueSender is normally created for a specific Queue, so that all messages sent using that sender are sent to the same destination. Queue objects can be either created at runtime, or built and stored in a JNDI namespace. Refer to Using Java Naming and Directory Interface (JNDI), for details on how to store and retrieve objects using JNDI.

JMS provides a mechanism to create a Queue at runtime that minimizes the implementation-specific code in the application. This mechanism uses the QueueSession.createQueue() method, which takes a string parameter describing the destination. The string itself is still in an implementation-specific format, but this is a more flexible approach than directly referencing the implementation classes.

For WebSphere MQ Everyplace JMS the string is the name of the WebSphere MQ Everyplace queue. This can optionally contain the queue manager name. If the queue manager name is included, the queue name is separated from it by a plus sign '+', for example:

ioQueue = session.createQueue("myQM+myQueue"); 

This will create a JMS Queue representing the WebSphere MQ Everyplace queue "myQueue" on queue manager "myQM". If no queue manager name is specified the local queue manager is used, i.e. the one that JMS is connected to. For example:

String queueName = "SYSTEM.DEFAULT.LOCAL.QUEUE";
 
...
 
ioQueue = session.createQueue(queueName);  

This will create a JMS Queue representing the WebSphere MQ Everyplace queue SYSTEM.DEFAULT.LOCAL.QUEUE on the queue manager that the JMS Connection is using.

Message types

JMS provides several message types, each of which embodies some knowledge of its content. To avoid referencing the implementation-specific class names for the message types, methods are provided on the Session object for message creation. In the sample program, a text message is created in the following manner:

System.out.println("Creating a TextMessage");
TextMessage outMessage = session.createTextMessage();
System.out.println("Adding Text");
outMessage.setText(outString);

The message types that can be used are:

Note:
Note that Version 2.0 of WebSphere MQ Everyplace does not support these two message types:

The message types are described in more detail later in this section.

Receiving a message

Messages are received by using a QueueReceiver. This is created from a Session by using the createReceiver() method. This method takes a Queue parameter that defines where the messages are received from. See "Sending a message" above for details of how to create a Queue object. The sample program creates a receiver and reads back the test message with the following code:

QueueReceiver queueReceiver = session.createReceiver(ioQueue); 
Message inMessage = queueReceiver.receive(1000);

The parameter in the receive call is a timeout in milliseconds. This parameter defines how long the method should wait if there is no message available immediately. You can omit this parameter, in which case the call blocks indefinitely. If you do not want any delay, use the receiveNoWait() method. The receive methods return a message of the appropriate type. For example, if a TextMessage is put on a queue, when the message is received the object that is returned is an instance of TextMessage . To extract the content from the body of the message, it is necessary to cast from the generic Message class, which is the declared return type of the receive methods, to the more specific subclass, such as TextMessage . If the received message type is not known, you can use the "instanceof" operator to determine which type it is. It is good practice always to test the message class before casting, so that unexpected errors can be handled gracefully. The following code illustrates the use of "instanceof", and extraction of the content from a TextMessage:

if (inMessage instanceof TextMessage){
    String replyString = ((TextMessage)inMessage).getText();
     ...
} else {
    //Print error message if Message was not a TextMessage.
    System.out.println("Reply message was not a TextMessage");
}

Message selectors

JMS provides a mechanism to select a subset of the messages on a queue so that only the subset is returned by a receive call. When creating a WebSphere MQ Everyplace, a string can be provided that contains an Structured Query Language (SQL) expression to determine which messages to retrieve. The selector can refer to fields in the JMS message header as well as fields in the message properties (these are effectively application-defined header fields).

This version of WebSphere MQ Everyplace JMS supports a restricted set of selectors, which are equivalent to the filter mechanism within WebSphere MQ Everyplace itself. The message selectors supported by WebSphere MQ Everyplace JMS are described in the JMS Messages section below.

The following example shows how to select on a user-defined property named myProp:

queueReceiver = session.createReceiver(ioQueue, "myProp ='blue'");
Note:
The JMS specification does not permit the selector associated with a receiver to be changed. Once a receiver is created, the selector is fixed for the lifetime of that receiver. This means that if you require different selectors, you must create new receivers.

Asynchronous delivery

An alternative to making calls to QueueReceiver.receive() is to register a method that is called automatically when a suitable message is available. The following fragment illustrates the mechanism:

import javax.jms.*;
public class MyClass implements MessageListener
{
    // The method that will be called by JMS when a message is available.
    public void onMessage(Message message)
    {
        System.out.println("message is "+message);
        //application specific processing here
          ...
    }
}
  ...
//In Main program (possibly of some other class)
MyClass listener = new MyClass();
queueReceiver.setMessageListener(listener);
//main program can now continue with other application specific
//behaviour.
Note:
Use of asynchronous delivery with a QueueReceiver marks the entire Session as asynchronous. All other QueueReceivers belonging to the same Session should also use asynchronous delivery. It is an error to make an explicit call to the receive methods of a QueueReceiver that is associated with a Session that is using asynchronous delivery.

WebSphere MQ Everyplace strictly interprets the JMS specification requirement that Sessions are single threaded. This has the following consequences:

Handling errors

Any runtime errors in a JMS application are reported by exceptions. The majority of methods in JMS throw JMSExceptions to indicate errors. It is good programming practice to catch these exceptions and handle them appropriately. Unlike normal Java Exceptions, a JMSException may contain a further exception embedded in it. For JMS, this can be a valuable way to pass important detail from the underlying transport. When a JMSException is thrown as a result of WebSphere MQ Everyplace raising an exception, the exception is usually included as the embedded exception in the JMSException. The standard implementation of JMSException does not include the embedded exception in the output of its toString() method. Therefore, it is necessary to check explicitly for an embedded exception and print it out, as shown in the following fragment:

try {
    ...code which may throw a JMSException
} catch (JMSException je) { 
    System.err.println("caught "+je);
    Exception e = je.getLinkedException();
    if (e != null) {
        System.err.println("linked exception:"+e);
    }
}

Exception listener

For asynchronous message delivery, the application code cannot catch exceptions raised by failures to receive messages. This is because the application code does not make explicit calls to receive() methods. To cope with this situation, it is possible to register an ExceptionListener, which is an instance of a class that implements the onException() method. When a serious error occurs, this method is called with the JMSException passed as its only parameter. Further details are in Sun's JMS documentation.

JMS messages

JMS messages are composed of the following parts:

Header
All messages support the same set of header fields. Header fields contain values that are used by both clients and providers to identify and route messages.

Properties
Each message contains a built-in facility to support application-defined property values. Properties provide an efficient mechanism to filter application-defined messages.

Body
JMS defines several types of message body which cover the majority of messaging styles currently in use. JMS defines five types of message body:

Text
A message containing a java.lang.String

Object
A message that contains a Serializable java object

Bytes
A stream of uninterpreted bytes for encoding a body to match an existing message format

Stream
A stream of Java primitive values filled and read sequentially, not supported in this version of WebSphere MQ Everyplace JMS

Map
A set of name-value pairs, where names are Strings and values are Java primitive types. The entries can be accessed sequentially or randomly by name. The order of the entries is undefined. Map is not supported in this version of WebSphere MQ Everyplace JMS.

The JMSCorrelationID header field is used to link one message with another. It typically links a reply message with its requesting message.

Message selectors

A Message contains a built-in facility to support application-defined property values. In effect, this provides a mechanism to add application-specific header fields to a message. Properties allow an application, via message selectors, to have a JMS provider select or filter messages on its behalf, using application-specific criteria. Application-defined properties must obey the following rules:

Property values are set before sending a message. When a client receives a message, the message properties are read-only. If a client attempts to set properties at this point, a MessageNotWriteableException is thrown. If clearProperties() is called, the properties can then be both read from, and written to.

A property value may duplicate a value in a message's body, or it may not. JMS does not define a policy for what should or should not be made into a property. However, for best performance, applications should only use message properties when they need to customize a message's header. The primary reason for doing this is to support customized message selection. A JMS message selector allows a client to specify the messages that it is interested in by using the message header. Only messages whose headers match the selector are delivered. Message selectors cannot reference message body values. A message selector matches a message when the selector evaluates to true when the message's header field and property values are substituted for their corresponding identifiers in the selector.

A message selector is a String, which can contain:

Literals

Identifiers

White space
This is the same as is defined for Java, space, horizontal tab, form feed, and line terminator.

Logical operators
Currently supports AND only.

Comparison operators

Note that Arithmetic operators are not currently supported.

The following message selector selects messages with a message type of car and a colour of blue: "JMSType ='car 'AND colour ='blue'"

When selecting Header fields WebSphere MQ Everyplace will interpret exact numeric literals so that they match the type of the field in question, that is a selector testing the JMSPriority or JMSDeliveryMode Header fields will interpret an exact numeric literal as an int, whereas a selector testing JMSExpiration or JMSTimestamp will interpret an exact numeric literal as a long. However, when selecting message properties WebSphere MQ Everyplace will always interpret an exact numeric literal as a long and an approximate numeric literal as a double. Application specific properties intended to be used for message selection should therefore be set using the setLongProperty and setDoubleProperty methods respectively.



© IBM Corporation 2002. All Rights Reserved