The Java EE 5 Tutorial

Chapter 31 The Java Message Service API

This chapter provides an introduction to the Java Message Service (JMS) API, a Java API that allows applications to create, send, receive, and read messages using reliable, asynchronous, loosely coupled communication. It covers the following topics:

Overview of the JMS API

This overview of the JMS API answers the following questions.

What Is Messaging?

Messaging is a method of communication between software components or applications. A messaging system is a peer-to-peer facility: A messaging client can send messages to, and receive messages from, any other client. Each client connects to a messaging agent that provides facilities for creating, sending, receiving, and reading messages.

Messaging enables distributed communication that is loosely coupled. A component sends a message to a destination, and the recipient can retrieve the message from the destination. However, the sender and the receiver do not have to be available at the same time in order to communicate. In fact, the sender does not need to know anything about the receiver; nor does the receiver need to know anything about the sender. The sender and the receiver need to know only which message format and which destination to use. In this respect, messaging differs from tightly coupled technologies, such as Remote Method Invocation (RMI), which require an application to know a remote application’s methods.

Messaging also differs from electronic mail (email), which is a method of communication between people or between software applications and people. Messaging is used for communication between software applications or software components.

What Is the JMS API?

The Java Message Service is a Java API that allows applications to create, send, receive, and read messages. Designed by Sun and several partner companies, the JMS API defines a common set of interfaces and associated semantics that allow programs written in the Java programming language to communicate with other messaging implementations.

The JMS API minimizes the set of concepts a programmer must learn in order to use messaging products but provides enough features to support sophisticated messaging applications. It also strives to maximize the portability of JMS applications across JMS providers in the same messaging domain.

The JMS API enables communication that is not only loosely coupled but also

The JMS specification was first published in August 1998. The latest version is Version 1.1, which was released in April 2002. You can download a copy of the specification from the JMS web site: http://java.sun.com/products/jms/.

When Can You Use the JMS API?

An enterprise application provider is likely to choose a messaging API over a tightly coupled API, such as remote procedure call (RPC), under the following circumstances.

For example, components of an enterprise application for an automobile manufacturer can use the JMS API in situations like these:

Using messaging for these tasks allows the various components to interact with one another efficiently, without tying up network or other resources. Figure 31–1 illustrates how this simple example might work.

Figure 31–1 Messaging in an Enterprise Application

Diagram showing messaging between various departments
in an enterprise

Manufacturing is only one example of how an enterprise can use the JMS API. Retail applications, financial services applications, health services applications, and many others can make use of messaging.

How Does the JMS API Work with the Java EE Platform?

When the JMS API was introduced in 1998, its most important purpose was to allow Java applications to access existing messaging-oriented middleware (MOM) systems, such as MQSeries from IBM. Since that time, many vendors have adopted and implemented the JMS API, so a JMS product can now provide a complete messaging capability for an enterprise.

Beginning with the 1.3 release of the Java EE platform, the JMS API has been an integral part of the platform, and application developers can use messaging with Java EE components.

The JMS API in the Java EE platform has the following features.

The JMS API enhances the Java EE platform by simplifying enterprise development, allowing loosely coupled, reliable, asynchronous interactions among Java EE components and legacy systems capable of messaging. A developer can easily add new behavior to a Java EE application that has existing business events by adding a new message-driven bean to operate on specific business events. The Java EE platform, moreover, enhances the JMS API by providing support for distributed transactions and allowing for the concurrent consumption of messages. For more information, see the Enterprise JavaBeans specification, v3.0.

The JMS provider can be integrated with the application server using the Java EE Connector architecture. You access the JMS provider through a resource adapter. This capability allows vendors to create JMS providers that can be plugged in to multiple application servers, and it allows application servers to support multiple JMS providers. For more information, see the Java EE Connector architecture specification, v1.5.

Basic JMS API Concepts

This section introduces the most basic JMS API concepts, the ones you must know to get started writing simple JMS client applications:

The next section introduces the JMS API programming model. Later sections cover more advanced concepts, including the ones you need to write Java EE applications that use message-driven beans.

JMS API Architecture

A JMS application is composed of the following parts.

Figure 31–2 illustrates the way these parts interact. Administrative tools allow you to bind destinations and connection factories into a JNDI namespace. A JMS client can then use resource injection to access the administered objects in the namespace and then establish a logical connection to the same objects through the JMS provider.

Figure 31–2 JMS API Architecture

Diagram of JMS API architecture, showing administrative
tool, JMS client, JNDI namespace, and JMS provider

Messaging Domains

Before the JMS API existed, most messaging products supported either the point-to-point or the publish/subscribe approach to messaging. The JMS specification provides a separate domain for each approach and defines compliance for each domain. A stand-alone JMS provider can implement one or both domains. A Java EE provider must implement both domains.

In fact, most implementations of the JMS API support both the point-to-point and the publish/subscribe domains, and some JMS clients combine the use of both domains in a single application. In this way, the JMS API has extended the power and flexibility of messaging products.

The JMS 1.1 specification goes one step further: It provides common interfaces that enable you to use the JMS API in a way that is not specific to either domain. The following subsections describe the two messaging domains and then describe the use of the common interfaces.

Point-to-Point Messaging Domain

A point-to-point (PTP) product or application is built on the concept of message queues, senders, and receivers. Each message is addressed to a specific queue, and receiving clients extract messages from the queues established to hold their messages. Queues retain all messages sent to them until the messages are consumed or until the messages expire.

PTP messaging has the following characteristics and is illustrated in Figure 31–3.

Figure 31–3 Point-to-Point Messaging

Diagram of point-to-point messaging, showing Client 1
sending a message to a queue, and Client 2 consuming and acknowledging the
message

Use PTP messaging when every message you send must be processed successfully by one consumer.

Publish/Subscribe Messaging Domain

In a publish/subscribe (pub/sub) product or application, clients address messages to a topic, which functions somewhat like a bulletin board. Publishers and subscribers are generally anonymous and can dynamically publish or subscribe to the content hierarchy. The system takes care of distributing the messages arriving from a topic’s multiple publishers to its multiple subscribers. Topics retain messages only as long as it takes to distribute them to current subscribers.

Pub/sub messaging has the following characteristics.

The JMS API relaxes this timing dependency to some extent by allowing subscribers to create durable subscriptions, which receive messages sent while the subscribers are not active. Durable subscriptions provide the flexibility and reliability of queues but still allow clients to send messages to many recipients. For more information about durable subscriptions, see Creating Durable Subscriptions.

Use pub/sub messaging when each message can be processed by zero, one, or many consumers. Figure 31–4 illustrates pub/sub messaging.

Figure 31–4 Publish/Subscribe Messaging

Diagram of pub/sub messaging, showing Client 1 publishing
a message to a topic, and the message being delivered to two subscribers to
the topic

Programming with the Common Interfaces

Version 1.1 of the JMS API allows you to use the same code to send and receive messages under either the PTP or the pub/sub domain. The destinations that you use remain domain-specific, and the behavior of the application will depend in part on whether you are using a queue or a topic. However, the code itself can be common to both domains, making your applications flexible and reusable. This tutorial describes and illustrates these common interfaces.

Message Consumption

Messaging products are inherently asynchronous: There is no fundamental timing dependency between the production and the consumption of a message. However, the JMS specification uses this term in a more precise sense. Messages can be consumed in either of two ways:

The JMS API Programming Model

The basic building blocks of a JMS application consist of

Figure 31–5 shows how all these objects fit together in a JMS client application.

Figure 31–5 The JMS API Programming Model

Diagram of the JMS API programming model: connection
factory, connection, session, message producer, message consumer, messages,
and destinations

This section describes all these objects briefly and provides sample commands and code snippets that show how to create and use the objects. The last subsection briefly describes JMS API exception handling.

Examples that show how to combine all these objects in applications appear in later sections. For more details, see the JMS API documentation, which is part of the Java EE API documentation.

JMS Administered Objects

Two parts of a JMS application, destinations and connection factories, are best maintained administratively rather than programmatically. The technology underlying these objects is likely to be very different from one implementation of the JMS API to another. Therefore, the management of these objects belongs with other administrative tasks that vary from provider to provider.

JMS clients access these objects through interfaces that are portable, so a client application can run with little or no change on more than one implementation of the JMS API. Ordinarily, an administrator configures administered objects in a JNDI namespace, and JMS clients then access them by using resource injection.

With Sun Java System Application Server Platform Edition 9, you use the asadmin command or the Admin Console to create JMS administered objects in the form of resources.

JMS Connection Factories

A connection factory is the object a client uses to create a connection to a provider. A connection factory encapsulates a set of connection configuration parameters that has been defined by an administrator. Each connection factory is an instance of the ConnectionFactory, QueueConnectionFactory, or TopicConnectionFactory interface. To learn how to create connection factories, see Creating JMS Administered Objects for the Synchronous Receive Example.

At the beginning of a JMS client program, you usually inject a connection factory resource into a ConnectionFactory object. For example, the following code fragment specifies a resource whose JNDI name is jms/ConnectionFactory and assigns it to a ConnectionFactory object:

@Resource(mappedName="jms/ConnectionFactory")
private static ConnectionFactory connectionFactory;

In a Java EE application, JMS administered objects are normally placed in the jms naming subcontext.


Note –

The mappedName element of the @Resource annotation is specific to the Application Server.


JMS Destinations

A destination is the object a client uses to specify the target of messages it produces and the source of messages it consumes. In the PTP messaging domain, destinations are called queues. In the pub/sub messaging domain, destinations are called topics. A JMS application can use multiple queues or topics (or both). To learn how to create destination resources, see Creating JMS Administered Objects for the Synchronous Receive Example.

To create a destination using the Application Server, you create a JMS destination resource that specifies a JNDI name for the destination.

In the Application Server implementation of JMS, each destination resource refers to a physical destination. You can create a physical destination explicitly, but if you do not, the Application Server creates it when it is needed and deletes it when you delete the destination resource.

In addition to injecting a connection factory resource into a client program, you usually inject a destination resource. Unlike connection factories, destinations are specific to one domain or the other. To create an application that allows you to use the same code for both topics and queues, you assign the destination to a Destination object.

The following code specifies two resources, a queue and a topic. The resource names are mapped to destinations created in the JNDI namespace.

@Resource(mappedName="jms/Queue")
private static Queue queue;

@Resource(mappedName="jms/Topic")
private static Topic topic;

With the common interfaces, you can mix or match connection factories and destinations. That is, in addition to using the ConnectionFactory interface, you can inject a QueueConnectionFactory resource and use it with a Topic, and you can inject a TopicConnectionFactory resource and use it with a Queue. The behavior of the application will depend on the kind of destination you use and not on the kind of connection factory you use.

JMS Connections

A connection encapsulates a virtual connection with a JMS provider. A connection could represent an open TCP/IP socket between a client and a provider service daemon. You use a connection to create one or more sessions.

