Section 11.2. Elements of Messaging with JMS


11.2. Elements of Messaging with JMS

The principal players in a JMS system are messaging clients, message destinations, and a JMS-compatible messaging provider .

Messaging clients produce and consume messages. Typically, messaging takes place asynchronously; a client produces a message and sends it to a message destination, and some time later another client receives the message. Message clients can be implemented using JMS, or they can use a native messaging API to participate in the messaging system. If a native message client (e.g., a client using the native IBM MQ Series APIs) produces a message to a message destination, a JMS connection to the native message system is responsible for retrieving the message, converting it into the appropriate JMS message representation, and delivering it to any relevant JMS-based clients.

Message destinations are places to and from which JMS clients send and receive messages. Message destinations are created within a JMS provider that manages all of the administrative and runtime functions of the messaging system. At a minimum, a JMS provider allows you to specify a network address for a destination, allowing clients to find the destination on the network. But providers may also support other administrative options on destinations, such as persistence options, resource limits, and the like.

11.2.1. Messaging Styles: Point-to-Point and Publish-Subscribe

Generally speaking, asynchronous messaging usually comes in two flavors: a message can be addressed and sent to a single receiver (point-to-point), or a message can be published to a particular channel or topic and any receiver that subscribes to that channel will receive the message (publish-subscribe). These two messaging styles have analogies at several levels in the distributed computing "stack," all the way from the network level (standard TCP packet delivery versus multicast networking) to the application level (email versus newsgroups).Figure 11-1 depicts the two message models supported by JMS, as well as the key interfaces that come into play in a JMS context. We discuss the specifics of these interfaces later in the chapter.

Most messaging providers support one or both of these messaging styles, so JMS provides support for them both in its API. JMS includes a set of generic messaging interfaces, described next. Each style of messaging is supported by specialized subclasses of these generic interfaces.

Figure 11-1. JMS message models


11.2.2. Key JMS Interfaces

The following key interfaces represent the concepts that come into play in any JMS client application, whether it is using point-to-point or publish-subscribe messaging:


Message

Messages are at the heart of JMS, naturally. Messages have accessor methods for their header fields, properties, and body contents. Subtypes of this interface provide implementations for different types of content.


MessageListener

A MessageListener is attached to a MessageConsumer by a client and receives a callback for each Message received by that consumer. MessageListeners are the key to asynchronous message delivery to clients, since the client attaches a listener to a consumer and then carries on with its thread(s) of control. MessageListeners must implement an onMessage( ) method, which is the callback used to notify the listener that a message has arrived.


ConnectionFactory

A ConnectionFactory creates connections to a JMS provider. ConnectionFactory references are obtained from a JMS provider through a JNDI lookup. A QueueConnectionFactory creates connections in a point-to-point context; TopicConnectionFactory creates connections in publish-subscribe contexts. The createConnection() method (added in JMS 1.1) creates a Connection appropriate for the type of messaging supported by the factory, either a QueueConnection or a TopicConnection. You can also cast a ConnectionFactory to its specific subtype (QueueConnectionFactory or TopicConnectionFactory) and use the createQueueConnection() or createTopicConnection() methods (respectively).


Destination

A Destination represents a network location, managed by a JMS provider, that can be used to exchange messages. A JMS client sends messages to Destinations and attaches MessageListeners to Destinations to receive messages from other clients. A client obtains references to Destinations using JNDI lookups. Queues and Topics are the Destinations in point-to-point and publish-subscribe contexts, respectively.


Connection

A Connection is a live connection to a JMS provider used for the receipt and delivery of messages. Before a client can exchange messages with a JMS destination, it must have a live connection that was started by the client. A Connection is obtained from a ConnectionFactory using its createXXXConnection( ) methods. A QueueConnectionFactory.createQueueConnection( ) returns a QueueConnection; a TopicConnectionFactory.createTopicConnection( ) returns a TopicConnection. The createConnection() method on ConnectionFactory (added in JMS 1.1) returns a Connection, which can be cast to its subtype if needed.


Session

