EJB Message-Driven Beans
JMS Is Asynchronous
One of the principal advantages of JMS messaging is that it's
asynchronous. In other words, a JMS client can send a
message without having to wait for a reply. Contrast this flexibility with the
synchronous messaging of Java RMI. RMI is an excellent choice for assembling
transactional components, but is too restrictive for some uses. Each time a
client invokes a bean's method it blocks the current thread until the method
completes execution. This lock-step processing makes the client dependent on
the availability of the EJB server, resulting in a tight coupling between the
client and enterprise bean.
In JMS, a client sends messages asynchronously to a destination
(topic or queue), from which other JMS clients can also receive messages. When
a JMS client sends a message, it doesn't wait for a reply; it sends the
message to a router, which is responsible for forwarding it to other clients.
Clients sending messages are decoupled from the clients receiving them;
senders are not dependent on the availability of receivers.
The limitations of RMI make JMS an attractive alternative for
communicating with other applications. Using the standard JNDI environment
naming context, an enterprise bean can obtain a JMS connection to a JMS
provider and use it to deliver asynchronous messages to other Java
applications. For example, a TravelAgent session bean can use JMS to notify
other applications that a reservation has been processed, as shown in Figure
13-1.

Figure 13-1. Using JMS with the TravelAgent EJB
|
In this case, the applications receiving JMS messages from the
TravelAgent EJB may be message-driven beans, other Java applications in the
enterprise, or applications in other organizations that benefit from being
notified that a reservation has been processed. Examples might include
business partners who share customer information or an internal marketing
application that adds customers to a catalog mailing list.
JMS enables the enterprise bean to send messages without
blocking. The enterprise bean doesn't know who will receive the message,
because it delivers the message to a virtual channel (destination) and not
directly to another application. Applications can choose to receive messages
from that virtual channel and receive notification of new reservations.
One interesting aspect of enterprise messaging is that the
decoupled asynchronous nature of the technology means that the transactions
and security contexts of the sender are not propagated to the receiver of the
message. For example, when the TravelAgent EJB sends the ticket message, it
may be authenticated by the JMS provider but its security context won't be
propagated to the JMS client that received the message. When a JMS client
receives the message from the TravelAgent EJB, it will have no idea about the
security context under which the message was sent. This is how it should be,
because the sender and receiver often operate in different environments with
different security domains.
Similarly, transactions are never propagated from the sender to
the receiver. For one thing, the sender has no idea who the receivers of the
message will be. If the message is sent to a topic there could be one receiver
or thousands; managing a distributed transaction under such ambiguous
circumstances is not tenable. In addition, the clients receiving the message
may not get it for a long time after it is sent because they are temporarily
down or otherwise unable to receive messages. One key strength of JMS is that
it allows senders and receivers to be temporally decoupled. Transactions are
designed to be executed quickly because they lock up resources; the
possibility of a long transaction with an unpredictable end is also not
tenable.
A JMS client can, however, have a distributed transaction with
the JMS provider so that it manages the send or receive operation in the
context of a transaction. For example, if the TravelAgent EJB's transaction
fails for any reason, the JMS provider will discard the ticket message sent by
the TravelAgent EJB. Transactions and JMS are covered in more detail in
Chapter 14.
JMS Messaging Models: Publish-and-Subscribe and Point-to-Point
JMS provides two types of messaging models: publish-and-subscribe and point-to-point. The JMS specification refers to these as
messaging domains. In JMS terminology,
publish-and-subscribe and point-to-point are frequently shortened to pub/sub
and p2p (or PTP), respectively. This chapter uses both the long and short
forms throughout.
In the simplest sense, publish-and-subscribe is intended for a
one-to-many broadcast of messages, while point-to-point is intended for
one-to-one delivery of messages (see Figure 13-2).