Connections implement the Connection interface. When you have a ConnectionFactory object, you can use it to create a Connection:

Connection connection = connectionFactory.createConnection();

Before an application completes, you must close any connections that you have created. Failure to close a connection can cause resources not to be released by the JMS provider. Closing a connection also closes its sessions and their message producers and message consumers.

connection.close();

Before your application can consume messages, you must call the connection’s start method; for details, see JMS Message Consumers. If you want to stop message delivery temporarily without closing the connection, you call the stop method.

JMS Sessions

A session is a single-threaded context for producing and consuming messages. You use sessions to create the following:

Sessions serialize the execution of message listeners; for details, see JMS Message Listeners.

A session provides a transactional context with which to group a set of sends and receives into an atomic unit of work. For details, see Using JMS API Local Transactions.

Sessions implement the Session interface. After you create a Connection object, you use it to create a Session:

Session session = connection.createSession(false,
     Session.AUTO_ACKNOWLEDGE);

The first argument means that the session is not transacted; the second means that the session automatically acknowledges messages when they have been received successfully. (For more information, see Controlling Message Acknowledgment.)

To create a transacted session, use the following code:

Session session = connection.createSession(true, 0);

Here, the first argument means that the session is transacted; the second indicates that message acknowledgment is not specified for transacted sessions. For more information on transactions, see Using JMS API Local Transactions. For information about the way JMS transactions work in Java EE applications, see Using the JMS API in a Java EE Application.

JMS Message Producers

A message producer is an object that is created by a session and used for sending messages to a destination. It implements the MessageProducer interface.

You use a Session to create a MessageProducer for a destination. The following examples show that you can create a producer for a Destination object, a Queue object, or a Topic object:

MessageProducer producer = session.createProducer(dest);
MessageProducer producer = session.createProducer(queue);
MessageProducer producer = session.createProducer(topic);

You can create an unidentified producer by specifying null as the argument to createProducer. With an unidentified producer, you do not specify a destination until you send a message.

After you have created a message producer, you can use it to send messages by using the send method:

producer.send(message);

You must first create the messages; see JMS Messages.

If you created an unidentified producer, use an overloaded send method that specifies the destination as the first parameter. For example:

MessageProducer anon_prod = session.createProducer(null);
anon_prod.send(dest, message);

JMS Message Consumers

A message consumer is an object that is created by a session and used for receiving messages sent to a destination. It implements the MessageConsumer interface.

A message consumer allows a JMS client to register interest in a destination with a JMS provider. The JMS provider manages the delivery of messages from a destination to the registered consumers of the destination.

For example, you could use a Session to create a MessageConsumer for a Destination object, a Queue object, or a Topic object:

MessageConsumer consumer = session.createConsumer(dest);
MessageConsumer consumer = session.createConsumer(queue);
MessageConsumer consumer = session.createConsumer(topic);

You use the Session.createDurableSubscriber method to create a durable topic subscriber. This method is valid only if you are using a topic. For details, see Creating Durable Subscriptions.

After you have created a message consumer, it becomes active, and you can use it to receive messages. You can use the close method for a MessageConsumer to make the message consumer inactive. Message delivery does not begin until you start the connection you created by calling its start method. (Remember always to call the start method; forgetting to start the connection is one of the most common JMS programming errors.)

You use the receive method to consume a message synchronously. You can use this method at any time after you call the start method:

connection.start();
Message m = consumer.receive();
connection.start();
Message m = consumer.receive(1000); // time out after a second

To consume a message asynchronously, you use a message listener, described in the next section.

JMS Message Listeners

A message listener is an object that acts as an asynchronous event handler for messages. This object implements the MessageListener interface, which contains one method, onMessage. In the onMessage method, you define the actions to be taken when a message arrives.

You register the message listener with a specific MessageConsumer by using the setMessageListener method. For example, if you define a class named Listener that implements the MessageListener interface, you can register the message listener as follows:

Listener myListener = new Listener();
consumer.setMessageListener(myListener);

After you register the message listener, you call the start method on the Connection to begin message delivery. (If you call start before you register the message listener, you are likely to miss messages.)

When message delivery begins, the JMS provider automatically calls the message listener’s onMessage method whenever a message is delivered. The onMessage method takes one argument of type Message, which your implementation of the method can cast to any of the other message types (see Message Bodies).

A message listener is not specific to a particular destination type. The same listener can obtain messages from either a queue or a topic, depending on the type of destination for which the message consumer was created. A message listener does, however, usually expect a specific message type and format.

Your onMessage method should handle all exceptions. It must not throw checked exceptions, and throwing a RuntimeException is considered a programming error.

The session used to create the message consumer serializes the execution of all message listeners registered with the session. At any time, only one of the session’s message listeners is running.

In the Java EE platform, a message-driven bean is a special kind of message listener. For details, see Using Message-Driven Beans to Receive Messages Asynchronously.

JMS Message Selectors

If your messaging application needs to filter the messages it receives, you can use a JMS API message selector, which allows a message consumer to specify the messages it is interested in. Message selectors assign the work of filtering messages to the JMS provider rather than to the application. For an example of an application that uses a message selector, see A Java EE Application That Uses the JMS API with a Session Bean.

A message selector is a String that contains an expression. The syntax of the expression is based on a subset of the SQL92 conditional expression syntax. The message selector in the example selects any message that has a NewsType property that is set to the value 'Sports' or 'Opinion':

NewsType = ’Sports’ OR NewsType = ’Opinion’

The createConsumer, createDurableSubscriber methods allow you to specify a message selector as an argument when you create a message consumer.

The message consumer then receives only messages whose headers and properties match the selector. (See Message Headers, and Message Properties.) A message selector cannot select messages on the basis of the content of the message body.

JMS Messages

The ultimate purpose of a JMS application is to produce and to consume messages that can then be used by other software applications. JMS messages have a basic format that is simple but highly flexible, allowing you to create messages that match formats used by non-JMS applications on heterogeneous platforms.

A JMS message has three parts: a header, properties, and a body. Only the header is required. The following sections describe these parts:

For complete documentation of message headers, properties, and bodies, see the documentation of the Message interface in the API documentation.

Message Headers

A JMS message header contains a number of predefined fields that contain values that both clients and providers use to identify and to route messages. Table 31–1 lists the JMS message header fields and indicates how their values are set. For example, every message has a unique identifier, which is represented in the header field JMSMessageID. The value of another header field, JMSDestination, represents the queue or the topic to which the message is sent. Other fields include a timestamp and a priority level.

Each header field has associated setter and getter methods, which are documented in the description of the Message interface. Some header fields are intended to be set by a client, but many are set automatically by the send or the publish method, which overrides any client-set values.

Table 31–1 How JMS Message Header Field Values Are Set

Header Field 

Set By 

JMSDestination

send or publish method

JMSDeliveryMode

send or publish method

JMSExpiration

send or publish method

JMSPriority

send or publish method

JMSMessageID

send or publish method

JMSTimestamp

send or publish method

JMSCorrelationID

Client 

JMSReplyTo

Client 

JMSType

Client 

JMSRedelivered

JMS provider 

Message Properties

You can create and set properties for messages if you need values in addition to those provided by the header fields. You can use properties to provide compatibility with other messaging systems, or you can use them to create message selectors (see JMS Message Selectors). For an example of setting a property to be used as a message selector, see A Java EE Application That Uses the JMS API with a Session Bean.

The JMS API provides some predefined property names that a provider can support. The use either of these predefined properties or of user-defined properties is optional.

Message Bodies

The JMS API defines five message body formats, also called message types, which allow you to send and to receive data in many different forms and provide compatibility with existing messaging formats. Table 31–2 describes these message types.

Table 31–2 JMS Message Types

Message Type 

Body Contains 

TextMessage

A java.lang.String object (for example, the contents of an XML file).

MapMessage

A set of name-value pairs, with names as String objects and values as primitive types in the Java programming language. The entries can be accessed sequentially by enumerator or randomly by name. The order of the entries is undefined.

BytesMessage

A stream of uninterpreted bytes. This message type is for literally encoding a body to match an existing message format. 

StreamMessage

A stream of primitive values in the Java programming language, filled and read sequentially. 

ObjectMessage

A Serializable object in the Java programming language.

Message

Nothing. Composed of header fields and properties only. This message type is useful when a message body is not required. 

The JMS API provides methods for creating messages of each type and for filling in their contents. For example, to create and send a TextMessage, you might use the following statements:

TextMessage message = session.createTextMessage();
message.setText(msg_text);     // msg_text is a String
producer.send(message);

At the consuming end, a message arrives as a generic Message object and must be cast to the appropriate message type. You can use one or more getter methods to extract the message contents. The following code fragment uses the getText method:

Message m = consumer.receive();
if (m instanceof TextMessage) {
    TextMessage message = (TextMessage) m;
    System.out.println("Reading message: " + message.getText());
} else {
    // Handle error
}

JMS Queue Browsers

You can create a QueueBrowser object to inspect the messages in a queue. Messages sent to a queue remain in the queue until the message consumer for that queue consumes them. Therefore, the JMS API provides an object that allows you to browse the messages in the queue and display the header values for each message. To create a QueueBrowser object, use the Session.createBrowser method. For example:

QueueBrowser browser = session.createBrowser(queue);

See A Simple Example of Browsing Messages in a Queue for an example of the use of a QueueBrowser object.

The createBrowser method allows you to specify a message selector as a second argument when you create a QueueBrowser. For information on message selectors, see JMS Message Selectors.

The JMS API provides no mechanism for browsing a topic. Messages usually disappear from a topic as soon as they appear: if there are no message consumers to consume them, the JMS provider removes them. Although durable subscriptions allow messages to remain on a topic while the message consumer is not active, no facility exists for examining them.

JMS Exception Handling

The root class for exceptions thrown by JMS API methods is JMSException. Catching JMSException provides a generic way of handling all exceptions related to the JMS API.

The JMSException class includes the following subclasses, which are described in the API documentation:

All the examples in the tutorial catch and handle JMSException when it is appropriate to do so.

Writing Simple JMS Client Applications

This section shows how to create, package, and run simple JMS client programs packaged as stand-alone application clients. These clients access a Java EE server. The clients demonstrate the basic tasks that a JMS application must perform:

In a Java EE application, some of these tasks are performed, in whole or in part, by the container. If you learn about these tasks, you will have a good basis for understanding how a JMS application works on the Java EE platform.

This section covers the following topics:

Each example uses two programs: one that sends messages and one that receives them. You can run the programs in NetBeans IDE or in two terminal windows.

When you write a JMS application to run in a Java EE application, you use many of the same methods in much the same sequence as you do for a stand-alone application client. However, there are some significant differences. Using the JMS API in a Java EE Application describes these differences, and Chapter 32, Java EE Examples Using the JMS API provides examples that illustrate them.

The examples for this section are in the following directory:

tut-install/javaeetutorial5/examples/jms/simple/

