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 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 33 provides examples that illustrate them.
The examples for this section are in the following directory:
The examples are in the following four subdirectories:
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
The sending program,
producer/src/java/Producer.java
, performs the following steps:
- 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;- 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;
}- 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);
}- Creates a
Connection
and aSession
:
Connection connection =
connectionFactory.createConnection();
Session session = connection.createSession(false,
Session.AUTO_ACKNOWLEDGE);- Creates a
MessageProducer
and aTextMessage
:
MessageProducer producer = session.createProducer(dest);
TextMessage message = session.createTextMessage();- 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);
}- 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.
- Closes the connection in a
finally
block, automatically closing the session andMessageProducer
:
} finally {
if (connection != null) {
try {
connection.close();
} catch (JMSException e) {
}
}
}The receiving program,
synchconsumer/src/java/SynchConsumer.java
, performs the following steps:
- Injects resources for a connection factory, queue, and topic.
- Assigns either the queue or topic to a destination object, based on the specified destination type.
- Creates a
Connection
and aSession
.- Creates a
MessageConsumer
:
consumer = session.createConsumer(dest);
- Starts the connection, causing message delivery to begin:
connection.start();
- 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 thewhile
loop and stops receiving messages after the control message arrives.- Closes the connection in a
finally
block, automatically closing the session andMessageConsumer
.The
receive
method can be used in several ways to perform a synchronous receive. If you specify no arguments or an argument of0
, the method blocks indefinitely until a message arrives: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 indefinitewhile
loop to receive messages, callingreceive
with a timeout argument. CallingreceiveNoWait
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 (page 28).
Creating JMS Administered Objects
Creating the JMS administered objects for this section involves the following:
If you built and ran the
SimpleMessage
example in Chapter 23 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:
Note: The first time you run a command that creates JMS resources, the command may take a long time because the Application Server is initializing the JMS provider.
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:
Compiling and Packaging the Clients
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.
Note: It is possible to run a JMS client program as a stand-alone Java class instead of packaging it in an application client JAR file. For instructions, see the task "To access a JMS resource from a stand-alone client" in the Sun Java System Application Server Platform Edition 9 Developer's Guide.
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 thebuild/jar
directory. Then the targets use thejar
command to package the class file and the manifest file in an application client JAR file.To compile and package the
Producer
andSynchConsumer
examples, do the following:The targets place the application client JAR file in the
dist
directory for each example.Running the Clients
You run the sample programs using the
appclient
command. Each of the programs takes one or more command-line arguments: a destination type and, forProducer
, a number of messages.Run the clients as follows.
- In a terminal window, go to the
producer/dist
directory:
cd ../producer/dist
- 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 3The messages are now in the queue, waiting to be received.
- In the same window, go to the
synchconsumer/dist
directory:
cd ../../synchconsumer/dist
- 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- 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
- In a different terminal window, run the
Producer
program.
cd <INSTALL>/javaeetutorial5/examples/jms/simple/producer/dist
appclient -client producer.jar queue 3When the messages have been sent, the
SynchConsumer
program receives them and exits.- 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- 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.
- Run the
Producer
program again. Now theSynchConsumer
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 3Because 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
The sending program is
producer/src/javaProducer.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
orQ
to stop the program.The receiving program,
asynchconsumer/src/java/AsynchConsumer.java
, performs the following steps:
- Injects resources for a connection factory, queue, and topic.
- Assigns either the queue or topic to a destination object, based on the specified destination type.
- Creates a
Connection
and aSession
.- Creates a
MessageConsumer
.- Creates an instance of the
TextListener
class and registers it as the message listener for theMessageConsumer
:
listener = new TextListener();
consumer.setMessageListener(listener);- Starts the connection, causing message delivery to begin.
- Listens for the messages published to the destination, stopping when the user types the character
q
orQ
:
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());
}
}- Closes the connection, which automatically closes the session and
MessageConsumer
.The message listener,
asynchconsumer/src/java/TextListener.java
, follows these steps:
- When a message arrives, the
onMessage
method is called automatically.- The
onMessage
method converts the incoming message to aTextMessage
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.
Compiling and Packaging the AsynchConsumer Client
To compile and package the
AsynchConsumer
example, do the following: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
As before, you run the programs using the
appclient
command.Run the clients as follows.
- Run the
AsynchConsumer
program, specifying the topic
destination type.
cd dist
appclient -client asynchconsumer.jar topicThe program displays the following lines and appears to hang:
Destination type is topic
To end program, type Q or q, then <return>- 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 3In 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 TextMessageThe last line appears because the program has received the non-text control message sent by the
Producer
program.- Type
Q
orq
to stop the program.- 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- 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- Type
Q
orq
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 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
To create a
QueueBrowser
for a queue, you call theSession.createBrowser
method with the queue as the argument. You obtain the messages in the queue as anEnumeration
object. You can then iterate through theEnumeration
object and display the contents of each message.The
messagebrowser/src/java/MessageBrowser.java
program performs the following steps:
- Injects resources for a connection factory and a queue.
- Creates a
Connection
and aSession
.- Creates a
QueueBrowser
:
QueueBrowser browser = session.createBrowser(queue);
- Retrieves the Enumeration that contains the messages:
Enumeration msgs = browser.getEnumeration();
- 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);
}
}- 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.
Compiling and Packaging the MessageBrowser Client
To compile and package the
MessageBrowser
example, do the following: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
As before, you run the sample programs using the
appclient
command. You may want to use two terminal windows.Run the clients as follows.
- Go to the
producer/dist
directory.- 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- Go to the
messagebrowser/dist
directory.- 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: nullThe first message is the
TextMessage
, and the second is the non-text control message.- Go to the
synchconsumer/dist
directory.- 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 1Running 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 theSynchConsumer
program on another system,jupiter
. Before you can do so, you need to perform these tasks:
Note: A limitation in the JMS provider in the Application Server may cause a runtime failure to create a connection to systems that use the Dynamic Host Configuration Protocol (DHCP) to obtain an IP address. You can, however, create a connection from a system that uses DHCP to a system that does not use DHCP. In the examples in this tutorial,
earth
can be a system that uses DHCP, andjupiter
can be a system that does not use DHCP.
Before you begin, start the server on both systems:
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
<
INSTALL
>/javaeetutorial5/examples/bp-project/build.properties
and change the location of thejavaee.home
property each time you run a program on a different system.To create a new connection factory on
jupiter
, perform these steps:The
create-local-factory
target, defined in thebuild.xml
file for theProducer
example, creates a connection factory namedjms/JupiterConnectionFactory
.To create a new connection factory on
earth
that points to the connection factory onjupiter
, perform these steps:The
create-remote-factory
target, defined in thebuild.xml
file for theProducer
example, also creates a connection factory namedjms/JupiterConnectionFactory
. In addition, it sets theAddressList
property for this factory to the name of the remote system.If you have already been working on either
earth
orjupiter
, you have the queue on one system. On the system that does not have the queue, type the following command:When you run the programs, they will work as shown in Figure 32-6. The program run on
earth
needs the queue onearth
only in order that the resource injection will succeed. The connection, session, and message producer are all created onjupiter
using the connection factory that points tojupiter
. The messages sent fromearth
will be received onjupiter
.
Figure 32-6 Sending Messages from One System to Another
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
fromearth
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:
- Open the file
<
INSTALL
>javaeetutorial5/examples/jms/simple/producer/src/java/Producer.java
in a text editor.- Find the following line:
@Resource(mappedName="jms/ConnectionFactory")
- Change the line to the following:
@Resource(mappedName="jms/JupiterConnectionFactory")
- Recompile and repackage the
Producer
example:
ant
- Open the file
<
INSTALL
>javaeetutorial5/examples/jms/simple/synchconsumer/src/java/SynchConsumer.java
in a text editor.- Repeat steps 2 and 3.
- Recompile and repackage the
SynchConsumer
example:
ant
- On
earth
, go to theproducer/dist
directory and runProducer
:
appclient -client producer.jar queue 3
- On
jupiter
, go to thesynchconsumer/dist
directory and runSynchConsumer
:
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 Java EE Server (page 1075) and An Application Example That Deploys a Message-Driven Bean on Two Java EE Servers (page 1080).
Deleting the Connection Factory and Stopping the Server
You will need the connection factory
jms/JupiterConnectionFactory
in Chapter 33. However, if you wish to delete it, go to theproducer
directory and type the following command: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. However, we recommend that you keep them, because they will be used in most of the examples in Chapter 33. After you have created them, they will be available whenever you restart the Application Server.Delete the class and JAR files for each program as follows:
You can also stop the Application Server, but you will need it to run the sample programs in the next section.