Figure 13-2. JMS messaging domains
|
Publish-and-subscribe
In publish-and-subscribe messaging, one producer can send a
message to many consumers through a virtual channel called a topic. Consumers can choose to subscribe to a topic. Any messages addressed to a topic
are delivered to all the topic's consumers. Every consumer receives a copy of
each message. The pub/sub messaging model is by and large a push-based model,
where messages are automatically broadcast to consumers without them having to
request or poll the topic for new messages.
In the pub/sub messaging model, the producer sending the message
is not dependent on the consumers receiving the message. Optionally, JMS
clients that use pub/sub can establish durable subscriptions that allow
consumers to disconnect and later reconnect and collect messages that were
published while they were disconnected.
The TravelAgent EJB in this chapter uses the pub/sub programming
model with a Topic object as a destination.
Point-to-point
The point-to-point messaging model allows JMS clients to send
and receive messages both synchronously and asynchronously via virtual
channels known as queues. The p2p messaging model has
traditionally been a pull- or polling-based model, where messages are
requested from the queue instead of being pushed to the client
automatically. (The JMS specification does not
specifically state how the p2p and pub/sub models must be implemented. Either
one may use push or pull, but at least conceptually pub/sub is push and p2p is
pull.)
A queue may have multiple receivers, but only one receiver may
receive each message. As shown earlier in Figure
13-2, the JMS provider will take care of doling out the messages among JMS
clients, ensuring that each message is consumed by only one JMS client. The
JMS specification does not dictate the rules for distributing messages among
multiple receivers.
The messaging API for p2p is similar to that used for pub/sub.
The following code listing shows how the TravelAgent EJB could be modified to
use the queue-based p2p API instead of the topic-based pub/sub model used in
the earlier example:
public TicketDO bookPassage(CreditCardDO card, double price)
throws IncompleteConversationalState {
...
TicketDO ticket = new TicketDO(customer,cruise,cabin,price);
String ticketDescription = ticket.toString();
QueueConnectionFactory factory = (QueueConnectionFactory)
jndiContext.lookup("java:comp/env/jms/QueueFactory");
Queue queue = (Queue)
jndiContext.lookup("java:comp/env/jms/TicketQueue");
QueueConnection connect = factory.createQueueConneciton();
QueueSession session = connect.createQueueSession(true,0);
QueueSender sender = session.createSender(queue);
TextMessage textMsg = session.createTextMessage();
textMsg.setText(ticketDescription);
sender.send(textMsg);
connect.close();
return ticket;
} catch(Exception e) {
throw new EJBException(e);
}
}
Which messaging model should you use?
The rationale behind the two models lies in the origin of the
JMS specification. JMS started out as a way of providing a common API for
accessing existing messaging systems. At the time of its conception, some
messaging vendors had a p2p model and some had a pub/sub model. Hence JMS
needed to provide an API for both models to gain wide industry support. The
JMS 1.0.2 specification does not require a JMS provider to support both
models. EJB 2.0 vendors, however, are required to support both messaging
models.
Almost anything that can be done with the pub/sub model can be
done with point-to-point, and vice versa. An analogy can be drawn to
developers' programming language preferences. In theory, any application that
can be written with Pascal can also be written with C. Anything that can be
written in C++ can also be written in Java. In some cases it comes down to a
matter of preference, or which model you are already familiar with.
In most cases, the decision about which model to use depends on
the distinct merits of each model. With pub/sub, any number of subscribers can
be listening on a topic, and they will all receive copies of the same message.
The publisher may not care if everybody is listening, or even if nobody is
listening. For example, consider a publisher that broadcasts stock quotes. If
any particular subscriber is not currently connected and misses out on a great
quote, the publisher is not concerned. In contrast, a point-to-point session
is likely to be intended for a one-on-one conversation with a specific
application at the other end. In this scenario, every message really matters.
The range and variety of the data the messages represent can be
a factor as well. Using pub/sub, messages are dispatched to the consumers
based on filtering that is provided through the use of specific topics. Even
when messaging is being used to establish a one-on-one conversation with
another known application, it can be advantageous to use pub/sub with multiple
topics to segregate different kinds of messages. Each kind of message can be
dealt with separately through its own unique consumer and onMessage() listener.
Point-to-point is more convenient when you want only a
particular receiver to process a given message once. This is perhaps the most
critical difference between the two models: p2p guarantees that only one
consumer processes each message. This is extremely important when messages
need to be processed separately but in tandem.
Entity and Session Beans Should Not Receive Messages
JmsClient_1 was designed to consume
messages produced by the TravelAgent EJB. Can another entity or session bean
receive those messages also? The answer is yes, but it's a really bad
idea.
Entity and session beans respond to Java RMI calls from EJB
clients and cannot be programmed to respond to JMS messages as do
message-driven beans. That means it's impossible to write a session or entity
bean that will be driven by incoming messages. The inability to make EJBs
respond to JMS messages was why message-driven beans were introduced in EJB
2.0. Message-driven beans are designed to consume messages from topics and
queues. They fill an important niche; we'll learn more about how to program
them in the next section.
It is possible to develop an entity or session bean that can
consume a JMS message from a business method, but the method must be called by
an EJB client first. For example, when the business method on the Hypothetical
EJB is called, it sets up a JMS session and then attempts to read a message
from a queue:
public class HypotheticalBean implements javax.ejb.SessionBean {
InitialContext jndiContext;
public String businessMethod() {
try{
QueueConnectionFactory factory = (QueueConnectionFactory)
jndiContext.lookup("java:comp/env/jms/QueueFactory");
Queue topic = (Queue)
jndiContext.lookup("java:comp/env/jms/Queue");
QueueConnection connect = factory.createQueueConneciton();
QueueSession session = connect.createQueueSession(true,0);
QueueReceiver receiver = session.createReceiver(queue);
TextMessage textMsg = (TextMessage)receiver.receive();
connect.close();
return textMsg.getText();
} catch(Exception e) {
throws new EJBException(e);
}
}
...
}
The QueueReceiver, which is a message
consumer, is used to proactively fetch a message from the queue. While this
has been programmed correctly, it is a dangerous operation because a call to
the QueueReceiver.receive() method blocks the
thread until a message becomes available. If a message is never delivered to
the receiver's queue, the thread will be blocked indefinitely! In other words,
if no one ever sends a message to the queue, the QueueReceiver will just sit there waiting forever.
To be fair, there are other receive()
methods that are less dangerous. For example, receive(long timeout) allows
you to specify a time after which the QueueReceiver
should stop blocking the thread and give up waiting for a message. There is
also receiveNoWait(), which checks for a message
and returns null if there are none waiting, thus
avoiding a prolonged thread block.
While the alternative receive()
methods are much safer, this is still a dangerous operation to perform. There
is no guarantee that the less risky receive()
methods will perform as expected, and the risk of programmer error (e.g.,
using the wrong receive() method) is too great.
Besides, the message-driven bean provides you with a powerful and simple
enterprise bean that is specially designed to consume JMS messages. This book
recommends that you do not attempt to consume messages from entity or session
beans.
Learning More About JMS
JMS (and enterprise messaging in general) represents a powerful
paradigm in distributed computing. In my opinion, the Java Message Service is
as important as Enterprise JavaBeans itself and should be well understood
before it is used in development.
While this chapter has provided a brief overview of JMS, it has
presented you only with enough material to prepare you for the discussion of
message-driven beans in the next section. JMS has a cornucopia of features and
details that are simply too extensive to cover in this book. To understand JMS
and how it is used, you will need to study it independently. (For a detailed treatment of JMS,
see Java Message Service by Richard Monson-Haefel and
David Chappell (O'Reilly).) Taking the time to learn JMS is well worth the effort.