The examples are in the following four subdirectories:

producer
synchconsumer
asynchconsumer
messagebrowser

A Simple Example of Synchronous Message Receives

This section describes the sending and receiving programs in an example that uses the receive method to consume messages synchronously. This section then explains how to compile, package, and run the programs using the Application Server.

The following sections describe the steps in creating and running the example:

Writing the Client Programs for the Synchronous Receive Example

    The sending program, producer/src/java/Producer.java, performs the following steps:

  1. Injects resources for a connection factory, queue, and topic:

    @Resource(mappedName="jms/ConnectionFactory")
    private static ConnectionFactory connectionFactory;
    @Resource(mappedName="jms/Queue")private static Queue queue;
    @Resource(mappedName="jms/Topic")private static Topic topic;
  2. Retrieves and verifies command-line arguments that specify the destination type and the number of arguments:

    final int NUM_MSGS;
    String destType = args[0];
    System.out.println("Destination type is " + destType);
    if ( ! ( destType.equals("queue") || destType.equals("topic") ) ) { 
        System.err.println("Argument must be \”queue\” or " + "\”topic\”");
        System.exit(1);
    }
    if (args.length == 2){ 
        NUM_MSGS = (new Integer(args[1])).intValue();
    } 
    else { 
        NUM_MSGS = 1;
    }
  3. Assigns either the queue or topic to a destination object, based on the specified destination type:

    Destination dest = null;
    try { 
        if (destType.equals("queue")) { 
            dest = (Destination) queue; 
        } else { 
            dest = (Destination) topic; 
        }
    } 
    catch (Exception e) {
        System.err.println("Error setting destination: " + e.toString()); 
        e.printStackTrace(); 
        System.exit(1);
    }
  4. Creates a Connection and a Session:

    Connection connection = connectionFactory.createConnection(); 
    Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
  5. Creates a MessageProducer and a TextMessage:

    MessageProducer producer = session.createProducer(dest);
    TextMessage message = session.createTextMessage();
  6. Sends one or more messages to the destination:

    for (int i = 0; i < NUM_MSGS; i++) { 
        message.setText("This is message " + (i + 1)); 
        System.out.println("Sending message: " + message.getText()); 
        producer.send(message);
    }
  7. Sends an empty control message to indicate the end of the message stream:

    producer.send(session.createMessage());

    Sending an empty message of no specified type is a convenient way to indicate to the consumer that the final message has arrived.

  8. Closes the connection in a finally block, automatically closing the session and MessageProducer:

    } finally { 
        if (connection != null) { 
            try { connection.close(); } 
            catch (JMSException e) { } 
        }
    }

    The receiving program, synchconsumer/src/java/SynchConsumer.java, performs the following steps:

  1. Injects resources for a connection factory, queue, and topic.

  2. Assigns either the queue or topic to a destination object, based on the specified destination type.

  3. Creates a Connection and a Session.

  4. Creates a MessageConsumer:

    consumer = session.createConsumer(dest);
  5. Starts the connection, causing message delivery to begin:

    connection.start();
  6. Receives the messages sent to the destination until the end-of-message-stream control message is received:

    while (true) {
        Message m = consumer.receive(1); 
        if (m != null) { 
            if (m instanceof TextMessage) { 
                message = (TextMessage) m; 
                System.out.println("Reading message: " + message.getText()); 
            } else { 
                break; 
            } 
        }
    }

    Because the control message is not a TextMessage, the receiving program terminates the while loop and stops receiving messages after the control message arrives.

  7. Closes the connection in a finally block, automatically closing the session and MessageConsumer.

The receive method can be used in several ways to perform a synchronous receive. If you specify no arguments or an argument of 0, the method blocks indefinitely until a message arrives:

Message m = consumer.receive();
Message m = consumer.receive(0);

For a simple client program, this may not matter. But if you do not want your program to consume system resources unnecessarily, use a timed synchronous receive. Do one of the following:

The SynchConsumer program uses an indefinite while loop to receive messages, calling receive with a timeout argument. Calling receiveNoWait would have the same effect.

Starting the JMS Provider

When you use the Application Server, your JMS provider is the Application Server. Start the server as described in Starting and Stopping the Application Server.

Creating JMS Administered Objects for the Synchronous Receive Example

Creating the JMS administered objects for this section involves the following:

If you built and ran the SimpleMessage example in Chapter 23, A Message-Driven Bean Example and did not delete the resources afterward, you need to create only the topic resource.

    You can create these objects using the Ant tool. To create all the resources, do the following:

  1. In a terminal window, go to the producer directory:


    cd producer
    
  2. To create all the resources, type the following command:


    ant create-resources
    

    To create only the topic resource, type the following command:


    ant create-topic
    

These Ant targets use the asadmin create-jms-resource command to create the connection factory and the destination resources.

To verify that the resources have been created, use the following command:


asadmin list-jms-resources

The output looks like this:


jms/Queue
jms/Topic
jms/ConnectionFactory
Command list-jms-resources executed successfully.

Compiling and Packaging the Clients for the Synchronous Receive Example

The simplest way to run these examples using the Application Server is to package each one in an application client JAR file. The application client JAR file requires a manifest file, located in the src/conf directory for each example, along with the .class file.

The build.xml file for each example contains Ant targets that compile and package the example. The targets place the .class file for the example in the build/jar directory. Then the targets use the jar command to package the class file and the manifest file in an application client JAR file.

    To compile and package the Producer and SynchConsumer examples using NetBeans IDE, follow these steps:

  1. In NetBeans IDE, choose Open Project from the File menu.

  2. In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/simple/.

  3. Select the producer folder.

  4. Select the Open as Main Project check box.

  5. Click Open Project.

  6. Right-click the project and choose Build.

  7. In NetBeans IDE, choose Open Project from the File menu.

  8. In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/simple/.

  9. Select the synchconsumer folder.

  10. Select the Open as Main Project check box.

  11. Click Open Project.

  12. Right-click the project and choose Build.

    To compile and package the Producer and SynchConsumer examples using Ant, follow these steps:

  1. In a terminal window, go to the producer directory:


    cd producer
    
  2. Type the following command:


    ant
    
  3. In a terminal window, go to the synchconsumer directory:


    cd ../synchconsumer
    
  4. Type the following command:


    ant
    

The targets place the application client JAR file in the dist directory for each example.

Running the Clients for the Synchronous Receive Example

    To run the sample programs using NetBeans IDE, follow these steps.

  1. Run the Producer example:

    1. Right-click the producer project and choose Properties.

    2. Select Run from the Categories tree.

    3. In the Arguments field, type the following:


      queue 3
      
    4. Click OK.

    5. Right-click the project and choose Run.

      The output of the program looks like this:


      Destination type is queue
      Sending message: This is message 1
      Sending message: This is message 2
      Sending message: This is message 3

      The messages are now in the queue, waiting to be received.

  2. Now run the SynchConsumer example:

    1. Right-click the synchconsumer project and choose Properties.

    2. Select Run from the Categories tree.

    3. In the Arguments field, type the following:


      queue
      
    4. Click OK.

    5. Right-click the project and choose Run.

      The output of the program looks like this:


      Destination type is queue
      Reading message: This is message 1
      Reading message: This is message 2
      Reading message: This is message 3
  3. Now try running the programs in the opposite order. Right-click the synchconsumer project and choose Run.

    The Output pane displays the destination type and then appears to hang, waiting for messages.

  4. Right-click the producer project and choose Run.

    The Output pane shows the output of both programs, in two different tabs.

  5. Now run the Producer example using a topic instead of a queue.

    1. Right-click the producer project and choose Properties.

    2. Select Run from the Categories tree.

    3. In the Arguments field, type the following:


      topic 3
      
    4. Click OK.

    5. Right-click the project and choose Run.

      The output of the program looks like this:


      Destination type is topic
      Sending message: This is message 1
      Sending message: This is message 2
      Sending message: This is message 3
  6. Now run the SynchConsumer example using the topic.

    1. Right-click the synchconsumer project and choose Properties.

    2. Select Run from the Categories tree.

    3. In the Arguments field, type the following:


      topic
      
    4. Click OK.

    5. Right-click the project and choose Run.

      The result, however, is different. Because you are using a topic, messages that were sent before you started the consumer cannot be received. (See Publish/Subscribe Messaging Domain, for details.) Instead of receiving the messages, the program appears to hang.

  7. Run the Producer example again. Right-click the producer project and choose Run.

    Now the SynchConsumer example receives the messages:


    Destination type is topic
    Reading message: This is message 1
    Reading message: This is message 2
    Reading message: This is message 3

You can also run the sample programs using the appclient command. Each of the programs takes one or more command-line arguments: a destination type and, for Producer, a number of messages.

    To run the clients using the appclient command, follow these steps:

  1. In a terminal window, go to the producer/dist directory:


    cd ../producer/dist
    
  2. Run the Producer program, sending three messages to the queue:


    appclient -client producer.jar queue 3
    

    The output of the program looks like this:


    Destination type is queue
    Sending message: This is message 1
    Sending message: This is message 2
    Sending message: This is message 3

    The messages are now in the queue, waiting to be received.

  3. In the same window, go to the synchconsumer/dist directory:


    cd ../../synchconsumer/dist
    
  4. Run the SynchConsumer program, specifying the queue:


    appclient -client synchconsumer.jar queue
    

    The output of the program looks like this:


    Destination type is queue
    Reading message: This is message 1
    Reading message: This is message 2
    Reading message: This is message 3
  5. Now try running the programs in the opposite order. Run the SynchConsumer program. It displays the destination type and then appears to hang, waiting for messages.


    appclient -client synchconsumer.jar queue
    
  6. In a different terminal window, run the Producer program.


    cd tut-install/javaeetutorial5/examples/jms/simple/producer/dist
    appclient -client producer.jar queue 3
    

    When the messages have been sent, the SynchConsumer program receives them and exits.

  7. Now run the Producer program using a topic instead of a queue:


    appclient -client producer.jar topic 3
    

    The output of the program looks like this:


    Destination type is topic
    Sending message: This is message 1
    Sending message: This is message 2
    Sending message: This is message 3
  8. Now run the SynchConsumer program using the topic:


    appclient -client synchconsumer.jar topic
    

    The result, however, is different. Because you are using a topic, messages that were sent before you started the consumer cannot be received. (See Publish/Subscribe Messaging Domain, for details.) Instead of receiving the messages, the program appears to hang.

  9. Run the Producer program again. Now the SynchConsumer program receives the messages:


    Destination type is topic
    Reading message: This is message 1
    Reading message: This is message 2
    Reading message: This is message 3

Because the examples use the common interfaces, you can run them using either a queue or a topic.

A Simple Example of Asynchronous Message Consumption

This section describes the receiving programs in an example that uses a message listener to consume messages asynchronously. This section then explains how to compile and run the programs using the Application Server.

The following sections describe the steps in creating and running the example:

