This chapter focuses on the anatomy of a message: the individual parts that make up a message (headers, properties, and the different kinds of message payloads). Appendix B, Appendix C, and Appendix D cover additional information that will prove invaluable as a reference when developing JMS applications. Appendix B, provides in-depth information on the purpose and application of JMS headers; Appendix C, covers the rules governing the use of JMS properties; and Appendix D, covers the syntax of message selectors. Although you do not need to read these appendixes to understand subsequent chapters in this book, you will need them as a reference when implementing real JMS applications. After you finish reading this chapter, take a look at Appendixes Appendix B, Appendix C, and Appendix D so you're familiar with their content.
The Message
is the most important part of the
entire JMS specification. All data and events in a JMS application
are communicated with messages, while the rest of JMS exists to
facilitate the transfer of messages. They are the lifeblood of the
system.
A JMS message both carries application data and provides event notification. Its role is unique to distributed computing. In RPC-based systems (CORBA, Java RMI, DCOM), a message is a command to execute a method or procedure, which blocks the sender until a reply has been received. A JMS message is not a command; it transfers data and tells the receiver that something has happened. A message doesn't dictate what the recipient should do and the sender doesn't wait for a response. This decouples the sender from the receiver, making messaging systems and their messages far more dynamic and flexible than request-reply paradigms.
A Message
object has two parts: the message data
itself, called the payload or message body, and the message headers
and properties (see Figure 3.1).
Messages come in various types that are defined by the payload they
carry. The payload itself might be very structured, as with
StreamMessage
and MapMessage
objects, or fairly unstructured, as with
TextMessage
, ObjectMessage
, and
BytesMessage
types. Messages can carry important
data or simply serve as notifications of events in the system. In
most cases, messages are both notifications and vehicles for carrying
data.
The
message
headers provide metadata about the message describing who or what
created the message, when it was created, how long the data is valid,
etc. The headers also contain routing information that describes the
destination of the message (topic or queue), how a message should be
acknowledged, and a lot more. In addition to headers, messages can
carry properties that can be defined and set by the JMS client. JMS
consumers can choose to receive messages based on the values of
certain headers and properties, using a special filtering mechanism
called message
selectors
.
Every JMS message has a set of standard
headers.
Each header is identified by a set of
accessor and mutator methods that follow
the idiom setJMS<HEADER>(
)
,
getJMS<HEADER>( )
. Here is a partial definition of the
Message
interface that shows all the
JMS
header methods:
public interface Message { public DestinationgetJMSDestination( )
throws JMSException; public voidsetJMSDestination(Destination destination)
throws JMSException; public intgetJMSDeliveryMode( )
throws JMSException; public voidsetJMSDeliveryMode(int deliveryMode)
throws JMSException; public StringgetJMSMessageID( )
throws JMSException; public voidsetJMSMessageID(String id)
throws JMSException; public longgetJMSTimestamp( )
throws JMSException; public voidsetJMSTimestamp(long timestamp)
throws JMSException; public longgetJMSExpiration( )
throws JMSException; public voidsetJMSExpiration(long expiration)
throws JMSException; public booleangetJMSRedelivered( )
throws JMSException; public voidsetJMSRedelivered(boolean redelivered)
throws JMSException; public intgetJMSPriority( )
throws JMSException; public voidsetJMSPriority(int priority)
throws JMSException; public DestinationgetJMSReplyTo( )
throws JMSException; public voidsetJMSReplyTo(Destination replyTo)
throws JMSException; public StringgetJMSCorrelationID( )
throws JMSException; public voidsetJMSCorrelationID(String correlationID)
throws JMSException; public byte[]getJMSCorrelationIDAsBytes( )
throws JMSException; public voidsetJMSCorrelationIDAsBytes(byte[] correlationID)
throws JMSException; public StringgetJMSType( )
throws JMSException; public voidsetJMSType(String type)
throws JMSException; }
JMS headers are divided into two large groups: automatically assigned headers and developer-assigned headers. The next two sections discuss these two types.
Most JMS headers are automatically
assigned; their value is set by the JMS provider when the message is
delivered, so that values assigned by the developer using the
setJMS<HEADER>( )
methods are ignored. In
other words, for headers that are automatically assigned, using the
mutator methods explicitly is fruitless.[1] This
doesn't mean, however, that the developer has no control over
the value of these headers. Some automatically assigned headers
depend on declarations made by the developer when creating the
Session
and MessageProducer
(i.e., TopicPublisher
). These cases are clearly
illustrated in the header definitions that follow.
The
JMSDestination
header identifies the destination with
either a Topic
or Queue
object,
both of which are Destination
types. Identifying
the message's destination is valuable to JMS clients that
consume messages from more than one topic or queue:
Topic destination = (Topic) message.getJMSDestination( );
There
are two types of delivery modes in JMS:
persistent and
nonpersistent. A persistent message should
be delivered
once-and-only-once
, which means that if the JMS provider
fails, the message is not lost; it will be delivered after the server
recovers. A nonpersistent message is delivered
at-most-once
, which means that it can be lost
permanently if the JMS provider fails. In both persistent and
nonpersistent delivery modes the message server should not send a
message to the same consumer more then once, but it is possible (see
the section on JMSRedelivered
for more details):
int deliverymode = message.getJMSDeliveryMode( ); if (deliverymode = javax.jms.DeliveryMode.PERSISTENT) { ... } else { // equals DeliveryMode.NON_PERSISTENT ... }
The delivery mode can be set using the setJMSDeliveryMode(
)
method on the producer
(i.e., TopicPublisher
). Once the delivery mode is
set on the MessageProducer
, it is applied to all
messages delivered using that producer. The
default setting is PERSISTENT
:
// Set the JMS delivery mode on the message producer TopicPublisher topicPublisher = topicSession.createPublisher(topic); topicPublisher.setDeliveryMode(DeliveryMode.NON_PERSISTENT);
The JMSMessageID
is a String
value
that uniquely identifies a message. How unique the identifier is
depends on the vendor. The JMSMessageID
can be
useful for historical repositories in JMS consumer applications where
messages need to be uniquely indexed. Used in conjunction with the
JMSCorrelationID
, the
JMSMessageID
is also useful for correlating
messages:
String messageid = message.getJMSMessageID( );
The JMSTimestamp
is set automatically by the message
producer when the send( )
operation is invoked. The timestamp is a long
value that measures time in milliseconds:
long timestamp = message.getJMSTimestamp( );
A
Message
object's
expiration date prevents the
message from being delivered to consumers after it has expired. This
is useful for messages whose data is only valid for a period of time:
long timeToLive = message.getJMSExpiration( );
The expiration time for messages is set in milliseconds on the
producer (i.e., TopicPublisher
) using the
setTimeToLive( )
method:
TopicPublisher topicPublisher = topicSession.createPublisher(topic);
// Set time to live as 1 hour (1000 millis x 60 sec x 60 min)
topicPublisher.setTimeToLive(3600000);
By default the timeToLive
is zero (0), which
indicates that the message doesn't expire. Calling
setTimeToLive( )
with a zero argument ensures that
a message is created without an expiration date.
The
JMSRedelivered
header indicates that the message was
redelivered to the consumer. The JMSRedelivered
header is true
if the message is redelivered, and
false
if it's not. A message may be marked
redelivered if a consumer failed to acknowledge previous delivery of
the message, or when the JMS provider is not certain whether the
consumer has already received the message:
boolean isRedelivered = message.getJMSRedelivered( )
Message redelivery is covered in more detail in Chapter 6.
The message producer may assign a priority to a message when it is delivered. There are two categories of message priorities: levels 0-4 are gradations of normal priority; levels 5-9 are gradations of expedited priority. The message servers may use a message's priority to prioritize delivery of messages to consumers—messages with an expedited priority are delivered ahead of normal priority messages:
int priority = message.getJMSPriority( );
The priority of messages can be declared by the JMS client using the
setPriority( )
method on the producer:
TopicPublisher topicPublisher = TopicSession.createPublisher(someTopic);
topicPublisher.setPriority(9);
While many of the JMS headers are set
automatically when the message is delivered, several others must be
set explicitly on the Message
object before it is
delivered by the producer.
In some cases, a JMS message producer may want the consumers to reply
to a message. The
JMSReplyTo
header, which contains a
javax.jms.Destination
, indicates which address a
JMS consumer should reply to. A JMS consumer is not required to send
a reply:
message.setJMSReplyTo(topic); ... Topic topic = (Topic) message.getJMSReplyTo( );
The
JMSCorrelationID
provides a header for associating the
current message with some previous message or application-specific
ID. In most cases the JMSCorrelationID
will be
used to tag a message as a reply to a previous message identified by
a JMSMessageID
, but the
JMSCorrelationID
can be any value, not just a
JMSMessageID
:
message.setJMSCorrelationID(identifier); ... String correlationid = message.getJMSCorrelationID( );
JMSType
is an optional header that is set by the
JMS client. Its main purpose is to identify the message structure and
type of payload; it is currently supported by only a few vendors.
Some MOM systems (IBM's MQSeries, for example) treat the
message body as uninterpreted bytes. These systems often provide a
message type as a simple way for applications to label the message
body. So a message type can be useful when exchanging messages with
non-JMS clients that require this type of information to process the
payload.
Other MOM systems (e.g., Sun's JMQ) and EAI systems (e.g., SagaVista and MQIntegrator) directly tie each message to some form of external message schema, with the message type as the link. These MOM systems require a message type because they provide metadata services bound to it.
[1] According to
the specification authors, the setJMS<HEADER>(
)
methods were left in the Message
interface for "general orthogonality," or to keep it
semantically symmetrical to balance the
getJMS<HEADER>( )
methods—a fairly
strange but established justification.