The JMS API allows applications to create, send, receive, and read messages. Messages can arrive asynchronously, meaning the client does not have to specifically request messages in order to receive them. In addition, the programmer can specify different levels of reliability, depending on the type of message transmitted. Unlike traditional low-level network remote procedure call mechanisms, JMS is loosely coupled, meaning that the sending and receiving applications do not both have to be available at the same time to enable communication (Sun Microsystems, 2002). In other words, the sender and receiver need not know anything about each other when communicating. This is particularly useful in the wireless domain because of the "sometimes on" characteristics of wireless devices.
The following components are present in a JMS implementation:
JMS provider. This component provides administrative and control features. It is the system that implements JMS interfaces. The J2EE 1.3 platform includes a JMS Provider (Sun Microsystems, 2002).
JMS clients. These are components or programs, written in Java, that are the producers and consumers of messages. In the wireless domain, clients may include cellular phones and PDAs.
Messages. These are the objects transmitted between JMS clients.
Administered objects. These are configured by the administrator and include destinations and connection factories. Together, the administered objects form the Java Naming and Directory Interface (JNDI) API namespace. For one client to establish a logical connection with another through the JMS provider, it needs to perform a lookup of administered objects, using a standard directory service.
The JMS programming model can be summarized in Figure 4.
Figure 4: The JMS Programming Model
A Connection Factory is an administered object that a client uses to create a connection with the JMS Provider. When the JMS program first loads up, the client performs a JNDI lookup of a particular connection factory. A connection factory is either an instance of a QueueConnectionFactory (for point-to-point) or TopicConnectionFactory (for publish/subscribe) interface. Once a connection factory is created, the client specifies the destination to which messages are sent or from which they are received.
A Destination is either a queue or a topic.
A Connection is formed by setting up a virtual connection with a JMS provider. This normally consists of an open TCP/IP socket between the client and the provider. On a wireless link, where bandwidth becomes an issue, normal TCP/IP can prove excessively bulky.
A Session is the context for creating message producer objects, message consumer objects, and messages. Arguments used when creating a session determine whether the session is transacted (considered as an atomic unit of work) as well as the message acknowledgment mechanism.
A Message Producer sends messages to a destination.
A Message Consumer receives messages from a destination. The message consumer allows a client to specify interest in receiving messages from a particular destination. The JMS provider is then responsible for delivering messages to the registered consumers. The receive method enables messages to be received synchronously. To receive messages asynchronously, a message listener needs to be registered. The message listener's onMessage method, which defines the actions taken on a message, is called whenever a message is delivered. Sometimes, a message consumer may only be interested in a subset of messages for which it is registered to receive. In this case, a message selector is registered with the JMS provider. A message selector is simply a string containing an expression and is quite similar in appearance and functionality to an SQL query. Messages consist of a header, properties (optional), and a body (optional). Message properties may be predefined or user-defined and can be used, for example, to provide compatibility with existing messaging systems or for setting message selector values. The message body supports numerous data types, such as text files, object messages, and stream messages.
JMS tackles several of the issues inherent in message-oriented middleware in the wireless domain. These include the following:
Message persistence. What happens if the JMS provider fails or is unavailable during message transfer?
Message expiration. How does one ensure that unconsumed messages do not cause performance degradation on the system, for example, by filling a particular queue to capacity?
Durability. Especially in the publish/subscribe domain, what happens to messages published while a subscriber is inactive?
JMS provides two delivery modes for messages. The NON_PERSISTENT mode ensures the lowest overhead but means that messages are not logged to stable storage and are, consequently, lost when a connection to a JMS provider goes down (JMS Specification, 2002). The PERSISTENT mode, on the other hand, is vital for lossy wireless links, where stable connections are unlikely. Messages delivered using the PERSISTENT mode are stored, so that if the link goes down, the JMS provider can reattempt the delivery later. The decision to choose PERSISTENT or NONPERSISTENT delivery is essentially a tradeoff between performance and reliability. Clearly, the PERSISTENT mode causes performance degradation due to the overhead involved in storing messages, but it guarantees delivery of messages. The reverse is true for the NONPERSISTENT mode (JMS Specification, 2002).
Each JMS message has an expiration field in its header. A time-to-live value in milliseconds can be specified in this field. The message expiration time is equal to the sum of the current GMT and the time-to-live value. Messages that are not delivered before the expiration time are deleted in order to conserve storage and computing resources (Sun Microsystems, 2002).
The createDurableSubscriber method of the TopicSession class allows a client to have messages retained by the JMS provider while the client is inactive. The JMS provider stores the messages published to a topic in a persistent queue. When the client reactivates, it is able to retrieve messages published while it was inactive (Sun Microsystems, 2002).
For a message to be considered as having been consumed successfully, it has to be acknowledged by the receiving party. However, for performance reasons, it may be inconvenient to acknowledge every message that is received. JMS provides different levels of acknowledgment. When the AUTO_ACKNOWLEDGE value of a particular session is set, all messages belonging to the session are acknowledged. The CLIENT_ACKNOWLEDGE value specifies that messages will be acknowledged on the session level. This means that acknowledging receipt of a message during a session automatically acknowledges receipt of all other messages during that session. The DUPS_OK_ACKNOWLEDGE value allows lazy acknowledgment of messages and is likely to result in duplicate messages being delivered. This value should be set when the client wishes to reduce session overhead but can tolerate duplicate messages arriving (Sun Microsystems, 2002).
Message priorities can be set in order to instruct the JMS provider to deliver urgent messages first. Message priorities are specified on a scale from 0 (lowest) to 9 (highest).
JMS sessions can be transactional. The first argument to the createSession method specifies whether the session is transacted or not. With transacted sessions, if one operation fails, all changes made during that session are rolled back to a previous committed state. Thus, the operations in the session are seen as an autonomic unit of work (Sun Microsystems, 2002).