Writing the Client Programs for the Asynchronous Receive Example

The sending program is producer/src/java/Producer.java, the same program used in the example in A Simple Example of Synchronous Message Receives.

An asynchronous consumer normally runs indefinitely. This one runs until the user types the letter q or Q to stop the program.

    The receiving program, asynchconsumer/src/java/AsynchConsumer.java, performs the following steps:

  1. Injects resources for a connection factory, queue, and topic.

  2. Assigns either the queue or topic to a destination object, based on the specified destination type.

  3. Creates a Connection and a Session.

  4. Creates a MessageConsumer.

  5. Creates an instance of the TextListener class and registers it as the message listener for the MessageConsumer:

    listener = new TextListener();consumer.setMessageListener(listener);
  6. Starts the connection, causing message delivery to begin.

  7. Listens for the messages published to the destination, stopping when the user types the character q or Q:

    System.out.println("To end program, type Q or q, " + "then <return>");
    inputStreamReader = new InputStreamReader(System.in);
    while (!((answer == ’q’) || (answer == ’Q’))) { 
        try { 
            answer = (char) inputStreamReader.read(); 
        } catch (IOException e) { 
            System.out.println("I/O exception: " + e.toString()); 
        }
    }
  8. Closes the connection, which automatically closes the session and MessageConsumer.

    The message listener, asynchconsumer/src/java/TextListener.java, follows these steps:

  1. When a message arrives, the onMessage method is called automatically.

  2. The onMessage method converts the incoming message to a TextMessage and displays its content. If the message is not a text message, it reports this fact:

    public void onMessage(Message message) { 
        TextMessage msg = null; 
        try { 
            if (message instanceof TextMessage) { 
                msg = (TextMessage) message; 
                 System.out.println("Reading message: " + msg.getText()); 
            } else { 
                 System.out.println("Message is not a " + "TextMessage"); 
            } 
        } catch (JMSException e) { 
            System.out.println("JMSException in onMessage(): " + e.toString()); 
        } catch (Throwable t) { 
            System.out.println("Exception in onMessage():" + t.getMessage()); 
        }
    }

You will use the connection factory and destinations you created in Creating JMS Administered Objects for the Synchronous Receive Example.

Compiling and Packaging the AsynchConsumer Client

    To compile and package the AsynchConsumer example using NetBeans IDE, follow these steps:

  1. In NetBeans IDE, choose Open Project from the File menu.

  2. In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/simple/.

  3. Select the asynchconsumer folder.

  4. Select the Open as Main Project check box.

  5. Click Open Project.

  6. Right-click the project and choose Build.

    To compile and package the AsynchConsumer example using Ant, follow these steps:

  1. In a terminal window, go to the asynchconsumer directory:


    cd ../../asynchconsumer
    
  2. Type the following command:


    ant
    

The targets package both the main class and the message listener class in the JAR file and place the file in the dist directory for the example.

Running the Clients for the Asynchronous Receive Example

    To run the programs using NetBeans IDE, follow these steps.

  1. Run the AsynchConsumer example:

    1. Right-click the asynchconsumer project and choose Properties.

    2. Select Run from the Categories tree.

    3. In the Arguments field, type the following:


      topic
      
    4. Click OK.

    5. Right-click the project and choose Run.

      The program displays the following lines and appears to hang:


      Destination type is topic
      To end program, type Q or q, then <return>
  2. Now run the Producer example:

    1. Right-click the producer project and choose Properties.

    2. Select Run from the Categories tree.

    3. In the Arguments field, type the following:


      topic 3
      
    4. Click OK.

    5. Right-click the project and choose Run.

      The output of the program looks like this:


      Destination type is topic
      Sending message: This is message 1
      Sending message: This is message 2
      Sending message: This is message 3

      In the other window, the AsynchConsumer program displays the following:


      Destination type is topic
      To end program, type Q or q, then <return>
      Reading message: This is message 1
      Reading message: This is message 2
      Reading message: This is message 3
      Message is not a TextMessage

      The last line appears because the program has received the non-text control message sent by the Producer program.

  3. Type Q or q in the Output window and press Return to stop the program.

  4. Now run the programs using a queue. In this case, as with the synchronous example, you can run the Producer program first, because there is no timing dependency between the sender and receiver.

    1. Right-click the producer project and choose Properties.

    2. Select Run from the Categories tree.

    3. In the Arguments field, type the following:


      queue 3
      
    4. Click OK.

    5. Right-click the project and choose Run.

      The output of the program looks like this:


      Destination type is queue
      Sending message: This is message 1
      Sending message: This is message 2
      Sending message: This is message 3
  5. Run the AsynchConsumer program.

    1. Right-click the asynchconsumer project and choose Properties.

    2. Select Run from the Categories tree.

    3. In the Arguments field, type the following:


      queue
      
    4. Click OK.

    5. Right-click the project and choose Run.

      The output of the program looks like this:


      Destination type is queue
      To end program, type Q or q, then <return>
      Reading message: This is message 1
      Reading message: This is message 2
      Reading message: This is message 3
      Message is not a TextMessage
  6. Type Q or q in the Output window and press Return to stop the program.

    To run the clients using the appclient command, follow these steps:

  1. Run the AsynchConsumer program, specifying the topic destination type.


    cd dist
    appclient -client asynchconsumer.jar topic
    

    The program displays the following lines and appears to hang:


    Destination type is topic
    To end program, type Q or q, then <return>
  2. In the terminal window where you ran the Producer program previously, run the program again, sending three messages. The command looks like this:


    appclient -client producer.jar topic 3
    

    The output of the program looks like this:


    Destination type is topic
    Sending message: This is message 1
    Sending message: This is message 2
    Sending message: This is message 3

    In the other window, the AsynchConsumer program displays the following:


    Destination type is topic
    To end program, type Q or q, then <return>
    Reading message: This is message 1
    Reading message: This is message 2
    Reading message: This is message 3
    Message is not a TextMessage

    The last line appears because the program has received the non-text control message sent by the Producer program.

  3. Type Q or q and press Return to stop the program.

  4. Now run the programs using a queue. In this case, as with the synchronous example, you can run the Producer program first, because there is no timing dependency between the sender and receiver:


    appclient -client producer.jar queue 3
    

    The output of the program looks like this:


    Destination type is queue
    Sending message: This is message 1
    Sending message: This is message 2
    Sending message: This is message 3
  5. Run the AsynchConsumer program:


    appclient -client asynchconsumer.jar queue
    

    The output of the program looks like this:


    Destination type is queue
    To end program, type Q or q, then <return>
    Reading message: This is message 1
    Reading message: This is message 2
    Reading message: This is message 3
    Message is not a TextMessage
  6. Type Q or q to stop the program.

A Simple Example of Browsing Messages in a Queue

This section describes an example that creates a QueueBrowser object to examine messages on a queue, as described in JMS Queue Browsers. This section then explains how to compile, package, and run the example using the Application Server.

The following sections describe the steps in creating and running the example:

Writing the Client Program for the Queue Browser Example

To create a QueueBrowser for a queue, you call the Session.createBrowser method with the queue as the argument. You obtain the messages in the queue as an Enumeration object. You can then iterate through the Enumeration object and display the contents of each message.

    The messagebrowser/src/java/MessageBrowser.java program performs the following steps:

  1. Injects resources for a connection factory and a queue.

  2. Creates a Connection and a Session.

  3. Creates a QueueBrowser:

    QueueBrowser browser = session.createBrowser(queue);
  4. Retrieves the Enumeration that contains the messages:

    Enumeration msgs = browser.getEnumeration();
  5. Verifies that the Enumeration contains messages, then displays the contents of the messages:

    if ( !msgs.hasMoreElements() ) { 
        System.out.println("No messages in queue");
    } else { 
        while (msgs.hasMoreElements()) { 
            Message tempMsg = (Message)msgs.nextElement(); 
            System.out.println("Message: " + tempMsg); 
        }
    }
  6. Closes the connection, which automatically closes the session and QueueBrowser.

The format in which the message contents appear is implementation-specific. In the Application Server, the message format looks like this:


Message contents: 
Text:   This is message 3
Class:                  com.sun.messaging.jmq.jmsclient.TextMessageImpl
getJMSMessageID():      ID:14-129.148.71.199(f9:86:a2:d5:46:9b)-40814-1129061034355
getJMSTimestamp():      1129061034355
getJMSCorrelationID():  null
JMSReplyTo:             null
JMSDestination:         PhysicalQueue
getJMSDeliveryMode():   PERSISTENT
getJMSRedelivered():    false
getJMSType():           null
getJMSExpiration():     0
getJMSPriority():       4
Properties:             null

You will use the connection factory and queue you created in Creating JMS Administered Objects for the Synchronous Receive Example.

Compiling and Packaging the MessageBrowser Client

    To compile and package the MessageBrowser example using NetBeans IDE, follow these steps:

  1. In NetBeans IDE, choose Open Project from the File menu.

  2. In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/simple/.

  3. Select the messagebrowser folder.

  4. Select the Open as Main Project check box.

  5. Click Open Project.

  6. Right-click the project and choose Build.

    To compile and package the MessageBrowser example using Ant, follow these steps:

  1. In a terminal window, go to the messagebrowser directory. If you are currently in the asynchconsumer/dist directory, you need to go up two levels:


    cd ../../messagebrowser
    
  2. Type the following command:


    ant
    

The targets place the application client JAR file in the dist directory for the example.

You also need the Producer example to send the message to the queue, and one of the consumer programs to consume the messages after you inspect them. If you did not do so already, package these examples.

Running the Clients for the Queue Browser Example

    To run the programs using NetBeans IDE, follow these steps.

  1. Run the Producer program, sending one message to the queue:

    1. Right-click the producer project and choose Properties.

    2. Select Run from the Categories tree.

    3. In the Arguments field, type the following:


      queue
      
    4. Click OK.

    5. Right-click the project and choose Run.

      The output of the program looks like this:


      Destination type is queue
      Sending message: This is message 1
  2. Run the MessageBrowser program. Right-click the messagebrowser project and choose Run.

    The output of the program looks like this:


    Message: 
    Text: This is message 1
    Class: com.sun.messaging.jmq.jmsclient.TextMessageImpl
    getJMSMessageID(): ID:12-129.148.71.199(8c:34:4a:1a:1b:b8)-40883-1129062957611
    getJMSTimestamp(): 1129062957611
    getJMSCorrelationID(): null
    JMSReplyTo: null
    JMSDestination: PhysicalQueue
    getJMSDeliveryMode(): PERSISTENT
    getJMSRedelivered(): false
    getJMSType(): null
    getJMSExpiration(): 0
    getJMSPriority(): 4
    Properties: null
    Message: 
    Class: com.sun.messaging.jmq.jmsclient.MessageImpl
    getJMSMessageID(): ID:13-129.148.71.199(8c:34:4a:1a:1b:b8)-40883-1129062957616
    getJMSTimestamp(): 1129062957616
    getJMSCorrelationID(): null
    JMSReplyTo: null
    JMSDestination: PhysicalQueue
    getJMSDeliveryMode(): PERSISTENT
    getJMSRedelivered(): false
    getJMSType(): null
    getJMSExpiration(): 0
    getJMSPriority(): 4
    Properties: null
  3. The first message is the TextMessage, and the second is the non-text control message.

  4. Run the SynchConsumer program to consume the messages.

    1. Right-click the synchconsumer project and choose Properties.

    2. Select Run from the Categories tree.

    3. In the Arguments field, type the following:


      queue
      
    4. Click OK.

    5. Right-click the project and choose Run.

      The output of the program looks like this:


      Destination type is queue
      Reading message: This is message 1

