Message Header

JMS header fields are used for the proper routing and processing of JMS messages. The JMS specification defines a set of standard JMS headers. These fields can be set by the client, by the implementation class of the message, or by the JMS provider.

Important 

Those header fields set by the send () method can also be set by the client, but the send () method always ignores a field set in this manner and uses its own value. However, message recipients are able to change the header fields of a message after it arrives.

Message producers and send ()/publish () methods were discussed in detail in the previous chapter. This section covers the standard JMS header fields in detail. Tables of message header getJMSXXX() and setJMSXXX () methods can be found at the end of this section.

JMSDestination

The JMSDestination header field contains the destination to which the message was sent. The value of this field is available for the message sender only after the message is sent and for message recipients soon after the message is received. As explained in the previous chapter, destinations are specified either when the message producer is created using the session or in the send () method of the message producer for unidentified message producers.

JMSDeliveryMode

The value of JMSDeliveryMode indicates the delivery mode used when the message was sent. Delivery mode can be set for each individual message a message consumer sends or can be set globally for all the messages sent by the message producer. This field is set by the send () method.

The delivery mode specified in the send () method takes precedence over the one specified globally. If the delivery mode is not specified the default delivery mode, in other words, NON_PERSISTENT, is used. This means that the most efficient delivery mode is used, but message delivery is not guaranteed.

JMSPriority

The value of JMSPriority indicates the message priority used when the message was sent. This field is set by the send() method. Message priority can be set for each individual message a message producer sends or can be set globally for all the messages sent by the message producer. JMS defines ten levels of priority from 0 to 9 (the default priority for messages is 4). The value of this field is available for the message sender only after the message is sent and for message recipients soon after the message is received.

The send() method uses either the priority globally specified for the message producer or the priority specified in the send() method. The message priority specified in the send() method takes precedence over the one specified globally. If the message priority is not specified it uses the default global priority.

JMSExpiration

The value of JMSExpiration indicates the sum of message time-to-live used when the message was sent and the current time in GMT. Message time-to-live can be set for each individual message a message producer sends or can be set globally for all the messages sent by the message producer. This field is set by the send() method. The value of this field is available for the message sender only after the message is sent and for message recipients soon after the message is received.

The send() method uses the sum of either the message time-to-live globally specified for the message producer or the message time-to-live specified in the send() method and the current time in GMT. The message time-to-live specified in the send() method takes precedence over the one specified globally. If the message time-to-live is not specified it uses the default message time-to-live which is 0. If the message time-to-live is 0 then the expiration is also 0, which means the message will never expire. Most of the JMS providers provide administration tools for monitoring and removing messages from destinations.

JMSMessageID

The JMS providers use the JMSMessageID header field to uniquely identify messages. The JMS specification mandates the uniqueness of message IDs for a given provider implementation, but it doesn't mandate uniqueness across multiple implementations. The values of this field are required to be prefixed with the string ‘ID:’.

This field is set by the send() method and JMS clients can disable the setting of this field using the setDisableMessageID() method defined in the interface javax.jms.MessageProducer. However, providers can always ignore this and continue to assign message IDs for the messages sent by the producer.

Assigning unique message IDs to messages requires extra effort from the JMS provider and also increases the size of the message. Hence clients can use this method to notify the provider that they don't depend on the unique message IDs. Then the provider can set the message ID to null. But the provider can also ignore this hint and continue to produce the message IDs. This is entirely up to the discretion of the JMS provider. The value of this field is available for the message sender only after the message is sent and for message recipients soon after the message is received.

JMSCorrelationID

JMS clients can use the header field JMSCorrelationID for linking messages. This field is extremely useful for JMS clients working in request/response mode. We have already seen that the message IDs are unique for a given provider implementation.

JMS clients sending a message in response to a request message can set the correlation ID of the response message to the message ID of the request message. This allows the JMS client that initiated the request to easily link the response it received to the request it sent. The example application explained later uses correlation ID to implement a pseudo-duplex messaging system. The correlation ID may also be set to a provider-native byte array. JMS clients normally set the value for correlation ID.