A Session can be thought of as a single, serialized flow of messages between a client and a JMS provider. A Session is used to create message consumers and producers and to create Messages that a client wishes to send. A Session is used within a single thread of control on a client. Since a Session is accessed only from within a single thread, the messages sent or received through its consumers and producers are serialized with respect to the client. Sessions also provide a context for defining transactions around message operations; further details can be found in "Transactional Messaging" later in this chapter. Sessions are created from Connections using their createXXXSession( ) methods. The QueueConnection.createQueueSession( ) method returns a QueueSession, and the TopicConnection.createTopicSession( ) method returns a TopicSession. The Connection.createSession() method (added in JMS 1.1) returns a Session, which can be cast to its subtype if needed.


MessageProducer/MessageConsumer

MessageProducers and MessageConsumers are used to send and receive messages to and from a destination, respectively. Producers and consumers are created using various createXXX( ) methods on Sessions, using the target Destination as the argument. In a point-to-point context, QueueSenders are created using the QueueSession.createSender( ) method, and QueueReceivers are created using the QueueSession.createReceiver( ) methods. In a publish-subscribe context, TopicPublishers are created using TopicSession.createPublisher( ), and TopicSubscribers are created using TopicSession.createSubscriber( ) and TopicSession.createDurableSubscriber( ) methods. You can also use the Session.createProducer() and Session.createConsumer() methods (added in JMS 1.1) to create MessageProducers and MessageConsumers that can be cast to their subtypes.

11.2.3. A Generic JMS Client

A JMS client follows the same general sequence of operations, regardless of whether it's using point-to-point or publish-subscribe messaging, or both. We'll walk through these steps here, using the point-to-point JMS interfaces to demonstrate. For the most part, the same pseudocode can be used with the publish-subscribe interfaces by just substituting Topic for Queue in the code samples in this section.

11.2.3.1. General setup

The very first step for a JMS client is to get a reference to an InitialContext for the JNDI service of the JMS provider. Full details on the various options for obtaining a JNDI Context can be found in Chapter 9, but in general, the client will create an InitialContext using a set of Properties that specify the location and type of the JNDI service associated with the JMS provider:

     Properties props = ...;     Context ctx = new InitialContext(props); 

Next, the JMS client needs to acquire a ConnectionFactory from the JMS provider using a JNDI lookup. The client would have to know what name the JMS provider used to publish the ConnectionFactory in JNDI space. Here, we look up a QueueConnectionFactory registered in JNDI under the name jms/someQFactory:

     QueueConnectionFactory qFactory =       (QueueConnectionFactory)ctx.lookup("jms/someQFactory"); 

An administrator would have to set up this ConnectionFactory on the JMS provider and associate it with this JNDI name on the server.

The client also uses JNDI to find Destinations published by the JMS provider. Here, we look up a Queue published under the JNDI name jms/someQ:

     Queue queue = (Queue)ctx.lookup("jms/someQ"); 

Once we have a ConnectionFactory and one or more Destinations to talk to, we need to create a Connection with the JMS provider. This Connection is the conduit through which messages will be physically sent and received. A Connection has to be started before messages can be received through it, but a Connection can always be used to send messages, regardless of whether it's started or stopped. Normally, a client won't start( ) a Connection until it's ready to receive and process messages. Here, we use our QueueConnectionFactory to create a QueueConnection and defer starting it until we create a MessageConsumer to receive messages:

     QueueConnection qConn = qFactory.createQueueConnection(...);

11.2.3.2. Client identifiers

When a client makes a connection to a JMS provider, a client identifier is associated with the client. The client identifier is used to maintain state on the JMS provider on behalf of the client, and the state data can persist beyond the lifetime of a client connection. The server-side state can be retrieved for the client when it reconnects using the same client ID. The only client state information defined by the JMS specification is durable topic subscriptions (described in "Durable Subscriptions" later in this chapter), but a JMS provider may support its own state information on behalf of clients as well. Only one client is allowed to be associated with a client ID (and its state information) on the JMS provider, so only a single connection with a given client ID can be made to a JMS provider at any given time.