To run the clients using the appclient command, follow these steps. You may want to use two terminal windows.

  1. Go to the producer/dist directory.

  2. Run the Producer program, sending one message to the queue:


    appclient -client producer.jar queue
    

    The output of the program looks like this:


    Destination type is queue
    Sending message: This is message 1
  3. Go to the messagebrowser/dist directory.

  4. Run the MessageBrowser program:


    appclient -client messagebrowser.jar
    

    The output of the program looks like this:


    Message: 
    Text: This is message 1
    Class: com.sun.messaging.jmq.jmsclient.TextMessageImpl
    getJMSMessageID(): ID:12-129.148.71.199(8c:34:4a:1a:1b:b8)-40883-1129062957611
    getJMSTimestamp(): 1129062957611
    getJMSCorrelationID(): null
    JMSReplyTo: null
    JMSDestination: PhysicalQueue
    getJMSDeliveryMode(): PERSISTENT
    getJMSRedelivered(): false
    getJMSType(): null
    getJMSExpiration(): 0
    getJMSPriority(): 4
    Properties: null
    Message: 
    Class: com.sun.messaging.jmq.jmsclient.MessageImpl
    getJMSMessageID(): ID:13-129.148.71.199(8c:34:4a:1a:1b:b8)-40883-1129062957616
    getJMSTimestamp(): 1129062957616
    getJMSCorrelationID(): null
    JMSReplyTo: null
    JMSDestination: PhysicalQueue
    getJMSDeliveryMode(): PERSISTENT
    getJMSRedelivered(): false
    getJMSType(): null
    getJMSExpiration(): 0
    getJMSPriority(): 4
    Properties: null

    The first message is the TextMessage, and the second is the non-text control message.

  5. Go to the synchconsumer/dist directory.

  6. Run the SynchConsumer program to consume the messages:


    appclient -client synchconsumer.jar queue
    

    The output of the program looks like this:


    Destination type is queue
    Reading message: This is message 1

Running JMS Client Programs on Multiple Systems

JMS client programs using the Application Server can exchange messages with each other when they are running on different systems in a network. The systems must be visible to each other by name (the UNIX host name or the Microsoft Windows computer name) and must both be running the Application Server. You do not have to install the tutorial examples on both systems; you can use the examples installed on one system if you can access its file system from the other system.


Note –

Any mechanism for exchanging messages between systems is specific to the Java EE server implementation. This tutorial describes how to use the Application Server for this purpose.


Suppose that you want to run the Producer program on one system, earth, and the SynchConsumer program on another system, jupiter. Before you can do so, you need to perform these tasks:

    Before you begin, start the server on both systems:

  1. Start the Application Server on earth.

  2. Start the Application Server on jupiter.

Creating Administered Objects for Multiple Systems

To run these programs, you must do the following:

You do not have to install the tutorial on both systems, but you must be able to access the filesystem where it is installed. You may find it more convenient to install the tutorial on both systems if the two systems use different operating systems (for example, Windows and Solaris). Otherwise you will have to edit the file tut-install/javaeetutorial5/examples/bp-project/build.properties and change the location of the javaee.home property each time you build or run a program on a different system.

    To create a new connection factory on jupiter, perform these steps:

  1. From a command shell on jupiter, go to the directory tut-install/javaeetutorial5/examples/jms/simple/producer/.

  2. Type the following command:


    ant create-local-factory
    

The create-local-factory target, defined in the build.xml file for the Producer example, creates a connection factory named jms/JupiterConnectionFactory.

    To create a new connection factory on earth that points to the connection factory on jupiter, perform these steps:

  1. From a command shell on earth, go to the directory tut-install/javaeetutorial5/examples/jms/simple/producer/.

  2. Type the following command:


    ant create-remote-factory -Dsys=remote-system-name
    

    Replace remote-system-name with the actual name of the remote system.

The create-remote-factory target, defined in the build.xml file for the Producer example, also creates a connection factory named jms/JupiterConnectionFactory. In addition, it sets the AddressList property for this factory to the name of the remote system.

If you have already been working on either earth or jupiter, you have the queue and topic on one system. On the system that does not have the queue and topic, type the following command:


ant create-resources

When you run the programs, they will work as shown in Figure 31–6. The program run on earth needs the queue on earth only in order that the resource injection will succeed. The connection, session, and message producer are all created on jupiter using the connection factory that points to jupiter. The messages sent from earth will be received on jupiter.

Figure 31–6 Sending Messages from One System to Another

Diagram showing a message being sent to a queue by a
producer on Earth and being received by a consumer on Jupiter

Editing, Recompiling, Repackaging, and Running the Programs

These steps assume that you have the tutorial installed on only one of the two systems you are using and that you are able to access the file system of jupiter from earth or vice versa.

    After you create the connection factories, edit the source files to specify the new connection factory. Then recompile, repackage, and run the programs. Perform the following steps:

  1. Open the following file in a text editor:

    tut-installjavaeetutorial5/examples/jms/simple/producer/src/java/Producer.java
    
  2. Find the following line:

    @Resource(mappedName="jms/ConnectionFactory")
  3. Change the line to the following:

    @Resource(mappedName="jms/JupiterConnectionFactory")
  4. Recompile and repackage the Producer example on earth.

    If you are using NetBeans IDE, right-click the producer project and choose Clean and Build.

    If you are using Ant, type the following:


    ant
    
  5. Open the following file in a text editor:

    tut-installjavaeetutorial5/examples/jms/simple/synchconsumer/src/java/SynchConsumer.java
    
  6. Repeat steps 2 and 3.

  7. Recompile and repackage the SynchConsumer example on jupiter.

    If you are using NetBeans IDE, right-click the synchconsumer project and choose Clean and Build.

    If you are using Ant, go to the synchconsumer directory and type:


    ant
    
  8. On earth, run Producer. If you are using NetBeans IDE on earth, perform these steps:

    1. Right-click the producer project and choose Properties.

    2. Select Run from the Categories tree.

    3. In the Arguments field, type the following:


      queue 3
      
    4. Click OK.

    5. Right-click the project and choose Run.

      If you are using the appclient command, go to the producer/dist directory and type the following:


      appclient -client producer.jar queue 3
      
  9. On jupiter, run SynchConsumer. If you are using NetBeans IDE on jupiter, perform these steps:

    1. Right-click the synchconsumer project and choose Properties.

    2. Select Run from the Categories tree.

    3. In the Arguments field, type the following:


      queue
      
    4. Click OK.

    5. Right-click the project and choose Run.

      If you are using the appclient command, go to the synchconsumer/dist directory and type the following:


      appclient -client synchconsumer.jar queue
      

For examples showing how to deploy Java EE applications on two different systems, see An Application Example That Consumes Messages from a Remote Server and An Application Example That Deploys a Message-Driven Bean on Two Servers.

Deleting the Connection Factory and Stopping the Server

You will need the connection factory jms/JupiterConnectionFactory in Chapter 32, Java EE Examples Using the JMS API. However, if you wish to delete it, go to the producer directory and type the following command:


ant delete-remote-factory

Remember to delete the connection factory on both systems.

You can also use Ant targets in the producer/build.xml file to delete the destinations and connection factories you created in Creating JMS Administered Objects for the Synchronous Receive Example. However, it is recommended that you keep them, because they will be used in most of the examples in Chapter 32, Java EE Examples Using the JMS API. After you have created them, they will be available whenever you restart the Application Server.

To delete the class and JAR files for each program using NetBeans IDE, right-click each project and choose Clean.

To delete the class and JAR files for each program using Ant, type the following:


ant clean

You can also stop the Application Server, but you will need it to run the sample programs in the next section.

Creating Robust JMS Applications

This section explains how to use features of the JMS API to achieve the level of reliability and performance your application requires. Many people choose to implement JMS applications because they cannot tolerate dropped or duplicate messages and require that every message be received once and only once. The JMS API provides this functionality.

The most reliable way to produce a message is to send a PERSISTENT message within a transaction. JMS messages are PERSISTENT by default. A transaction is a unit of work into which you can group a series of operations, such as message sends and receives, so that the operations either all succeed or all fail. For details, see Specifying Message Persistence and Using JMS API Local Transactions.

The most reliable way to consume a message is to do so within a transaction, either from a queue or from a durable subscription to a topic. For details, see Creating Temporary Destinations, Creating Durable Subscriptions, and Using JMS API Local Transactions.

For other applications, a lower level of reliability can reduce overhead and improve performance. You can send messages with varying priority levels (see Setting Message Priority Levels) and you can set them to expire after a certain length of time (see Allowing Messages to Expire).

The JMS API provides several ways to achieve various kinds and degrees of reliability. This section divides them into two categories:

The following sections describe these features as they apply to JMS clients. Some of the features work differently in Java EE applications; in these cases, the differences are noted here and are explained in detail in Using the JMS API in a Java EE Application.

This section includes three sample programs, which you can find in the directory tut-install/javaeetutorial5/examples/jms/advanced/. Each sample uses a utility class called SampleUtilities.java.

Using Basic Reliability Mechanisms

The basic mechanisms for achieving or affecting reliable message delivery are as follows:

Controlling Message Acknowledgment

    Until a JMS message has been acknowledged, it is not considered to be successfully consumed. The successful consumption of a message ordinarily takes place in three stages.

  1. The client receives the message.

  2. The client processes the message.

  3. The message is acknowledged. Acknowledgment is initiated either by the JMS provider or by the client, depending on the session acknowledgment mode.

In transacted sessions (see Using JMS API Local Transactions), acknowledgment happens automatically when a transaction is committed. If a transaction is rolled back, all consumed messages are redelivered.

In nontransacted sessions, when and how a message is acknowledged depend on the value specified as the second argument of the createSession method. The three possible argument values are as follows:

If messages have been received from a queue but not acknowledged when a session terminates, the JMS provider retains them and redelivers them when a consumer next accesses the queue. The provider also retains unacknowledged messages for a terminated session that has a durable TopicSubscriber. (See Creating Durable Subscriptions.) Unacknowledged messages for a nondurable TopicSubscriber are dropped when the session is closed.

