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:
Therefore, in WebSphere MQ Everyplace terms:
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:
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.
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.
Use the createQueueConnection() to create a QueueConnection:
QueueConnection connection; connection = factory.createQueueConnection();
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();
Once a connection has been created, you can use the createQueueSession() method on the QueueConnection to obtain a session. The method takes two parameters:
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
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.
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:
The message types are described in more detail later in this section.
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"); }
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'");
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.
WebSphere MQ Everyplace strictly interprets the JMS specification requirement that Sessions are single threaded. This has the following consequences:
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); } }
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 are composed of the following parts:
The JMSCorrelationID header field is used to link one message with another. It typically links a reply message with its requesting message.
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:
JMSMessageID, JMSTimestamp, JMSCorrelationID, and JMSType values may be null, and if so, are treated as a NULL value.
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.