JMSReplyTo

JMS clients can use the header field JMSReplyTo for specifying the destination to which the reply messages are to be sent. JMS clients receiving the messages can ignore the value of this field if they want. This field is also extremely useful for JMS clients working in a request/response mode. JMS clients normally set the value for this field.

JMSTimestamp

The value for JMSTimestamp indicates the time at which the provider sent the message. This need not be the same as the time at which the message was transmitted to the provider because the provider may delay the sending if the message participates in a transaction.

This field is set by the send() method and JMS clients can disable the setting of this field using the setDisableMessageTimestamp() method defined in the interface javax.jms.MessageProducer. Providers can always ignore this and continue to set the timestamp.

Assigning timestamps has the same kind of overhead associated with it as assigning message IDs. Telling the provider to disable them would cause them to be set to a default value of zero, unless the provider wishes to assign them anyway. The actual implementation is up to the provider.

JMSRedelivered

This is the only header field whose value is directly set by the provider. The provider sets the value of this field to True if it is has already delivered this message once. A message can get redelivered due to number of reasons, for example, the client fails to acknowledge the message or a runtime exception is thrown in the callback method of the asynchronous message listener.

JMSType

Providers that maintain a repository of application-specific message types use the JMSType header field. An example of an application-specific message is SonicMQ's XMLMessage type. This message has a setDocument() method that accepts an org.w3c.dom.Document as a parameter, removing the need for explicit casts and thus saving processor overhead. The JMS client sets this header.

Message Header Methods

The header fields described above each have getJMSXXX() and setJMSXXX() methods, which can be used to manipulate their contents. These methods are defined by the javax.jms.Message interface and are listed below. Note that all these methods are all public.

The getJMSXXX() Methods

Method

Description

Destination getJMSDestination() throws JMSException

Returns the destination of the message.

int getJMSDeliveryMode() throws JMSException

Returns the delivery mode.

int getJMSPriority() throws JMSException

Returns the message priority.

long getJMSExpiration() throws JMSException

Returns the message expiration time as number of milliseconds since 1st of Jan 1970.

String getJMSMessageID() throws JMSException

Returns the message ID.

String getJMSCorrelationID() throws JMSException

Returns the correlation ID as a string.

byte[] getJMSCorrelationIDAsBytes() throws JMSException

Returns the correlation ID as a byte array. The use of this method is not portable across different providers.

Destination getJMSReplyTo() throws JMSException

Returns the destination to which the reply messages are to be sent.

long getJMSTimestamp() throws JMSException

Returns the timestamp as number of milliseconds since 1st of Jan 1970.

boolean getJMSRedelivered() throws JMSException

Returns the redelivered flag.

String getJMSType() throws JMSException

Returns the JMSType set by the JMS client.

The setJMSXXX() Methods

For each getXXX() method in the table above, there is a corresponding set method. Below is a selection, and those that are missing can be extrapolated from the methods already described:

Method

Description

void setJMSDestination (Destination dest) throws JMSException

Sets the destination of the message

void setJMSDeliveryMode (int deliveryMode) throws JMSException

Sets the delivery mode

void setJMSPriority (int priority) throws JMSException

Sets the message priority

Using JMS in a Request/Response Paradigm

The sample application illustrates the use of different JMS header fields and shows how the different fields can be accessed and mutated. It also illustrates the use of JMSCorrelationID and JMSReplyTo header fields to build a pseudo-duplex messaging system where one application sends a request message and another application receives the request and sends a response to the request.

Architecture

The system uses a client and a server application communicating using the Point-to-Point (PTP) messaging model. Both the applications use a request queue and a response queue, which are stored as Administered objects in the standard WinNT file system namespace.

This example also illustrates the setting of different JMS headers by the send() method. We achieve this by printing the message headers before and after the message is sent.