If you use a queue or a durable subscription, you can use the Session.recover method to stop a nontransacted session and restart it with its first unacknowledged message. In effect, the session’s series of delivered messages is reset to the point after its last acknowledged message. The messages it now delivers may be different from those that were originally delivered, if messages have expired or if higher-priority messages have arrived. For a nondurable TopicSubscriber, the provider may drop unacknowledged messages when its session is recovered.

The sample program in the next section demonstrates two ways to ensure that a message will not be acknowledged until processing of the message is complete.

A Message Acknowledgment Example

The AckEquivExample.java program shows how both of the following two scenarios ensure that a message will not be acknowledged until processing of it is complete:

With a message listener, the automatic acknowledgment happens when the onMessage method returns (that is, after message processing has finished). With a synchronous receiver, the client acknowledges the message after processing is complete. If you use AUTO_ACKNOWLEDGE with a synchronous receive, the acknowledgment happens immediately after the receive call; if any subsequent processing steps fail, the message cannot be redelivered.

The program is in the following directory:

tut-install/javaeetutorial5/examples/jms/advanced/ackequivexample/src/java/

The program contains a SynchSender class, a SynchReceiver class, an AsynchSubscriber class with a TextListener class, a MultiplePublisher class, a main method, and a method that runs the other classes’ threads.

The program uses the following objects:

To create the new queue and connection factory, you can use Ant targets defined in the file tut-install/javaeetutorial5/examples/jms/advanced/ackequivexample/build.xml.

    To run this example, follow these steps:

  1. In a terminal window, go to the following directory:

    tut-install/javaeetutorial5/examples/jms/advanced/ackequivexample/
    
  2. To create the objects needed in this example, type the following commands:


    ant create-control-queue
    ant create-durable-cf
    
  3. To compile and package the program using NetBeans IDE, follow these steps:

    1. In NetBeans IDE, choose Open Project from the File menu.

    2. In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/advanced/.

    3. Select the ackequivexample folder.

    4. Select the Open as Main Project check box.

    5. Click Open Project.

    6. Right-click the project and choose Build.

    To compile and package the program using Ant, type the following command:


    ant
    
  4. To run the program using NetBeans IDE, right-click the ackequivexample project and choose Run.

      To run the program from the command line, follow these steps:

    1. Go to the dist directory:


      cd dist
      
    2. Type the following command:


      appclient -client ackequivexample.jar
      

The program output looks something like this:


Queue name is jms/ControlQueue
Queue name is jms/Queue
Topic name is jms/Topic
Connection factory name is jms/DurableConnectionFactory
  SENDER: Created client-acknowledge session
  SENDER: Sending message: Here is a client-acknowledge message
  RECEIVER: Created client-acknowledge session
  RECEIVER: Processing message: Here is a client-acknowledge message
  RECEIVER: Now I’ll acknowledge the message
SUBSCRIBER: Created auto-acknowledge session
SUBSCRIBER: Sending synchronize message to control queue
PUBLISHER: Created auto-acknowledge session
PUBLISHER: Receiving synchronize messages from control queue; count = 1
PUBLISHER: Received synchronize message;  expect 0 more
PUBLISHER: Publishing message: Here is an auto-acknowledge message 1
PUBLISHER: Publishing message: Here is an auto-acknowledge message 2
SUBSCRIBER: Processing message: Here is an auto-acknowledge message 1
PUBLISHER: Publishing message: Here is an auto-acknowledge message 3
SUBSCRIBER: Processing message: Here is an auto-acknowledge message 2
SUBSCRIBER: Processing message: Here is an auto-acknowledge message 3

After you run the program, you can delete the destination resource jms/ControlQueue. Go to the directory tut-install/javaeetutorial5/examples/jms/advanced/ackequivexample/ and type the following command:


ant delete-control-queue

You will need the other resources for other examples.

To delete the class and JAR files for the program using NetBeans IDE, right-click the project and choose Clean.

To delete the class and JAR files for the program using Ant, type the following:


ant clean

Specifying Message Persistence

The JMS API supports two delivery modes for messages to specify whether messages are lost if the JMS provider fails. These delivery modes are fields of the DeliveryMode interface.

You can specify the delivery mode in either of two ways.

If you do not specify a delivery mode, the default is PERSISTENT. Using the NON_PERSISTENT delivery mode may improve performance and reduce storage overhead, but you should use it only if your application can afford to miss messages.

Setting Message Priority Levels

You can use message priority levels to instruct the JMS provider to deliver urgent messages first. You can set the priority level in either of two ways.

The ten levels of priority range from 0 (lowest) to 9 (highest). If you do not specify a priority level, the default level is 4. A JMS provider tries to deliver higher-priority messages before lower-priority ones but does not have to deliver messages in exact order of priority.

Allowing Messages to Expire

By default, a message never expires. If a message will become obsolete after a certain period, however, you may want to set an expiration time. You can do this in either of two ways.

If the specified timeToLive value is 0, the message never expires.

When the message is sent, the specified timeToLive is added to the current time to give the expiration time. Any message not delivered before the specified expiration time is destroyed. The destruction of obsolete messages conserves storage and computing resources.

Creating Temporary Destinations

Normally, you create JMS destinations (queues and topics) administratively rather than programmatically. Your JMS provider includes a tool that you use to create and remove destinations, and it is common for destinations to be long-lasting.

The JMS API also enables you to create destinations (TemporaryQueue and TemporaryTopic objects) that last only for the duration of the connection in which they are created. You create these destinations dynamically using the Session.createTemporaryQueue and the Session.createTemporaryTopic methods.

The only message consumers that can consume from a temporary destination are those created by the same connection that created the destination. Any message producer can send to the temporary destination. If you close the connection that a temporary destination belongs to, the destination is closed and its contents are lost.

You can use temporary destinations to implement a simple request/reply mechanism. If you create a temporary destination and specify it as the value of the JMSReplyTo message header field when you send a message, then the consumer of the message can use the value of the JMSReplyTo field as the destination to which it sends a reply. The consumer can also reference the original request by setting the JMSCorrelationID header field of the reply message to the value of the JMSMessageID header field of the request. For example, an onMessage method can create a session so that it can send a reply to the message it receives. It can use code such as the following:

producer = session.createProducer(msg.getJMSReplyTo());
replyMsg = session.createTextMessage("Consumer " +
    "processed message: " + msg.getText());
replyMsg.setJMSCorrelationID(msg.getJMSMessageID());
producer.send(replyMsg);

For more examples, see Chapter 32, Java EE Examples Using the JMS API.

Using Advanced Reliability Mechanisms

The more advanced mechanisms for achieving reliable message delivery are the following:

Creating Durable Subscriptions

To ensure that a pub/sub application receives all published messages, use PERSISTENT delivery mode for the publishers. In addition, use durable subscriptions for the subscribers.

The Session.createConsumer method creates a nondurable subscriber if a topic is specified as the destination. A nondurable subscriber can receive only messages that are published while it is active.

At the cost of higher overhead, you can use the Session.createDurableSubscriber method to create a durable subscriber. A durable subscription can have only one active subscriber at a time.

A durable subscriber registers a durable subscription by specifying a unique identity that is retained by the JMS provider. Subsequent subscriber objects that have the same identity resume the subscription in the state in which it was left by the preceding subscriber. If a durable subscription has no active subscriber, the JMS provider retains the subscription’s messages until they are received by the subscription or until they expire.

You establish the unique identity of a durable subscriber by setting the following:

You set the client ID administratively for a client-specific connection factory using the Admin Console.

After using this connection factory to create the connection and the session, you call the createDurableSubscriber method with two arguments: the topic and a string that specifies the name of the subscription:

String subName = "MySub";
MessageConsumer topicSubscriber =
     session.createDurableSubscriber(myTopic, subName);

The subscriber becomes active after you start the Connection or TopicConnection. Later, you might close the subscriber:

topicSubscriber.close();

The JMS provider stores the messages sent or published to the topic, as it would store messages sent to a queue. If the program or another application calls createDurableSubscriber using the same connection factory and its client ID, the same topic, and the same subscription name, the subscription is reactivated, and the JMS provider delivers the messages that were published while the subscriber was inactive.

To delete a durable subscription, first close the subscriber, and then use the unsubscribe method, with the subscription name as the argument:

topicSubscriber.close();
session.unsubscribe("MySub");

The unsubscribe method deletes the state that the provider maintains for the subscriber.

Figure 31–7 and Figure 31–8 show the difference between a nondurable and a durable subscriber. With an ordinary, nondurable subscriber, the subscriber and the subscription begin and end at the same point and are, in effect, identical. When a subscriber is closed, the subscription also ends. Here, create stands for a call to Session.createConsumer with a Topic argument, and close stands for a call to MessageConsumer.close. Any messages published to the topic between the time of the first close and the time of the second create are not consumed by the subscriber. In Figure 31–7, the subscriber consumes messages M1, M2, M5, and M6, but messages M3 and M4 are lost.

Figure 31–7 Nondurable Subscribers and Subscriptions

Diagram showing messages being lost when nondurable subscriptions
are used

With a durable subscriber, the subscriber can be closed and re-created, but the subscription continues to exist and to hold messages until the application calls the unsubscribe method. In Figure 31–8, create stands for a call to Session.createDurableSubscriber, close stands for a call to MessageConsumer.close, and unsubscribe stands for a call to Session.unsubscribe. Messages published while the subscriber is closed are received when the subscriber is created again. So even though messages M2, M4, and M5 arrive while the subscriber is closed, they are not lost.

Figure 31–8 A Durable Subscriber and Subscription

Diagram showing messages being preserved when durable
subscriptions are used

See A Java EE Application That Uses the JMS API with a Session Bean for an example of a Java EE application that uses durable subscriptions. See A Message Acknowledgment Example and the next section for examples of client applications that use durable subscriptions.

A Durable Subscription Example

The DurableSubscriberExample.java program shows how durable subscriptions work. It demonstrates that a durable subscription is active even when the subscriber is not active. The program contains a DurableSubscriber class, a MultiplePublisher class, a main method, and a method that instantiates the classes and calls their methods in sequence.

The program is in the following directory:

tut-install/javaeetutorial5/examples/jms/advanced/durablesubscriberexample/src/java/

The program begins in the same way as any publish/subscribe program: The subscriber starts, the publisher publishes some messages, and the subscriber receives them. At this point, the subscriber closes itself. The publisher then publishes some messages while the subscriber is not active. The subscriber then restarts and receives the messages.

    Before you run this program, compile and package the source file and create a connection factory that has a client ID. Perform the following steps:

  1. To compile and package the program using NetBeans IDE, follow these steps:

    1. In NetBeans IDE, choose Open Project from the File menu.

    2. In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/advanced/.

    3. Select the durablesubscriberexample folder.

    4. Select the Open as Main Project check box.

    5. Click Open Project.

    6. Right-click the project and choose Build.

    To compile and package the program using Ant, follow these steps:

    1. Go to the following directory:

      tut-install/javaeetutorial5/examples/jms/advanced/durablesubscriberexample/
      
    2. Type the following command:


      ant
      
  2. If you did not do so for A Message Acknowledgment Example, create a connection factory named jms/DurableConnectionFactory:


    ant create-durable-cf
    

