Our email application will be modeled using the PTP domain. When an email is placed on the queue we only want one receiver, else the email could be sent out multiple times.
The item that is put on the queue is a JMS Message. This is a generic type that has message headers and contents. There are various subtypes of the JMS Message. For example,
This message holds a String. You manipulate the
This message stores a Serializable object. You manipulate the
This message holds name/value pairs. You manipulate the
This message is a stream of bytes. This can be used for wrapping existing message formats.
This message allows you to work with a stream of primitives
We will use a
MapMessage in our example, as it is one
way that we can place email header information and the email body,
into the message.
EJB 2.0: Message Driven Bean
Now we have talked about the fundamentals of JMS, we can talk about the new component type that is in the EJB 2.0 specification. (The EJB 2.0 spec if a Public Draft at the time of this article, although the Message Driven Bean part is not rumored to change.)
Let's review the design so far. We have a sender that puts messages on the queue, and a receiver that will read the messages and use that information to send out email. This receiver could be a program that starts up, subscribes to the "EmailQueue", and deals with the messages that come in. Instead of doing this every time we need a receiver, it would be nice to have a component architecture that allows for the concurrent processing of a stream of messages, is transactionally aware, and takes care of the infrastructure code, letting us work on the business logic. This is where Message Driven Beans come in. A Message Driven Bean (MDB) is simply a JMS message consumer. A client cannot access a MDB directly (as you do with session and entity beans). Your only interface to the MDB is by sending a JMS message to the destination of which the MDB is listening. To allow reuse, as with the other EJBs, a lot of information is provided in the EJB deployment descriptor. This means we do not worry about where we go to get the message (whether to a queue or topic), we just write the onMessage(Message msg) code to deal with the message.
We have talked about JMS, and Message Driven Beans; now it's time to build our example.
Email Application Steps
We will walk through the following steps to get the email application working.
- Setup an "Email Queue" message queue in the JMS server
- Write an email client, responsible for sending a java message to the email queue
- Write a message driven bean that consumes these messages, and sends emails using their contents
- Write the deployment descriptors for the MDB
- Package the code
The code that we will create is
The EmailClient that will plug a message on the queue
The Message Driven Bean that consumes the JMS messages from the client, and then uses the EmailHelper to send out an email
A helper class that has the static method
Step One: Setup an email message queue
This step will depend on your messaging server (e.g. IBM MQSeries,
SonicMQ, and so on). We need to setup a JMS Queue. I will use the
EmailQueue, which will be used in both the client
(sender) and Message Driven Bean (receiver) deployment descriptor.
Step Two: Writing the email client
Now we will write our client (JMS sender). This client will take in
information about the email that we will eventually send out. The
main() method takes in the arguments from the command
line, builds a Hashtable (any Map would work), and calls the
sendmail(Map m) method. The sendmail method takes this
information, builds a
MapMessage from it, and then sends
the message to the
EmailQueue queue via
The majority of the work is done in the constructor. It is there that we do all of the JMS work. The constructor does the following:
- We connect to the JNDI service via the
- We lookup a connection factory for a queue [
- We create a queue connection to our JMS server [
- We create a JMS session, which is a context for producing and consuming
- We lookup the queue that we will use to send the messages [
- Finally, we create a sender, that can send messages to the queue that we just
looked up, using the session that we created earlier [
That's it. Now we have a client that can send messages to our queue. We can compile and run this client via:
% java com.customware.client.EmailClient email@example.com
firstname.lastname@example.org "my subject" "my email body"
We have a client. Now we build our consumer.
Step Three: Write a Message Driven Bean
When writing session, or entity beans, you have to write a remote interface, home interface, and the bean class (and optionally a Primary Key class for an entity bean). With Message Driven Beans we only have the bean class, as there is no "client" that interfaces with the bean.
A Message Driven Bean must implement two interfaces:
This is the JMS interface that provides the
This is the EJB interface that contains the EJB lifecycle methods:
If you look at the code in
EmailMDB.java you will see
that the first few methods implement the
MessageDrivenBean interface. All we are doing in these
methods is printing that they are called. The
setMessageDrivenContext() method saves away the context
to an instance variable, in case we want to query it later. Nine times
out of ten, this is what you would do. The last method is the callback
onMessage(Message msg) from the
MessageListener interface. This is where we consume the
message and deal with it. First, we create a
by casting the message that was given to us. Then we loop through the
name-value pairs in the map message, and plug in their values to a
standard Hashtable. Note the methods that we use to go through the
// Get the names out of the Map Message Enumeration mapnames = mapmessage.getMapNames(); // Get the string out of the map message, giving the key String val = mapmessage.getString(key);
Finally, we call the
which takes care of sending the email for us.
That was pretty easy. That is the great thing about MDBs. We don't have lots of ugly JMS code (like we had in the client, or like we would have if we wrote the consumer from scratch); in fact how does the MDB know where to get these messages? For that, we go to the deployment descriptors.