The JMS client identifier can be set in two ways. A client can set a client ID on any Connections that it makes with the JMS provider, using the Connection.setClientID( ) method:

     qConn.setClientID("client-1"); 

Again, only a single connection with a given client ID is allowed at any given time. If a client with this same client ID (even this one) already has a connection with the client ID, then an InvalidClientIDException will be thrown when setClientID( ) is called. Alternatively, a ConnectionFactory can be configured on the JMS provider with a client ID that is applied to any Connections that are created through it. The ConnectionFactory interface doesn't provide a facility for the client to set the factory's client ID; this is a function that would have to be provided in the JMS provider's administrative interface. A ConnectionFactory with a preset client ID is, by definition, intended to be used by a single client.

11.2.3.3. Authenticated connections

When a client creates a connection, it has the option to provide a username and password that will be authenticated by the JMS provider. This is done using overloaded versions of the createXXXConnection( ) methods on a ConnectionFactory. We can create an authenticated QueueConnection, for example, with a call like this:

     QueueConnection authQConn =       qFactory.createQueueSession("JimFarley", "myJMSPassword"); 

If this is successful, the client will operate under the given principal name and be given the appropriate rights. JMS providers aren't required to support authentication of connections. If a JMS provider does support authenticated connections, the principals and access rights will be administered on the JMS server.

11.2.3.4. Sessions

Once a connection to the JMS provider is established, we need to create one or more Sessions to be used to send and receive messages. Again, Sessions are a single-threaded context for handling messages, so we need a separate Session for each concurrent thread that we plan to use for messaging. Sessions are created from Connections. Here, we create a QueueSession from our QueueConnection:

     QueueSession qSess =       qConn.createQueueSession(false, Session.AUTO_ACKNOWLEDGE); 

When creating either QueueSessions or TopicSessions, two arguments are used to create the Session. The first is a boolean flag indicating whether we want the Session to be transacted. (See "Transactional Messaging" later in this chapter for details on transactional sessions .) The second argument indicates how we want the Session to acknowledge received messages with the JMS provider. The three options for the acknowledge mode of a Session are specified using static final values on the Session class:


Session.AUTO_ACKNOWLEDGE

This instructs the Session to acknowledge messages automatically for the client. A message is acknowledged when received by the client. If a MessageListener handles the message, then the acknowledgment is not sent until the listener's onMessage( ) method returns. If the message is received because of a call to receive( ) on a MessageConsumer, then the acknowledgment is sent immediately after the call to receive( ) returns.


Session.DUPS_OK_ACKNOWLEDGE

This option instructs the Session to do "lazy acknowledgment": acknowledgments can be delayed if the Session decides to do so. This could lead to a message being delivered to a client more than once, if the delay between delivery and acknowledgment is longer than the JMS provider's timeout and it assumed the message was never received.


Session.CLIENT_ACKNOWLEDGE

This option is used when the client wants to manually acknowledge messages, by calling the acknowledge( ) method on the Message.

11.2.3.5. Sending messages

Messages are sent to Destinations using MessageProducers, which are created from Sessions. When they are created, producers are associated with a Destination, and any Messages sent using the producer are delivered to that Destination using the Connection from which the Session was generated.

In a point-to-point context, the message producers are QueueSenders, generated from QueueSessions using the Queue the sender should point to:

     QueueSender qSender = qSess.createSender(queue); 

Once a producer has been created, the client needs to create and initialize Messages to be sent. Messages are also created from a Session. Here, we create a TextMessage from our QueueSession and set its text body to be some interesting text we want to send to the Queue:

     TextMessage tMsg = qSess.createTextMessage( );     tMsg.setText("The sky is blue."); 

To actually send the message, we simply invoke the appropriate method on our MessageProducer. Here, we call send( ) on our QueueSender:

     qSender.send(tMsg); 

Note that it's not necessary to ensure that the underlying Connection (from which we generated our Session) is started in order to send messages. Starting the Connection is required only to commence delivery of messages from the Destination to the client.

11.2.3.6. Receiving messages

