EJB Message-Driven Beans
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();