The various actions performed by the client application are listed below:

  • Looks up the request queue, response queue, and queue connection factory.

  • Creates a connection using the connection factory.

  • Creates a non-transacted session in the auto-acknowledge mode using the connection.

  • Creates a queue sender for the request queue using the session.

  • Creates a text message with the message body as the current time and sets the JMSReplyTo header to the response queue and prints the JMS header of the message.

  • Sends the message and prints the JMS header fields of the sent message again.

  • Creates a queue receiver to the response queue and receives messages using the queue receiver in an infinite loop. If the JMSCorrelationID of the received message is the same as the JMSMessageID of the sent message, it breaks out of the loop.

  • Prints the message text of the sent and received messages and the message header of the response message.

The sequence diagram shown below further illustrates the sequence of actions performed by the client:

click to expand

The various actions performed by the server application on startup and on message reception are listed below:

  • Looks up the request queue and queue connection factory

  • Creates a connection and a session

  • Creates a queue receiver for the request queue using the session and registers an asynchronous message listener for the queue receiver

  • Prints the body as well as the header fields of received messages

  • Creates a new message for every received message and sets the body of the new message to the body of the received message appended with the current date and time

  • Sets the JMSCorrelationID header of the new message to the JMSMessageID header of the received message

  • Creates a queue sender for the destination specified by the JMSReplyTo header of the received message

  • Prints the JMS header fields for the new message before sending

  • Sends the message and prints the JMS header fields for the sent message

The sequence diagram shown below depicts the sequence of actions performed by the server at startup and on asynchronous message delivery:

click to expand

This example was tested against JMQ from Sun Microsystems, but should work with any other JMS-compliant provider. See Appendix A for details on porting code between providers.

Configuring Administered Objects

The sample application uses three Administered objects: a queue connection factory and two queues. The script shown below creates the required Administered objects and stores them in the standard WinNT file system namespace using the jmqconfig tool provided by JMQ. This script is specific to JMQ and may vary across different providers:

     @echo off     if "%JMQ_HOME%\lib\jmq.jar" == "\lib\jmq.jar" goto nojmqhome     REM Create and add the request queue     call "%JMQ_HOME%\bin\jmqconfig" -a -t q -n requestQueue -o "name=requestQueue" -i     "com.sun.jndi.fscontext.RefFSContextFactory" -u "file:C:\temp"     REM Create and add the request queue     call "%JMQ_HOME%\bin\jmqconfig" -a -t q -n responseQueue -o "name=requestQueue" -     i "com.sun.jndi.fscontext.RefFSContextFactory" -u "file:C:\temp"     REM Create and add the queue connection factory     call "%JMQ_HOME%\bin\jmqconfig" -a -t qf -n QCFactory -o "host=localhost" -i     "com.sun.jndi.fscontext.RefFSContextFactory" -u "file:C:\temp"     goto end     :nojmqhome        echo Please set the JMQ_HOME environment variable.        goto end     :end 

The command-line options for jmqconfig were briefly discussed in the last chapter. Please refer to the JMQ documentation for an exhaustive list of command-line options available for jmqconfig.

Note 

Note that you can download all the examples in this chapter from the Wrox web site: http://www.wrox.com.

The DuplexProperties Interface

This interface holds the constants that specify the initial context factory, provider URL and the names of the queues and queue connection factory. In a practical scenario you may store these properties in some sort of configuration file and read then using a resource bundle. The variables CTX_FACT and PROV_URL hold the name of the initial context factory and the provider URL respectively. These values are used for JNDI lookup. Remember to change the value of PROV_URL to suit your installation directory:

     public interface DuplexProperties {        public static final String CTX_FACT =           "com.sun.jndi.fscontext.RefFSContextFactory";        public static final String PROV_URL = "file:C:\\temp"; 

The variable QCF_NAME holds the JNDI name of the queue connection factory used for creating the queue connections. Both the client and the server applications use this for looking up the connection factory:

        public static final String QCF_NAME = "QCFactory"; 

The variables REQUEST_QUEUE and RESPONSE_QUEUE hold the JNDI names of the request and response queues respectively. The client application looks up both request and response queues whereas the server application looks up only the request queue. The server application sends the response to the queue specified by the client application using the JMSReplyTo header field of the message:

        public static final String REQUEST_QUEUE = "requestQueue";        public static final String RESPONSE_QUEUE = "responseQueue";     } 