Receiving messages involves creating a MessageConsumer that is associated with a particular Destination. This establishes a consumer with the JMS provider, and the provider is responsible for delivering any appropriate messages that arrive at the Destination to the new consumer. MessageConsumers are also generated from Sessions, in order to associate them with a serialized flow of messages. In a point-to-point context, a QueueReceiver is generated from a QueueSession using its createReceiver( ) methods. Here, we simply create a new receiver tied to our Queue. Other options for creating QueueReceivers are discussed in "Point-to-Point Messaging" later in this chapter.

     QueueReceiver qReceiver = qSess.createReceiver(queue); 

By creating a MessageConsumer, all we've done is tell the JMS provider that we want to receive messages from a particular Destination. We haven't specified what to do with the Messages on the client side. Since JMS is an asynchronous message delivery system, it uses the same listener pattern that is used in Swing GUI programming or JavaBeans event handling (two other asynchronous event contexts). Messages in JMS are processed using MessageListeners. A client needs to implement a MessageListener with an onMessage( ) method that does something useful with the Messages coming from the Destination. Example 11-1 shows a basic MessageListenera TextLogger that simply prints the contents of any TextMessages it encounters.

Example 11-1. Simple MessageListener implementation
 import javax.jms.*; public class TextLogger implements MessageListener {   // Default constructor   public TextLogger( ) {}   // Message handler   public void onMessage(Message msg) {     // If it's a text message, print it to stdout     if (msg instanceof TextMessage) {       TextMessage tMsg = (TextMessage)msg;       try {         System.out.println("Received message: " + tMsg.getText( ));       }       catch (JMSException je) {         System.out.println("Error retrieving message text: " +                            je.getMessage( ));       }     }     // For other types of messages, print an error     else {       System.out.println("Unsupported message type encountered.");     }   } } 

Once a MessageListener has been defined, the client needs to create one and register it with a MessageConsumer. In our running example, we create one of our TextLoggers and associate it with our QueueReceiver using its setMessageListener( ) method:

     MessageListener listener = new TextLogger( );     qReceiver.setMessageListener(listener); 

It's important to remember that no messages will be delivered over our underlying Connection until it's been started. In our running example, we created our QueueConnection but never started it, so we do that now to start delivery of Messages to our QueueReceiver, and from there to our TextLogger listener:

     qConn.start( );

11.2.3.7. Temporary destinations

A client can create its own temporary destinations, which are Destinations that are visible only to the Connections that created them, and that live only for the duration of the Connection used to create them. Although a temporary destination lives only for the life of the Connection it was created from, it is created using methods on the Session. For example, to create a TemporaryQueue:

     Queue tempQueue = qSession.createTemporaryQueue( ); 

Temporary destinations can be used, for example, to receive responses to messages that are sent with a JMSReplyTo header:

     TextMessage request = qSession.createTextMessage( );     request.setJMSReplyTo(tempQueue); 

They can also be used to exchanging asynchronous messages between threads in the same client.

11.2.3.8. Cleaning up

Connections and Sessions require resources to be allocated by the JMS provider (similar to how JDBC connections use up resources on an RDBMS), so it's a good idea to free them up explicitly when you are done with them.

Sessions are closed by simply calling close( ) on them:

     qSess.close( ); 

When a Session is closed, all MessageConsumers and MessageProducers associated with it are rendered unusable. If you try to use them to communicate with the JMS provider, they will throw an IllegalStateException. A call to Session.close( ) will block until any pending processing of incoming Messages (e.g., a MessageListener's onMessage( ) method) is complete.

Closing a Session doesn't close the underlying Connection from which it came. You can close one Session and open up another one as long as the Connection is active. To close a Connection and free up its server-side resources, call its close( ) method:

     qConn.close( ); 

All Sessions (and, subsequently, all of their consumers and producers) generated from a Connection become unusable once it is closed. The call to Connection.close( ) will block until incoming Message processing has completed on all of the Sessions associated with it.



Java Enterprise in a Nutshell
Java Enterprise in a Nutshell (In a Nutshell (OReilly))
ISBN: 0596101422
EAN: 2147483647
Year: 2004
Pages: 269

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