To run the program using NetBeans IDE, right-click the durablesubscriberexample project and choose Run.

    To run the program from the command line, follow these steps:

  1. Go to the dist directory:


    cd dist
    
  2. Type the following command:


    appclient -client durablesubscriberexample.jar
    

The output looks something like this:


Connection factory without client ID is jms/ConnectionFactory
Connection factory with client ID is jms/DurableConnectionFactory
Topic name is jms/Topic
Starting subscriber
PUBLISHER: Publishing message: Here is a message 1
SUBSCRIBER: Reading message: Here is a message 1
PUBLISHER: Publishing message: Here is a message 2
SUBSCRIBER: Reading message: Here is a message 2
PUBLISHER: Publishing message: Here is a message 3
SUBSCRIBER: Reading message: Here is a message 3
Closing subscriber
PUBLISHER: Publishing message: Here is a message 4
PUBLISHER: Publishing message: Here is a message 5
PUBLISHER: Publishing message: Here is a message 6
Starting subscriber
SUBSCRIBER: Reading message: Here is a message 4
SUBSCRIBER: Reading message: Here is a message 5
SUBSCRIBER: Reading message: Here is a message 6
Closing subscriber
Unsubscribing from durable subscription

After you run the program, you can delete the connection factory jms/DurableConnectionFactory. Go to the directory tut-install/javaeetutorial5/examples/jms/advanced/durablesubscriberexample/ and type the following command:


ant delete-durable-cf

To delete the class and JAR files for the program using NetBeans IDE, right-click the project and choose Clean.

To delete the class and JAR files for the program using Ant, type the following:


ant clean

Using JMS API Local Transactions

You can group a series of operations into an atomic unit of work called a transaction. If any one of the operations fails, the transaction can be rolled back, and the operations can be attempted again from the beginning. If all the operations succeed, the transaction can be committed.

In a JMS client, you can use local transactions to group message sends and receives. The JMS API Session interface provides commit and rollback methods that you can use in a JMS client. A transaction commit means that all produced messages are sent and all consumed messages are acknowledged. A transaction rollback means that all produced messages are destroyed and all consumed messages are recovered and redelivered unless they have expired (see Allowing Messages to Expire).

A transacted session is always involved in a transaction. As soon as the commit or the rollback method is called, one transaction ends and another transaction begins. Closing a transacted session rolls back its transaction in progress, including any pending sends and receives.

In an Enterprise JavaBeans component, you cannot use the Session.commit and Session.rollback methods. Instead, you use distributed transactions, which are described in Using the JMS API in a Java EE Application.

You can combine several sends and receives in a single JMS API local transaction. If you do so, you need to be careful about the order of the operations. You will have no problems if the transaction consists of all sends or all receives or if the receives come before the sends. But if you try to use a request/reply mechanism, whereby you send a message and then try to receive a reply to the sent message in the same transaction, the program will hang, because the send cannot take place until the transaction is committed. The following code fragment illustrates the problem:

// Don’t do this!
outMsg.setJMSReplyTo(replyQueue);
producer.send(outQueue, outMsg);
consumer = session.createConsumer(replyQueue);
inMsg = consumer.receive();
session.commit();

Because a message sent during a transaction is not actually sent until the transaction is committed, the transaction cannot contain any receives that depend on that message’s having been sent.

In addition, the production and the consumption of a message cannot both be part of the same transaction. The reason is that the transactions take place between the clients and the JMS provider, which intervenes between the production and the consumption of the message. Figure 31–9 illustrates this interaction.

Figure 31–9 Using JMS API Local Transactions

Diagram of local transactions, showing separate transactions
for sending and consuming a message

The sending of one or more messages to one or more destinations by client 1 can form a single transaction, because it forms a single set of interactions with the JMS provider using a single session. Similarly, the receiving of one or more messages from one or more destinations by client 2 also forms a single transaction using a single session. But because the two clients have no direct interaction and are using two different sessions, no transactions can take place between them.

Another way of putting this is that the act of producing and/or consuming messages in a session can be transactional, but the act of producing and consuming a specific message across different sessions cannot be transactional.

This is the fundamental difference between messaging and synchronized processing. Instead of tightly coupling the sending and receiving of data, message producers and consumers use an alternative approach to reliability, one that is built on a JMS provider’s ability to supply a once-and-only-once message delivery guarantee.

When you create a session, you specify whether it is transacted. The first argument to the createSession method is a boolean value. A value of true means that the session is transacted; a value of false means that it is not transacted. The second argument to this method is the acknowledgment mode, which is relevant only to nontransacted sessions (see Controlling Message Acknowledgment). If the session is transacted, the second argument is ignored, so it is a good idea to specify 0 to make the meaning of your code clear. For example:

session = connection.createSession(true, 0);

The commit and the rollback methods for local transactions are associated with the session. You can combine queue and topic operations in a single transaction if you use the same session to perform the operations. For example, you can use the same session to receive a message from a queue and send a message to a topic in the same transaction.

You can pass a client program’s session to a message listener’s constructor function and use it to create a message producer. In this way, you can use the same session for receives and sends in asynchronous message consumers.

The next section provides an example of the use of JMS API local transactions.

A Local Transaction Example

The TransactedExample.java program demonstrates the use of transactions in a JMS client application. The program is in the following directory:

tut-install/javaeetutorial5/examples/jms/advanced/transactedexample/src/java/

    This example shows how to use a queue and a topic in a single transaction as well as how to pass a session to a message listener’s constructor function. The program represents a highly simplified e-commerce application in which the following things happen.

  1. A retailer sends a MapMessage to the vendor order queue, ordering a quantity of computers, and waits for the vendor’s reply:

    producer = session.createProducer(vendorOrderQueue);
    outMessage = session.createMapMessage();
    outMessage.setString("Item", "Computer(s)");
    outMessage.setInt("Quantity", quantity);
    outMessage.setJMSReplyTo(retailerConfirmQueue);
    producer.send(outMessage);
    System.out.println("Retailer: ordered " + quantity + " computer(s)");
    orderConfirmReceiver = session.createConsumer(retailerConfirmQueue);
    connection.start();
  2. The vendor receives the retailer’s order message and sends an order message to the supplier order topic in one transaction. This JMS transaction uses a single session, so you can combine a receive from a queue with a send to a topic. Here is the code that uses the same session to create a consumer for a queue and a producer for a topic:

    vendorOrderReceiver = session.createConsumer(vendorOrderQueue);
    supplierOrderProducer = session.createProducer(supplierOrderTopic);

    The following code receives the incoming message, sends an outgoing message, and commits the session. The message processing has been removed to keep the sequence simple:

    inMessage = vendorOrderReceiver.receive();
    // Process the incoming message and format the outgoing 
    // message
    ...
    supplierOrderProducer.send(orderMessage);
    ...
    session.commit();
  3. Each supplier receives the order from the order topic, checks its inventory, and then sends the items ordered to the queue named in the order message’s JMSReplyTo field. If it does not have enough in stock, the supplier sends what it has. The synchronous receive from the topic and the send to the queue take place in one JMS transaction.

    receiver = session.createConsumer(orderTopic);
    ...
    inMessage = receiver.receive();
    if (inMessage instanceof MapMessage) {
        orderMessage = (MapMessage) inMessage;
    }
    // Process message
    MessageProducer producer = 
        session.createProducer((Queue) orderMessage.getJMSReplyTo());
    outMessage = session.createMapMessage();
    // Add content to message
    producer.send(outMessage);
    // Display message contentssession.commit();
  4. The vendor receives the replies from the suppliers from its confirmation queue and updates the state of the order. Messages are processed by an asynchronous message listener; this step shows the use of JMS transactions with a message listener.

    MapMessage component = (MapMessage) message;
    ...
    orderNumber = component.getInt("VendorOrderNumber");
    Order order = Order.getOrder(orderNumber).processSubOrder(component);
    session.commit();
  5. When all outstanding replies are processed for a given order, the vendor message listener sends a message notifying the retailer whether it can fulfill the order.

    Queue replyQueue = (Queue) order.order.getJMSReplyTo();
    MessageProducer producer = session.createProducer(replyQueue);
    MapMessage retailerConfirmMessage = session.createMapMessage();
    // Format the message
    producer.send(retailerConfirmMessage);
    session.commit();
  6. The retailer receives the message from the vendor:

    inMessage = (MapMessage) orderConfirmReceiver.receive();

Figure 31–10 illustrates these steps.

Figure 31–10 Transactions: JMS Client Example

Diagram of steps in transaction example

The program contains five classes: Retailer, Vendor, GenericSupplier, VendorMessageListener, and Order. The program also contains a main method and a method that runs the threads of the Retailer, Vendor, and two supplier classes.

All the messages use the MapMessage message type. Synchronous receives are used for all message reception except for the case of the vendor processing the replies of the suppliers. These replies are processed asynchronously and demonstrate how to use transactions within a message listener.

At random intervals, the Vendor class throws an exception to simulate a database problem and cause a rollback.

All classes except Retailer use transacted sessions.

The program uses three queues named jms/AQueue, jms/BQueue, and jms/CQueue, and one topic named jms/OTopic.

    Before you run the program, do the following:

  1. In a terminal window, go to the following directory:

    tut-install/javaeetutorial5/examples/jms/advanced/transactedexample/
    
  2. Create the necessary resources using the following command:


    ant create-resources
    

    This command creates three destination resources with the names jms/AQueue, jms/BQueue, and jms/CQueue, all of type javax.jms.Queue, and one destination resource with the name jms/OTopic, of type javax.jms.Topic.

  3. To compile and package the program using NetBeans IDE, follow these steps:

    1. In NetBeans IDE, choose Open Project from the File menu.

    2. In the Open Project dialog, navigate to tut-install/javaeetutorial5/examples/jms/advanced/.

    3. Select the transactedexample folder.

    4. Select the Open as Main Project check box.

    5. Click Open Project.

    6. Right-click the project and choose Build.

    To compile and package the program using Ant, follow these steps:

    1. Go to the following directory:

      tut-install/javaeetutorial5/examples/jms/advanced/transactedexample/
      
    2. Type the following command:


      ant
      

    To run the program using NetBeans IDE, follow these steps:

  1. Right-click the transactedexample project and choose Properties.

  2. Select Run from the Categories tree.

  3. In the Arguments field, type a number that specifies the number of computers to order:

    3

  4. Click OK.

  5. Right-click the project and choose Run.

    To run the program from the command line, follow these steps:

  1. Go to the dist directory:


    cd dist
    
  2. Use a command like the following to run the program. The argument specifies the number of computers to order:


    appclient -client transactedexample.jar 3
    

The output looks something like this:


