EJB Message-Driven Beans
Pages: 1, 2, 3, 4, 5, 6, 7
JMS as a Resource
JMS is a standard vendor-neutral API that is part of the J2EE platform and can be used to access enterprise messaging systems. Enterprise messaging systems (a.k.a. message-oriented middleware) facilitate the exchange of messages among software applications over a network. JMS is analogous to JDBC: whereas JDBC is an API that can be used to access many different relational databases, JMS provides the same vendor-independent access to enterprise messaging systems. Many enterprise messaging products currently support JMS, including IBM's MQSeries, BEA's WebLogic JMS service, Sun Microsystems' iPlanet Message Queue, and Progress' SonicMQ, to name a few. Software applications that use the JMS API for sending or receiving messages are portable across brands of JMS vendors.
Java applications that use JMS are called JMS clients, and the messaging system that handles routing and delivery of messages is called the JMS provider. A JMS application is a business system composed of many JMS clients and, generally, one JMS provider.
A JMS client that sends a message is called a producer, while a JMS client that receives a message is called a consumer. A single JMS client can be both a producer and a consumer. When we use the terms consumer and producer, we mean a JMS client that receives messages or sends messages, respectively.
In EJB, enterprise beans of all types can use JMS to send messages to various destinations. Those messages are consumed by other Java applications or message-driven beans. JMS facilitates sending messages from enterprise beans by using a messaging service, sometimes called a message broker or router. Message brokers have been around for a couple of decades--the oldest and most established being IBM's MQSeries--but JMS is fairly new and is specifically designed to deliver a variety of message types from one Java application to another.
Reimplementing the TravelAgent EJB with JMS
We can modify the TravelAgent EJB developed in Chapter 12 so
that it uses JMS to alert some other Java application that a reservation has
been made. The following code shows how to modify the bookPassage() method so that the TravelAgent EJB will
send a simple text message based on the description information from the TicketDO object:
public TicketDO bookPassage(CreditCardDO card, double price)
throws IncompleteConversationalState {
if (customer == null || cruise == null || cabin == null) {
throw new IncompleteConversationalState();
}
try {
ReservationHomeLocal resHome = (ReservationHomeLocal)
jndiContext.lookup("java:comp/env/ejb/ReservationHomeLocal");
ReservationLocal reservation =
resHome.create(customer, cruise, cabin, price, new Date());
Object ref = jndiContext.lookup
("java:comp/env/ejb/ProcessPaymentHomeRemote");
ProcessPaymentHomeRemote ppHome = (ProcessPaymentHomeRemote)
PortableRemoteObject.narrow(ref, ProcessPaymentHomeRemote.class);
ProcessPaymentRemote process = ppHome.create();
process.byCredit(customer, card, price);
TicketDO ticket = new TicketDO(customer,cruise,cabin,price);
String ticketDescription = ticket.toString();
TopicConnectionFactory factory = (TopicConnectionFactory)
jndiContext.lookup("java:comp/env/jms/TopicFactory");
Topic topic = (Topic)
jndiContext.lookup("java:comp/env/jms/TicketTopic");
TopicConnection connect = factory.createTopicConnection();
TopicSession session = connect.createTopicSession(true,0);
TopicPublisher publisher = session.createPublisher(topic);
TextMessage textMsg = session.createTextMessage();
textMsg.setText(ticketDescription);
publisher.publish(textMsg);
connect.close();
return ticket;
} catch(Exception e) {
throw new EJBException(e);
}
}
We needed to add a lot of new code to send a message. However, while it may look a little overwhelming at first, the basics of JMS are not all that complicated.
TopicConnectionFactory and Topic
In order to send a JMS message we need a connection to the JMS
provider and a destination address for the message. The connection to the JMS
provider is made possible by a JMS connection factory; the destination address
of the message is identified by a Topic object.
Both the connection factory and the Topic object
are obtained from the TravelAgent EJB's JNDI ENC:
TopicConnectionFactory factory = (TopicConnectionFactory)
jndiContext.lookup("java:comp/env/jms/TopicFactory");
Topic topic = (Topic)
jndiContext.lookup("java:comp/env/jms/TicketTopic");
The TopicConnectionFactory in JMS is similar in function to
the DataSource in JDBC. Just as the DataSource provides a JDBC connection to a database, the
TopicConnectionFactory provides a JMS connection to
a message router. (This analogy is not perfect. One
might also say that the TopicSession is analogous
to the DataSource, since both represent
transaction-resources connections.)
The Topic object itself represents a
network-independent destination to which the message will be addressed. In
JMS, messages are sent to destinations--either topics or queues--instead of
directly to other applications. A topic is analogous to an email list or
newsgroup; any application with the proper credentials can receive messages
from and send messages to a topic. When a JMS client receives messages from a
topic, the client is said to subscribe to that topic.
JMS decouples applications by allowing them to send messages to each other
through a destination, which serves as virtual channel.
JMS also supports another destination type, called a Queue. The difference between topics and queues is
explained in more detail later.
TopicConnection and TopicSession
The TopicConnectionFactory is used to
create a TopicConnection, which is an actual
connection to the JMS provider:
TopicConnection connect = factory.createTopicConnection();
TopicSession session = connect.createTopicSession(true,0);
Once a TopicConnection is obtained, it can be used to
create a TopicSession. A TopicSession allows the Java developer to group the
actions of sending and receiving messages. In this case you will need only a
single TopicSession. However, having more than one
TopicSession object is frequently helpful: if you
wish to produce and consume messages using multithreading, a different Session needs to be created by each thread accessing that
thread. This is because JMS Session objects use a
single-threaded model, which prohibits concurrent accessing of a single Session from multiple threads. The thread that creates a
TopicSession is usually the thread that uses that
Session's producers and consumers (i.e., TopicPublisher and TopicSubscriber objects). If you wish to produce and
consume messages using multithreading, a different Session should be created and used by each thread.
The createTopicSession() method has
two parameters:
createTopicSession(boolean transacted, int acknowledgeMode)
According to the EJB 2.0 specification, these arguments are
ignored at runtime because the EJB container manages the transaction and
acknowledgment mode of any JMS resource obtained from the JNDI ENC. The
specification recommends that developers use the arguments true for transacted and 0 for acknowlegeMode, but
since they are supposed to be ignored, it should not matter what you use.
Unfortunately, not all vendors adhere to this part of the specification. Some
vendors ignore these parameters while some do not. Consult your vendor's
documentation to determine the proper values for these parameters in both
container-managed and bean-managed transactions.
It's good programming practice to close a TopicConnection after its been used, in order to conserve
resources:
TopicConnection connect = factory.createTopicConnection();
...
connect.close();