The JNDIService Class

This is a utility class used by both the client and the server for performing JNDI lookup:

     import javax.naming.NamingException;     import javax.naming.Context;     import javax.naming.InitialContext;     import javax.naming.Properties;     public class JNDIService {         private static Properties prop; 

This class has a static initialization method to initialize the provider URL and initial context factory:

      public static void init (String contextFactory, String providerURL) {         prop = new Properties();         if (contextFactory != null) {             prop.put (Context.INITIAL_CONTEXT_FACTORY, contextFactory);         }         if (providerURL != null) {             prop.put (Context.PROVIDER_URL, providerURL);         }     } 

This class also provides a static utility method for performing JNDI lookup, which accepts the JNDI name as an argument and returns the looked up object:

        public static Object lookup (String jndiName) throws NamingException {           Context ctx = new InitialContext (prop);           Object obj = ctx.lookup (jndiName);           ctx.close();           return obj;        }     } 

The DuplexHelper Class

This is a utility class used by both the client and server classes for printing the different message header values for the incoming and outgoing messages:

     import javax.jms.Message;     import javax.jms.JMSException;     import javax.jms.Destination;     import javax.jms.DeliveryMode;     public class DuplexHelper { 

This class has a static method to print the values of all ten standard JMS message header fields. This method takes the Message object whose header values are to be printed as an argument. Its first action is to print the value of the JMSDestination header field. If the value of this header field is not null it prints the name of the destination, otherwise it prints the string Not set:

     public static void printHeader (Message msg) throws JMSException {        //Print JMSDestination Header        Destination dest = msg.getJMSDestination();        if (dest != null) {            System.out.print ("\tDestination: " + dest);        } else {           System.out.print ("\tDestination: Not set");        } 

The JMSDeliveryMode header field is compared against the static member variables of the interface javax.jms.DeliveryMode and the delivery mode is printed. Then the value of the JMSExpiration header field is printed:

           //Print JMSDeliveryMode Header           int deliveryMode = msq.getJMSDeliveryMode();           switch (deliveryMode) {              case DeliveryMode.PERSISTENT:              System.out.print ("|Delivery Mode: Persistent");              break;              case DeliveryMode.NON_PERSISTENT:                 System.out.print ("|Delivery Mode: Non-Persistent");                 break;              default:                 System.out.print ("|Delivery Mode: Not set");                 break;           }           //Print JMSExpiration Header            System.out.println ("|Expiration: " + msg.getJMSExpiration()); 

If the value of the JMSPriority header field is not -1 the value is printed, otherwise the string Not set is printed. The value of the JMSTimestamp header field is then printed:

           //Print JMSPriority Header           int prior = msg.getJMSPriority();           if(prior != -1) {              System.out.print ("\tPriority: " + prior);           } else {              System.out.print ("\tPriority: Not set");           }           //Print JMSTimestamp Header           System.out.print (" |Timestamp: " + msg.getJMSTimestamp()); 

If the value of the JMSReplyTo header field is not null the name of the destination to which the reply messages are expected to be sent is printed, otherwise the string "Not set" is printed. Then the value of the JMSRedelivered header field is printed:

           //Print JMSReplyTo Header           Destination replyTo = msg.getJMSReplyTo();           if(replyTo != null) {              System.out.println("|Reply To: " + replyTo);           } else {              System.out.println("|Reply To: Not set");           }           //Print JMSRedelivered Header           System.out.print("\tRedelivered: " + msg.getJMSRedelivered()); 

If the JMSType field is set the value of the message type is printed, otherwise the string "Not set" is printed:

           //Print JMSType Header           String type = msg.getJMSType();           if (type != null) {               System.out.println("|Type: " + type);           } else {              System.out.println("|Type: Not set");           } 

If the header fields for JMSMessageID and JMSCorrelationID are set their values are printed, otherwise the string "Not set" is printed:

           //Print JMSMessageID Header           String msgId = msg.getJMSMessageID();           if (msgId != null) {              System.out.println("\tMessage ID: " + msgId);           } else {              System.out.println("\tMessage ID: Not set");           }           //Print JMSCorrelationID Header           String corId = msg.getJMSCorrelationID();           if (corId != null) {              System.out.println("\tCorrelation ID: " + corId);           } else {              System.out.println("\tCorrelation ID: Not set");           }        }     } 

The DuplexClient Class

The duplex client is a Java application with a main() method. The class is initialized in the main() method and the start() method is called on the instance of the DuplexClient class. The start() method does all the processing, in other words, sending the message, printing the message header, and receiving the response message:

     import javax.jms.*;     import java.util.Date;     public class DuplexClient implements DuplexProperties {        public static void main (String args []) throws Exception {           DuplexClient client = new DuplexClient();           client.start();        } 

The start() method first initializes the JNDI lookup service class with the provider URL and the initial context factory and looks up the request queue, response queue, and the queue connection factory:

     public void start() throws Exception {           JNDIService.init (CTX_FACT, PROV_URL);           Queue requestQueue = (Queue) JNDIService.lookup (REQUEST_QUEUE);           Queue responseQueue = (Queue) JNDIService.lookup (RESPONSE_QUEUE);           QueueConnectionFactory qcf =              (QueueConnectionFactory) JNDIService.lookup (QCF_NAME); 

A queue connection is then created using the queue connection factory and a non-transacted queue session is created using this queue connection. A queue sender is created using the queue session that is attached to the request queue:

           QueueConnection qCon = qcf.createQueueConnection();           QueueSession qSes =              qCon.createQueueSession (false, QueueSession. AUTO_ACKNOWLEDGE);           QueueSender sender = qSes.createSender(requestQueue); 

A text message is created using the queue session, its message body is set to the current date-time, and the JMSReplyTo header is set to the response queue:

           TextMessage reqMsg = qSes. createTextMessage();           reqMsg.setText ("Message sent at " + new java.util.Date());           reqMsg.setJMSReplyTo (responseQueue); 

The message header fields for the message are printed using the DuplexHelper class and the message is sent. The message headers are printed once again after the message is sent:

           System.out.println ("Request message header before send.");           DuplexHelper.printHeader (reqMsg);           sender.send (reqMsg);           System.out.println ("Request message header after send.");           DuplexHelper.printHeader (reqMsg); 

A queue receiver that is attached to the response queue is created using the queue session and the queue connection is started so that the receiver can start accepting incoming messages:

           QueueReceiver receiver = qSes.createReceiver (responseQueue);           qCon.start(); 

The queue receiver sits in an infinite loop and receives messages. If the JMSCorrelationID of the received message is the same as the JMSMessageID of the sent message the message texts for the received and sent messages are printed. In addition, the message header fields of the received message are printed and the method breaks out of the infinite loop:

           while (true) {              TextMessage resMsg = (TextMessage) receiver.receive();              String msgId = reqMsg.getJMSMessageID();              String corId = resMsg.getJMSCorrelationID();              if(msgId.equals(corld)) {                 System.out.println("Response received.");                 System.out.println("Request Message: ");                 System.out.println(reqMsg.getText());                 System.out.println("Response Message: ");                 System.out.println(resMsg.getText());                 System.out.println("Response message properties.");                 DuplexHelper.printHeader (resMsg);                 qCon.close();                 return;              }           }        }     } 

The DuplexServer Class

The duplex server looks up the request queue and the queue connection factory. It creates a queue connection to the provider using the queue connection factory and uses the queue connection to create a non-transacted queue session in the auto-acknowledge mode. A queue receiver is created for the request queue and a message listener is attached to the queue receiver that asynchronously listens for messages. The queue connection is started so that it can accept incoming messages.

When the message listener receives a request message it creates a new response message using the queue session and sets the JMSCorrelationID header of the response message to the JMSMessageID header of the request message. The message listener then creates a queue sender using the queue session that is attached to the destination specified in the JMSReplyTo header of the request message and sends the message:

     import javax.jms.*;     import java.util.Date;     public class DuplexServer implements DuplexProperties{ 

The class is initialized in the main() method and the start() method is called on the instance of the DuplexServer class. The start() method does all the processing, in other words, starting the connection and setting a message listener for asynchronous message delivery:

        public static void main (String args []) throws Exception {           DuplexServer server = new DuplexServer();           server.start();        } 

The start method first initializes the JNDI lookup service class with the provider URL and the initial context factory and looks up the request queue and the queue connection factory:

        public void start() throws Exception {           JNDIService.init (CTX_FACT, PROV_URL);           Queue requestQueue = (Queue)JNDIService.lookup(REQUEST_QUEUE);           QueueConnectionFactory qcf =              (QueueConnectionFactory) JNDIService.lookup (QCF_NAME); 

Then it creates a queue connection using the queue connection factory and a non-transacted queue session using the queue connection:

           final QueueConnection qCon = qcf.createQueueConnection();           final QueueSession qSes = qCon.createQueueSession (false,              QueueSession.AUTO_ACKNOWLEDGE);           QueueReceiver receiver = qSes.createReceiver (requestQueue); 

A message listener is created as an inner class and registered to the queue receiver. The server uses the callback method of the message listener to receive the request messages sent by clients:

           receiver.setMessageListener (new MessageListener() {              public void onMessage (Message msg) {                 try {                    TextMessage reqMsg = (TextMessage)msg;                    System.out.println("Request received.");                    System.out.println("Request Message: " +                       reqMsg.getText());                    System.out.println("Request message header.");                    DuplexHelper.printHeader (reqMsg); 

The message listener then creates a queue sender using the queue session that is attached to the destination specified in the JMSReplyTo header of the request message. The server creates a new message from the same session:

                    Queue responseQueue = (Queue) reqMsg.getJMSReplyTo();                    QueueSender sender = qSes.createSender (responseQueue);                    TextMessage resMsg = qSes.createTextMessage();                    resMsg.setText (reqMsg.getText() + "\r\nProcessed at "                                                                + new Date()); 

It then sets the JMSCorrelationID header of the response message to the JMSMessageID header of the request message:

                    resMsg.setJMSCorrelationID(reqMsg.getJMSMessageID());                    System.out.println("Response message header before send.");                    DuplexHelper.printHeader (resMsg); 

The final step is to send the response to the destination specified in the JMSReplyTo header of the request message. Finally the server starts the queue connection so that it can start processing the request messages:

                    sender.send (resMsg);                    System.out.println("Response message header after send.");                    DuplexHelper.printHeader(resMsg);                 } catch (Exception ex) {                    throw new RuntimeException(ex.getMessage());                 }              }           });           qCon.start();        }     } 

Running the Application

Before you start the application, first run the configuration script from above (DuplexConfig.bat from the download) to store the required Administered objects and then start the irouter binary that comes with JMQ.

Start the server by invoking the Java interpreter on DuplexServer class and start the client by invoking the Java interpreter on the DuplexClient class. When you compile and run the example please make sure you have the required JAR files from the JMQ lib directory in the class path. The relevant files are fscontext. jar, jms. jar, jndi. jar, jmq. jar, and providerutil. jar.

The output from the client is shown below:

click to expand

The output from the server is shown below:

click to expand

If you compare the message headers for a given message before and after it is sent you can see that the headers JMSDestination, JMSDeliveryMode, JMSExpiration JMSPriority, JMSTimestamp, and JMSMessageID are set by the send() method whereas the JMS client sets the values for the headers JMSReplyTo, JMSCorrelationID, and JMSType. The provier always sets the JMSRedelivered header.



Professional JMS
Professional JMS
ISBN: 1861004931
EAN: 2147483647
Year: 2000
Pages: 154

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net