Quantity to be ordered is 3
Retailer: ordered 3 computer(s)
Vendor: Retailer ordered 3 Computer(s)
Vendor: ordered 3 monitor(s) and hard drive(s)
Monitor Supplier: Vendor ordered 3 Monitor(s)
Monitor Supplier: sent 3 Monitor(s)
  Monitor Supplier: committed transaction
  Vendor: committed transaction 1
Hard Drive Supplier: Vendor ordered 3 Hard Drive(s)
Hard Drive Supplier: sent 1 Hard Drive(s)
Vendor: Completed processing for order 1
  Hard Drive Supplier: committed transaction
Vendor: unable to send 3 computer(s)
  Vendor: committed transaction 2
Retailer: Order not filled
Retailer: placing another order
Retailer: ordered 6 computer(s)
Vendor: JMSException occurred: javax.jms.JMSException: 
Simulated database concurrent access exception
javax.jms.JMSException: Simulated database concurrent access exception
        at TransactedExample$Vendor.run(Unknown Source)
  Vendor: rolled back transaction 1
Vendor: Retailer ordered 6 Computer(s)
Vendor: ordered 6 monitor(s) and hard drive(s)
Monitor Supplier: Vendor ordered 6 Monitor(s)
Hard Drive Supplier: Vendor ordered 6 Hard Drive(s)
Monitor Supplier: sent 6 Monitor(s)
  Monitor Supplier: committed transaction
Hard Drive Supplier: sent 6 Hard Drive(s)
  Hard Drive Supplier: committed transaction
  Vendor: committed transaction 1
Vendor: Completed processing for order 2
Vendor: sent 6 computer(s)
Retailer: Order filled
  Vendor: committed transaction 2

After you run the program, you can delete the physical destinations and the destination resources. Go to the directory tut-install/javaeetutorial5/examples/jms/advanced/transactedexample/ and type the following command:


ant delete-resources

Use the following command to remove the class and JAR files:


ant clean

Using the JMS API in a Java EE Application

This section describes the ways in which using the JMS API in a Java EE application differs from using it in a stand-alone client application:

A general rule in the Java EE platform specification applies to all Java EE components that use the JMS API within EJB or web containers:

Application components in the web and EJB containers must not attempt to create more than one active (not closed) Session object per connection.

This rule does not apply to application clients.

Using @Resource Annotations in Java EE Components

When you use the @Resource annotation in an application client component, you normally declare the JMS resource static:

@Resource(mappedName="jms/ConnectionFactory")
private static ConnectionFactory connectionFactory;

@Resource(mappedName="jms/Queue")
private static Queue queue;

However, when you use this annotation in a session bean, a message-driven bean, or a web component, do not declare the resource static:

@Resource(mappedName="jms/ConnectionFactory")
private ConnectionFactory connectionFactory;

@Resource(mappedName="jms/Topic")
private Topic topic;

If you declare the resource static, runtime errors will result.

Using Session Beans to Produce and to Synchronously Receive Messages

A Java EE application that produces messages or synchronously receives them can use a session bean to perform these operations. The example in A Java EE Application That Uses the JMS API with a Session Bean uses a stateless session bean to publish messages to a topic.

Because a blocking synchronous receive ties up server resources, it is not a good programming practice to use such a receive call in an enterprise bean. Instead, use a timed synchronous receive, or use a message-driven bean to receive messages asynchronously. For details about blocking and timed synchronous receives, see Writing the Client Programs for the Synchronous Receive Example.

Using the JMS API in a Java EE application is in many ways similar to using it in a stand-alone client. The main differences are in resource management and transactions.

Resource Management

The JMS API resources are a JMS API connection and a JMS API session. In general, it is important to release JMS resources when they are no longer being used. Here are some useful practices to follow.

Transactions

Instead of using local transactions, you use container-managed transactions for bean methods that perform sends or receives, allowing the EJB container to handle transaction demarcation. Because container-managed transactions are the default, you do not have to use an annotation to specify them.

You can use bean-managed transactions and the javax.transaction.UserTransaction interface’s transaction demarcation methods, but you should do so only if your application has special requirements and you are an expert in using transactions. Usually, container-managed transactions produce the most efficient and correct behavior. This tutorial does not provide any examples of bean-managed transactions.

Using Message-Driven Beans to Receive Messages Asynchronously

The sections What Is a Message-Driven Bean? and How Does the JMS API Work with the Java EE Platform? describe how the Java EE platform supports a special kind of enterprise bean, the message-driven bean, which allows Java EE applications to process JMS messages asynchronously. Session beans allow you to send messages and to receive them synchronously but not asynchronously.

A message-driven bean is a message listener that can reliably consume messages from a queue or a durable subscription. The messages can be sent by any Java EE component (from an application client, another enterprise bean, or a web component) or from an application or a system that does not use Java EE technology.

Like a message listener in a stand-alone JMS client, a message-driven bean contains an onMessage method that is called automatically when a message arrives. Like a message listener, a message-driven bean class can implement helper methods invoked by the onMessage method to aid in message processing.

A message-driven bean, however, differs from a stand-alone client’s message listener in the following ways:

The EJB container automatically performs several setup tasks that a stand-alone client has to do:

If JMS is integrated with the application server using a resource adapter, the JMS resource adapter handles these tasks for the EJB container.

Your message-driven bean class must implement the javax.jms.MessageListener interface and the onMessage method.

It may implement a @PostConstruct callback method to create a connection, and a @PreDestroy callback method to close the connection. Typically, it implements these methods if it produces messages or does synchronous receives from another destination.

The bean class commonly injects a MessageDrivenContext resource, which provides some additional methods that you can use for transaction management.

The main difference between a message-driven bean and a session bean is that a message-driven bean has no local or remote interface. Instead, it has only a bean class.

A message-driven bean is similar in some ways to a stateless session bean: Its instances are relatively short-lived and retain no state for a specific client. The instance variables of the message-driven bean instance can contain some state across the handling of client messages: for example, a JMS API connection, an open database connection, or an object reference to an enterprise bean object.

Like a stateless session bean, a message-driven bean can have many interchangeable instances running at the same time. The container can pool these instances to allow streams of messages to be processed concurrently. The container attempts to deliver messages in chronological order when it does not impair the concurrency of message processing, but no guarantees are made as to the exact order in which messages are delivered to the instances of the message-driven bean class. Because concurrency can affect the order in which messages are delivered, you should write your applications to handle messages that arrive out of sequence.

For example, your application could manage conversations by using application-level sequence numbers. An application-level conversation control mechanism with a persistent conversation state could cache later messages until earlier messages have been processed.

Another way to ensure order is to have each message or message group in a conversation require a confirmation message that the sender blocks on receipt of. This forces the responsibility for order back on the sender and more tightly couples senders to the progress of message-driven beans.

To create a new instance of a message-driven bean, the container does the following:

To remove an instance of a message-driven bean, the container calls the @PreDestroy callback method.

Figure 31–11 shows the life cycle of a message-driven bean.

Figure 31–11 Life Cycle of a Message-Driven Bean

Diagram showing message-driven bean life cycle

Managing Distributed Transactions

JMS client applications use JMS API local transactions (described in Using JMS API Local Transactions), which allow the grouping of sends and receives within a specific JMS session. Java EE applications commonly use distributed transactions to ensure the integrity of accesses to external resources. For example, distributed transactions allow multiple applications to perform atomic updates on the same database, and they allow a single application to perform atomic updates on multiple databases.

In a Java EE application that uses the JMS API, you can use transactions to combine message sends or receives with database updates and other resource manager operations. You can access resources from multiple application components within a single transaction. For example, a servlet can start a transaction, access multiple databases, invoke an enterprise bean that sends a JMS message, invoke another enterprise bean that modifies an EIS system using the Connector architecture, and finally commit the transaction. Your application cannot, however, both send a JMS message and receive a reply to it within the same transaction; the restriction described in Using JMS API Local Transactions still applies.

Distributed transactions within the EJB container can be either of two kinds:

You can use either container-managed transactions or bean-managed transactions with message-driven beans. To ensure that all messages are received and handled within the context of a transaction, use container-managed transactions and use the Required transaction attribute (the default) for the onMessage method. This means that if there is no transaction in progress, a new transaction will be started before the method is called and will be committed when the method returns.

When you use container-managed transactions, you can call the following MessageDrivenContext methods:

If you use bean-managed transactions, the delivery of a message to the onMessage method takes place outside the distributed transaction context. The transaction begins when you call the UserTransaction.begin method within the onMessage method, and it ends when you call UserTransaction.commit or UserTransaction.rollback. Any call to the Connection.createSession method must take place within the transaction. If you call UserTransaction.rollback, the message is not redelivered, whereas calling setRollbackOnly for container-managed transactions does cause a message to be redelivered.

Neither the JMS API specification nor the Enterprise JavaBeans specification (available from http://java.sun.com/products/ejb/) specifies how to handle calls to JMS API methods outside transaction boundaries. The Enterprise JavaBeans specification does state that the EJB container is responsible for acknowledging a message that is successfully processed by the onMessage method of a message-driven bean that uses bean-managed transactions. Using bean-managed transactions allows you to process the message by using more than one transaction or to have some parts of the message processing take place outside a transaction context. In most cases, however, container-managed transactions provide greater reliability and are therefore preferable.

When you create a session in an enterprise bean, the container ignores the arguments you specify, because it manages all transactional properties for enterprise beans. It is still a good idea to specify arguments of true and 0 to the createSession method to make this situation clear:

session = connection.createSession(true, 0);

When you use container-managed transactions, you normally use the Required transaction attribute (the default) for your enterprise bean’s business methods.

You do not specify a message acknowledgment mode when you create a message-driven bean that uses container-managed transactions. The container acknowledges the message automatically when it commits the transaction.

If a message-driven bean uses bean-managed transactions, the message receipt cannot be part of the bean-managed transaction, so the container acknowledges the message outside the transaction.

If the onMessage method throws a RuntimeException, the container does not acknowledge processing the message. In that case, the JMS provider will redeliver the unacknowledged message in the future.

Using the JMS API with Application Clients and Web Components

An application client in a Java EE application can use the JMS API in much the same way that a stand-alone client program does. It can produce messages, and it can consume messages by using either synchronous receives or message listeners. See Chapter 23, A Message-Driven Bean Example for an example of an application client that produces messages. For an example of using an application client to produce and to consume messages, see An Application Example That Deploys a Message-Driven Bean on Two Servers.

The Java EE platform specification does not impose strict constraints on how web components should use the JMS API. In the Application Server, a web component can send messages and consume them synchronously but cannot consume them asynchronously.

Because a blocking synchronous receive ties up server resources, it is not a good programming practice to use such a receive call in a web component. Instead, use a timed synchronous receive. For details about blocking and timed synchronous receives, see Writing the Client Programs for the Synchronous Receive Example.

Further Information about JMS

For more information about JMS, see: