Chapter 4. Messaging and developing MDBs – EJB 3 in Action

Chapter 4. Messaging and developing MDBs

This chapter covers

  • Introducing messaging concepts
  • Building a message producer using JMS
  • Developing MDBs

In this chapter we’ll take a closer look at developing message-driven beans (MDBs) as well as provide you with an overview of the concepts and technologies these powerful EJB 3 components build on. First we’ll introduce you to basic messaging concepts, and then we’ll explore the Java Messaging Service (JMS) by creating a message producer. Finally, we’ll take a look at MDBs, the EJB 3 answer to messaging.

You should gain an understanding of messaging and JMS before diving into MDB for two reasons. First, most MDBs you’ll encounter are glorified JMS message consumers implementing JMS interfaces (such as javax.jms.MessageListener) and using JMS components (such as javax.jms.Message). Second, for most solutions with MDB your messaging will involve much more than simply consuming messages. For the simplest of these tasks, such as sending messages, you’ll have to understand JMS. This chapter assumes a basic familiarity with JMS and we offer only a brief description of it.

If you’re comfortable with messaging and JMS, feel free to skip to the sections on MDBs. It is good to reinforce what you know from time to time, though, so you just might want to quickly jog through the first few sections with us anyway.

4.1. Messaging concepts

When we talk about messaging in the Java EE context, what we really mean is the process of loosely coupled, asynchronous communication between system components. Most communication between components is synchronous, such as simple method invocation or Java RMI. In both cases, the invoker and the invocation target have to be present for the communication to succeed. Synchronous communication also means that the invoker must wait for the target to complete the request for service before proceeding.

As an analogy, you’re communicating synchronously when you (the invoker) call and talk to someone over the phone. But what if the person (the invocation target) isn’t available? If possible, you leave a voicemail message. The voicemail service makes the communication asynchronous by storing your message so that the receiver can listen to it later and respond. Message-oriented middleware (MOM) enables messaging in much the same way that a voicemail service does—by acting as the middleman between a message sender and the receiver so that they don’t have to be available simultaneously. In this section, we briefly introduce MOM, show how messaging is used in our ActionBazaar application, and examine popular messaging models.

4.1.1. Message-oriented middleware

Message-oriented middleware is software that enables asynchronous messages between system components. When a message is sent, the software stores the message in a location specified by the sender and acknowledges receipt immediately. The message sender is called a producer, and the location where the message is stored is called a destination. At a later point in time, any software component interested in messages at that particular destination can retrieve currently stored messages. The software components receiving the messages are called the message consumers.Figure 4.1 depicts the various components of MOM.

Figure 4.1. Basic MOM message flow. When the producer sends a message to the software, it is stored immediately and later collected by the consumer. Looks a lot like e-mail, doesn’t it?

MOM is not a new concept by any means. MOM products include IBM Web-Sphere MQ, TIBCO Rendezvous, SonicMQ, ActiveMQ, and Oracle Advanced Queuing.

To flesh out messaging concepts a bit more, let’s explore a problem in the ActionBazaar application. We’ll continue working on this problem as we progress through the chapter.

4.1.2. Messaging in ActionBazaar

As an additional source of revenue, ActionBazaar will list items for bid when the company is able to find good bulk deals through its extensive purchasing network. These items, displayed on the site as “ActionBazaar Specials,” come with “complete satisfaction” guarantees. ActionBazaar automatically ships these items from their warehouse to winning bidders as soon as they order them. When ActionBazaar started as a two-person Internet operation, Joe and John, the two founders, made a sweet deal with Turtle Shipping Company’s founder Dave Turtle. As a part of the deal, Joe and John agreed to ship with Turtle for a few years.

As soon as a user places an order for an “ActionBazaar Special,” a shipping request is sent to the Turtle system via a business-to-business (B2B) connection, as shown in figure 4.2. The order confirmation page loads only after Turtle confirms receipt. Now that the number of ActionBazaar customers has gone through the roof, the slow Turtle servers and B2B connection simply cannot keep up and completing a shipping order takes what seems like forever. To make matters worse, the Turtle server occasionally goes down, causing orders to fail altogether.

Figure 4.2. ActionBazaar ordering before MOM is introduced. Slow B2B processing is causing customer dissatisfaction.

Taking a closer look at things, we see that we could make the forwarding process of the shipping request asynchronous and solve this problem. Instead of communicating directly with the Turtle server, the ActionBazaar ordering process could send a message containing the shipping request to MOM, as depicted in figure 4.3. As soon as the message is stored in MOM, the order can be confirmed without making the user wait. At a later point in time, the Turtle server could request pending shipping request messages from MOM and process them at its own pace.

Figure 4.3. ActionBazaar ordering after MOM is introduced. Messaging enables both fast customer response times and reliable processing.

In this case, the most obvious advantage MOM is offering is an increase in reliability. The reliability stems from not insisting that both the ActionBazaar and Turtle servers be up and running at the same time. Also, the servers are not expected to function at the same processing rate. In the most extreme case, even if the Turtle server is down at any given time the shipping request is not lost and is simply delivered later. Another significant advantage of messaging is loosely coupled system integration. We could, if we wanted to, easily switch from the Turtle Shipping Company to O’Hare Logistics once our current contract runs out. Note how different this is from having to know the exact interface details of the Turtle servers for synchronous communication technologies like RMI or even remote session beans.

So far we’ve described a particular form of messaging known as point-to-point to explain basic messaging concepts. This is a good time to move away from this simplification and fully discuss messaging models.

4.1.3. Messaging models

A messaging model is simply a way of messaging when a number of senders and consumers are involved. It will be more obvious what this means as we describe each model. Two popular messaging models are standardized in Java EE: point-to-point (PTP) messaging and publish-subscribe messaging. We’ll discuss each of these messaging models next.

Point-to-point

You can probably guess from the names of the messaging models how they function. In the PTP scheme, a single message travels from a single producer (point A) to a single consumer (point B). PTP message destinations are called queues. Note that PTP doesn’t guarantee that messages are delivered in any particular order—the term queue is more symbolic than anything else. Also, if more than one potential receiver exists for a message, a random receiver is chosen, as figure 4.4 shows. The classic message-in-a-bottle story is a good analogy of PTP messaging. The message in a bottle is set afloat by the lonely castaway (the producer). The ocean (the queue) carries the message to an anonymous beach dweller (the consumer) and the message can only be “found” once.

Figure 4.4. The PTP messaging model with one producer and two consumers

The ActionBazaar shipping request forwarding problem is an excellent candidate for the PTP model, as we want to be guaranteed that the message is received once and only once.

Publish-subscribe (pub-sub)

Publish-subscribe messaging is much like posting to an Internet newsgroup. As shown in figure 4.5, a single producer produces a message that is received by any number of consumers who happen to be connected to the destination at the time. Much like Internet postings, the message destination in this model is called a topic and a consumer is called a subscriber.

Figure 4.5. The publish-subscribe messaging model with one producer and three consumers. Each topic subscriber receives a copy of the message.

Pub-sub messaging works particularly well in broadcasting information across systems. For example, it could be used to broadcast a system maintenance notification several hours before an outage to all premium sellers whose systems are directly integrated with ActionBazaar and who are listening at the moment.

At this point, you should have a good conceptual foundation of messaging and are perhaps eager to get a taste of some code. Next, we take a brief look at JMS and implement the ActionBazaar message producer for sending the message.


The request-reply model

In the ActionBazaar example, you might want a receipt confirmation from Turtle once they have the shipping request you sent to the queue.

A third kind of model called request-reply comes in handy in these kinds of situations. In this model, we give the message receiver enough information so that they might “call us back.” This is known as an overlay model because it is typically implemented on top of either the PTP or pub-sub models.

For example, in the PTP model the sender specifies a queue to be used to send a reply to (in JMS, this is called the reply to queue) as well as a unique ID shared by both the outgoing and incoming messages (known as the correlation ID in JMS). The receiver receives the message and sends a reply to the reply queue, copying the correlation ID. The sender receives the message in the reply queue and determines which message received a reply by matching the correlation ID.


4.2. Introducing Java Messaging Service

In this section we provide an overview to JMS API by building a basic message producer. JMS is a deceptively simple and small API to a very powerful technology. The JMS API is to messaging what the Java Database Connectivity (JDBC) API is to database access. JMS provides a uniform, standard way of accessing MOM in Java and is therefore an alternative to using product-specific APIs. With the exception of Microsoft Message Queuing (MSMQ), most major MOM products support JMS.

The easiest way to learn JMS might be by looking at code in action. We’re going to explore JMS by first developing the ActionBazaar code that sends out the shipping request. We develop a message producer using JMS and learn about structure of the message interface; then in the next section, we develop the message consumer using MDBs.

4.2.1. Developing the JMS message producer

As we described in our scenario in section 4.1.2, when a user places an order for an “ActionBazaar Special,” a shipping request is sent to a queue shared between ActionBazaar and Turtle. The code in listing 4.1 sends the message out and could be part of a method in a simple Java object invoked by the ActionBazaar application. All relevant shipping information—such as the item number, shipping address, shipping method, and insurance amount—is packed into a message and sent out to ShippingRequestQueue.

Listing 4.1. JMS code that sends out shipping requests from ActionBazaar

As we explain each logical step of this code in the following sections, we’ll go through a large subset of the JMS API components and see usage patterns. Note that for simplicity we have removed the code for exception handling.

Retrieving the connection factory and destination

In JMS, administrative objects are similar to JDBC javax.sql.DataSource objects. These are resources that are created and configured outside the code and stored in JNDI. JMS has two administrative objects: javax.jms.ConnectionFactory and javax.jms.Destination, both of which we use in listing 4.1. We then retrieve the connection factory using dependency injection with the @Resource annotation, and the connection factory encapsulates all the configuration information needed to connect to MOM. We also inject the queue to forward the shipping request to, aptly named, ShippingRequestQueue. With EJB 3, using resources is much easier; you don’t have to deal with the complexity of JNDI or configure resource references in deployment descriptors. Chapter 5 discusses dependency injection in greater detail.

The next step in listing 4.1 is creating a connection to MOM and getting a new JMS session.

Opening the connection and session

The javax.jms.Connection object represents a live MOM connection, which we create using the createConnection method of the connection factory. Connections are thread-safe and designed to be sharable because opening a new connection is resource intensive. A JMS session (javax.jms.Session), on the other hand, provides a single-threaded, task-oriented context for sending and receiving messages. We create a session from the connection using the createSession method. The first parameter of the method specifies whether the session is transactional. We’ve decided that our session should be transactional and therefore set the parameter to true. This means that the requests for messages to be sent won’t be realized until either the session’s commit method is called or the session is closed. (If the session isn’t transactional, messages will be sent as soon as the send method is invoked.) The second parameter of the createSession method specifies the acknowledge mode and only has an effect for nontransactional sessions receiving messages, which we’ll discuss later. Having set up the session, we are now ready to take on the meat of the matter: sending the message.

Preparing and sending the message

The session is not directly used for sending or receiving messages (we could argue that having it do so would simplify the JMS API). Instead, a javax.jms.MessageProducer needed to send messages to the shipping request queue is constructed using the session’s createProducer method. Then we create and populate the javax.jms.Message to be sent. In our example, we send the Serializable Java object ShippingRequest to Turtle, so the most appropriate message type for us is javax.jms.ObjectMessage (which we create using the createObjectMessage method). We then create an instance of the ShippingRequest object and set the item number, shipping address, shipping method, and insurance amount fields. Once ShippingRequest is set up, we set it as the payload of the message using set-Object. Finally, we instruct the message producer to send the message out using the send method.

Releasing resources

A large number of resources are allocated under the hood for both the session and connection objects, so it is important to explicitly close both once we’ve finished with them, as we do with

session.close();
connection.close();

This step is even more important in our case since no messages are sent out until our transactional session is committed when we close the session.

If all goes well, a message containing the shipping request winds up in the queue. Before we look at the message consumer code that receives this message, let’s discuss the javax.jms.Message object in a little more detail.

4.2.2. The JMS message interface

The Message interface standardizes what is exchanged across JMS and is an extremely robust data encapsulation mechanism. As figure 4.6 shows, a JMS message has the following parts: the message header, message properties, and the message body, each of which is detailed in the sections that follow.

Figure 4.6. Anatomy of a message. A JMS message has a header, properties, and a body.

A good analogy for JMS messages is mailing envelopes. Let’s see how this analogy fits next.

Message headers

Headers are name-value pairs common to all messages. In our envelope analogy, the message header is the information on an envelope that is pretty standard: the to and from addresses, postage, and postmark. For example, the JMS message version of a postmark is the JMSTimestamp header. MOM sets this header to the current time when the message is sent.

Here are some other commonly used JMS headers:

  • JMSCorrelationID
  • JMSReplyTo
  • JMSMessageID
Message properties

Message properties are just like headers, but are explicitly created by the application instead of being standard across messages. In the envelope analogy, if you decide to write “Happy Holidays” on the envelope to let the receiver know the envelope contains a gift or note, the text is a property instead of a header. In the ActionBazaar example, one way to mark a shipping request as fragile would be to add a boolean property called Fragile and set it to true. The code to do this would look like this:

message.setBooleanProperty("Fragile", true);

A property can be a boolean, byte, double, float, int, long, short, String, or Object.

Message body

The message body contains the contents of the envelope; it is the payload of the message. What you’re trying to send in the body determines what message type you should use. In listing 4.1, we chose javax.jms.ObjectMessage because we were sending out the ShippingRequest Java object. Alternatively, we could have chosen to send BytesMessage, MapMessage, StreamMessage, or TextMessage. Each of these message types has a slightly different interface and usage pattern. There are no hard-and-fast rules dictating the choice of message types. Explore all the choices before making a decision for your application.


The Spring JmsTemplate

Spring’s JmsTemplate greatly simplifies common JMS tasks like sending messages by automating generic code. Using JmsTemplate, our entire message producer code could be reduced to a few lines. This is a great way to take care of tasks, as long as you aren’t doing anything too complicated such as using temporary queues, JMS headers, and properties.

At the time of this writing, Spring doesn’t have very robust asynchronous messageprocessing capabilities when compared to MDB. Any future MDB-like features in Spring are likely to utilize the relatively arcane JCA container, which leaves room for a great Spring/EJB 3 integration case.


We just finished reviewing most of the major parts of JMS that you need to send and use with MDB. While full coverage of JMS is beyond the scope of this chapter, we encourage you to fully explore the fascinating JMS API by visiting http://java.sun.com/products/jms/docs.html. In particular, you should explore how JMS message consumers work.

Having taken a closer look at JMS messages, the time is right to look at the Turtle server message consumer, which we’ll build using an MDB.

4.3. Working with message-driven beans

We’ll now build on our brief coverage of MDBs in chapters 1 and 2 and explore MDBs in detail, why you should consider using them, and how to develop them. We’ll also discuss some best practices as well as pitfalls to avoid when developing MDBs.

Message-driven beans are EJB components designed to consume the asynchronous messages we’ve been discussing. Although MDBs are intended to handle many different kinds of messages (see the sidebar “JCA Connectors and Messaging”), we’ll primarily focus on MDBs that process JMS messages because most enterprise applications use JMS. From this perspective, you might ask why we’d need to employ EJBs to handle the task of consuming messages at all when we could use the code we just developed for the JMS message consumer. We’ll address this question next. We’ll develop a simple message consumer application using MDBs and show you how to use the @MessageDriven annotation. You’ll also learn more about the MessageListener interface, activation configuration properties, and the MDB lifecycle.


JCA connectors and messaging

Although by far JMS is the primary messaging provider for MDBs, as of EJB 2.1 it is not the only one. Thanks to the Java EE Connector Architecture (JCA), MDBs can receive messages from any enterprise information system (EIS), such as PeopleSoft HR or Oracle Manufacturing, not just MOMs that support JMS.

Suppose you have a legacy application that needs to send messages to an MDB. You can do this by implementing a JCA-compliant adapter/connector that includes support for message inflow contract. Once your JCA resource adapter or connector is deployed to a Java EE container, you can use the message inflow contract to have an asynchronous message delivered to an endpoint inside the container. A JCA endpoint is essentially the same as a JMS destination—it acts as a server proxy to an MDB (a message consumer/listener in JMS terms). As soon as a message arrives at the endpoint, the container triggers any registered MDBs listening to the endpoint and delivers the message to it.

For its part, the MDB implements a listener interface that is appropriate to the JCA connector/message type and passes activation configuration parameters to the JCA connector and registers as a listener to the JCA connector (we’ll discuss message listeners and activation configuration parameters shortly). JCA also enables MOM providers to integrate with Java EE containers in a standardized manner using a JCA-compliant connector or resource adapter.

For more information on JCA, visit http://java.sun.com/j2ee/connector/index.jsp.


4.3.1. Why use MDBs?

Given the less-than-stellar reputation of EJB 2, you might question the value of EJB 3 MDBs. The truth is, MDBs have enjoyed a reasonable degree of success even in the darkest hours of EJB. In this section, we’ll explain why you should take a serious look at MDBs.

Multithreading

Your business application may require multithreaded message consumers that can process messages concurrently. MDBs help you avoid complexity because they handle multithreading right out of the box, without any additional code. They manage incoming messages among multiple instances of beans (in a pool) that have no special multithreading code themselves. As soon as a new message reaches the destination, an MDB instance is retrieved from the pool to handle the message, as figure 4.7 shows. This is popularly known as MDB pooling, which you’ll learn about when we discuss the MDB lifecycle later in this chapter.

Figure 4.7. As soon as a message arrives at its destination, the container retrieves it and assigns a servicing MDB instance from the pool.

Simplified messaging code

In addition, MDBs relieve you from coding the mechanical aspects of processing messages—tasks such as looking up connection factories or destinations, creating connections, opening sessions, creating consumers, and attaching listeners. As you’ll see when we build the Turtle message consumer MDB, all these tasks are handled behind the scenes for you. In EJB 3, using sensible defaults for common circumstances eliminates most of the configuration. In the worst-case scenario, you’ll have to supply configuration information using simple annotations or through the deployment descriptor.

Starting message consumption

To start picking up messages from the shipping request queue, someone needs to invoke the appropriate method in your code. In a production environment, it is not clear how this will be accomplished. Starting message consumption through a user-driven manual process obviously is not desirable. In a server environment, almost every means of executing the method on server startup is highly system dependent, not to mention awkward. The same is true about stopping message receipt manually. On the other hand, registered MDBs would be bootstrapped or torn down gracefully by the container when the server is started or stopped.

We’ll continue consolidating these three points as we start investigating a real example of developing MDBs soon. Before we do that, though, let’s list the simple rules for developing an MDB.

4.3.2. Programming rules

Like all EJBs, MDBs are plain Java objects that follow a simple set of rules and sometimes have annotations. Don’t take these rules too seriously yet; simply note them in preparation for going through the code-intensive sections that follow.

  • The MDB class must directly (by using the implements keyword in the class declaration) or indirectly (through annotations or descriptors) implement a message listener interface.
  • The MDB class must be concrete. It cannot be either a final or an abstract class.
  • The MDB must be a POJO class and not a subclass of another MDB.
  • The MDB class must be declared public.
  • The bean class must have a no-argument constructor. If you don’t have any constructors in your Java class, the compiler will create a default constructor. The container uses this constructor to create a bean instance.
  • You cannot define a finalize method in the bean class. If any cleanup code is necessary, it should be defined in a method designated as PreDestroy.
  • You must implement the methods defined in the message listener interface. These methods must be public and cannot be static or final.
  • You must not throw the javax.rmi.RemoteException or any runtime exceptions. If a RuntimeException is thrown, the MDB instance is terminated.

We’ll apply these rules next in developing our example MDB.

4.3.3. Developing a message consumer with MDB

Let’s now explore developing an MDB by reworking the Turtle server JMS message consumer as an MDB. To make the code a bit more interesting, we’ll actually implement the processShippingRequest method mentioned in the JMS code. Listing 4.2 shows the MDB code that first retrieves shipping requests sent to the queue and then saves each request in the Turtle database table named SHIPPING_REQUEST. Note that we’re using JDBC for simplicity’s sake and because it lets us demonstrate the MDB lifecycle methods for opening and closing JDBC connections. We recommend that you consider EJB 3 Java Persistence API (discussed in part 3) for persisting your data instead of using straight JDBC.

Listing 4.2. Turtle server shipping request processor MDB

The @MessageDriven annotation identifies this object as an MDB and specifies the MDB configuration, including the fact that we are listening on the shipping request queue. Our code then marks this MDB as a JMS message listener . The onMessage method implements the message listener interface and processes incoming messages. A message-driven context is injected and used inside the onMessage method to roll back transactions as needed. A database resource is injected . The lifecycle callbacks open and close a connection derived from the database resource. Finally, the shared JDBC connection is used by the business logic called in onMessage to save each shipping request into the database.

Next, we’ll examine the major MDB features by analyzing this code in greater detail, starting with the @MessageDriven annotation.

4.3.4. Using the @MessageDriven annotation

MDBs are one of the simplest kinds of EJBs to develop, and they support the smallest number of annotations. In fact, the @MessageDriven annotation and the @ActivationConfigProperty annotation nested inside it are the only MDB-specific annotations. The @MessageDriven annotation in our example represents what you’ll typically use most of the time. The annotation is defined as follows:

@Target(TYPE)
@Retention(RUNTIME)
public @interface MessageDriven {
String name() default "";
Class messageListenerInterface default Object.class;
ActivationConfigProperty[] activationConfig() default {};
String mappedName();
String description();

}

Notice that all three of the annotation’s arguments are optional. If you are a minimalist, you can keep the annotation as simple as this:

@MessageDriven
public class ShippingRequestProcessorMDB

and leave any details to be added elsewhere, such as in the deployment descriptor.

The first element, name, specifies the name of the MDB—in our case, ShippingRequestProcessor. If the name element is omitted, the code uses the name of the class, ShippingRequestProcessorMDB, in our example. The second parameter, messageListenerInterface, specifies which message listener the MDB implements. The last parameter, activationConfig, is used to specify listener-specific configuration properties. Let’s take a closer look at the two last parameters.

4.3.5. Implementing the MessageListener

An MDB implements a message listener interface for the very same reason our plain JMS consumer implemented the javax.jms.MessageListener interface. The container uses the listener interface to register the MDB with the message provider and to pass incoming messages by invoking implemented message listener methods. Using the messageListenerInterface parameter of the @MessageDriven annotation is just one way to specify a message listener; we could have done the following instead:

@MessageDriven(
name="ShippingRequestJMSProcessor",
messageListenerInterface="javax.jms.MessageListener")
public class ShippingRequestProcessorMDB {

However, we chose to omit this parameter and specified the interface using the implements keyword:

public class ShippingRequestProcessorMDB implements MessageListener {

Yet another option is to specify the listener interface through the deployment descriptor and leave this detail out of our code altogether. The approach you choose is largely a matter of taste. We prefer the second approach because it resembles our JMS example.

MDBs let you specify a message listener with relative flexibility, which is especially cool if you consider the following scenario: suppose that we decide to switch messaging technologies and use Java API for XML Messaging (JAXM) to send shipping requests instead of JMS. (JAXM is essentially a SOAP-based XML messaging API. For more information, visit http://java.sun.com/webservices/jaxm/.) Thanks to JCA support, we can use still use MDBs to receive shipping requests (see the sidebar “JCA Connectors and Messaging” to learn how this might be done). All we have to do is switch to the JAXM message listener interface, javax.jaxm.OneWayMessageListener, instead of using javax.jms.MessageListener. We can reuse most of the MDB code and configuration:

public class ShippingRequestProcessorMDB implements
javax.jaxm.OneWayMessageListener {

However you choose to specify the message listener, make sure you provide a valid implementation of all methods required by your message listener—especially when using the deployment descriptor approach, where there are no compile-time checks to watch your back. Next, let’s take a look at the last (but definitely not least) parameter of the @MessageDriven annotation: activationConfig.

4.3.6. Using ActivationConfigProperty

The activationConfig property of the @MessageDriven annotation allows you to provide messaging system–specific configuration information through an array of ActivationConfigProperty instances. ActivationConfigProperty is defined as follows:

public @interface ActivationConfigProperty {
String propertyName();
String propertyValue();
}

Each activation property is essentially a name-value pair that the underlying messaging provider understands and uses to set up the MDB. The best way to grasp how this works is through example. Here, we provide three of the most common JMS activation configuration properties: destinationType, connectionFactoryJndiName, and destinationName:

@MessageDriven(
name="ShippingRequestProcessor",
activationConfig = {
@ActivationConfigProperty(
propertyName="destinationType",
propertyValue="javax.jms.Queue"),
@ActivationConfigProperty(
propertyName="connectionFactoryJndiName",
propertyValue="jms/QueueConnectionFactory"
),
@ActivationConfigProperty(
propertyName="destinationName",
propertyValue="jms/ShippingRequestQueue")
}
)

First, the destinationType property tells the container this JMS MDB is listening to a queue. If we were listening to a topic instead, the value could be specified as javax.jms.Topic. Next, connectionFactoryJndiName specifies the JNDI name of the connection factory that should be used to create JMS connections for the MDB. Finally, the destinationName parameter specifies that we are listening for messages arriving at a destination with the JNDI name of jms/ShippingRequestQueue.

There are a few other configuration properties for JMS that we’ll describe in the sections that follow. Visualizing what happens behind the scenes can help you remember these configuration properties. The container does something similar to our JMS message consumer setup steps (as shown in listing 4.2) to bootstrap the MDB. Most of the method parameters that we specify during those steps are made available as configuration properties in the MDB world.

acknowledgeMode

Messages are not actually removed from the queue until the consumer acknowledges them. There are many “modes” through which messages can be acknowledged. By default, the acknowledge mode for the underlying JMS session is assumed to be Auto-acknowledge, which means that the session acknowledged messages on our behalf in the background. This is the case for our example (since we omitted this property). All of the acknowledgment modes supported by JMS are listed in table 4.1. We could change the acknowledge mode to Dups-ok-acknowledge (or any other acknowledge mode we discuss in table 4.1) using the following:

@ActivationConfigProperty(
propertyName="acknowledgeMode",
propertyValue="Dups-ok-acknowledge")
Table 4.1. JMS session acknowledge modes. For nontransacted sessions, you should choose the mode most appropriate for your project. In general, Auto-acknowledge is the most common and convenient. The only other mode supported with MDB is Dups-ok-acknowledge.

Acknowledgment Mode

Description

Supported with MDB

Auto-acknowledge

The session automatically acknowledges receipt after a message has been received or is successfully processed.

YES

Client-acknowledge

You have to manually acknowledge the receipt of the message by calling the acknowledge method on the message.

NO

Dups-ok-acknowledge

The session can lazily acknowledge receipt of the message. This is similar to Auto-acknowledge but useful when the application can handle delivery of duplicate messages and rigorous acknowledgment is not a requirement.

YES

SESSION_TRANSACTED

This is returned for transacted sessions if the Session.getAcknowledgeMode method is invoked.

NO

subscriptionDurability

If our MDB is listening on a topic, we can specify whether the topic subscription is durable or nondurable.

Recall that in the pub-sub domain, a message is distributed to all currently subscribed consumers. In general, this is much like a broadcast message in that anyone who is not connected to the topic at the time does not receive a copy of the message. The exception to this rule is what is known as a durable subscription. Once a consumer obtains a durable subscription on a topic, all messages sent to the topic are guaranteed to be delivered to that consumer. If the durable subscriber is not connected to a topic when a message is received, MOM retains a copy of the message until the subscriber connects and delivers the message. The following shows how to create a durable subscriber:

MessageConsumer playBoySubscriber = session.createDurableSubscriber(
playBoyTopic, "JoeOgler");

Here, we are creating a durable subscription message consumer to the javax.jms.Topic playBoyTopic with a subscription ID of JoeOgler. From now on, all messages to the topic will be held until a consumer with the subscription ID JoeOgler receives them. You can remove this subscription with the following code:

session.unsubscribe("JoeOgler");

If you want the MDB to be a durable subscriber, then ActivationConfigProperty would look like this:

@ActivationConfigProperty(
propertyName="destinationType",
propertyValue="javax.jms.Topic"),
@ActivationConfigProperty(
propertyName="subscriptionDurability",
propertyValue="Durable")

For nondurable subscriptions, explicitly set the value of the subscriptionDurability property to NonDurable, which is also the default.

messageSelector

The messageSelector property is the MDB parallel to applying a selector for a JMS consumer. Our current code consumes all messages at the destination. If we prefer, we could filter the messages we retrieve by using a message selector—a criterion applied to the headers and properties of messages specifying which messages the consumer wants to receive. For example, if we want to receive all shipping requests whose Fragile property is set to true, we use the following code:

MessageConsumer consumer = session.createConsumer(destination,
"Fragile IS TRUE");

As you might have noticed, the selector syntax is almost identical to the WHERE clause in SQL 92, but the selector syntax uses message header and property names instead of column names. Selector expressions can be as complex and expressive as you need them to be. They can include literals, identifiers, whitespace, expressions, standard brackets, logical and comparison operators, arithmetic operators, and null comparisons.

Using our JMS message selector example, we could specify in our MDB that we want to handle only fragile shipping requests as follows:

@ActivationConfigProperty(
propertyName="messageSelector",
propertyValue="Fragile IS TRUE")

Table 4.2 summarizes some common message selector types.

Table 4.2. Commonly used message selector types. The selector syntax is almost identical to the SQL WHERE clause.

Type

Description

Example

Literals

Can be strings, exact or approximate numeric values, or booleans.

BidManagerMDB

100

TRUE

Identifiers

Can be a message property or header name; case sensitive.

RECIPIENT

NumOfBids

Fragile

JMSTimestamp

Whitespace

Same as defined in the Java language specification: space, tab, form feed, and line terminator.

 

Comparison operators

Comparison operators, such as =, >, >=, <=, <>.

RECIPIENT='BidManagerMDB'

NumOfBids>=100

Logical operators

All three types of logical operators—NOT, AND, OR—are supported.

RECIPIENT='BidManagerMDB'

AND NumOfBids>=100

Null comparison

IS NULL and IS NOT NULL comparisons.

FirstName IS NOT NULL

True/false comparison

IS [NOT] TRUE and IS [NOT] FALSE comparisons.

Fragile IS TRUE

Fragile IS FALSE

We’re now ready to examine lifecycle callbacks in MDBs.

4.3.7. Using bean lifecycle callbacks

As you recall from chapter 3, similar to stateless session beans, MDBs have a simple lifecycle (see figure 4.8 for a refresher). The container is responsible for the following:

  • Creates MDB instances and sets them up
  • Injects resources, including the message-driven context (discussed in the next chapter in detail)
  • Places instances in a managed pool
  • Pulls an idle bean out of the pool when a message arrives (the container may have to increase the pool size at this point)
  • Executes the message listener method; e.g., the onMessage method
  • When the onMessage method finishes executing, pushes the idle bean back into the “method-ready” pool
  • As needed, retires (or destroys) beans out of the pool
Figure 4.8. The MDB lifecycle has three states: does not exist, idle, and busy. There are only two lifecycle callbacks corresponding to bean creation and destruction; you can use PostConstruct and PreDestroy methods to receive these callbacks.

The MDB’s two lifecycle callbacks are (1) PostConstruct, which is called immediately after an MDB is created and set up and all the resources are injected, and (2) PreDestroy, which is called right before the bean instance is retired and removed from the pool. These callbacks are typically used for allocating and releasing injected resources that are used by the onMessage method, which is exactly what we do in our example.

The processShippingRequest method saves shipping requests that the onMessage method extracts from the incoming JMS message:

private void processShippingRequest(ShippingRequest request)
throws SQLException {
Statement statement = connection.createStatement();
statement.execute(
"INSERT INTO "
+ "SHIPPING_REQUEST ("
...
+ request.getInsuranceAmount() + " )");
}

The method creates a statement from an open JDBC connection and uses it to save a record into the SHIPPING_REQUEST table containing all the fields from the ShippingRequest object. The JDBC connection object used to create the statement is a classic heavy-duty resource. It is expensive to open and should be shared whenever possible. On the other hand, it can hold a number of native resources, so it is important to close the connection when it is no longer needed. We accomplish both these goals using callback methods as well as resource injection.

First, the JDBC data source that the connection is created from is injected using the @Resource annotation:

@Resource(name="jdbc/TurtleDS")
public void setDataSource(DataSource dataSource) {
this.dataSource = dataSource;
}

The @Resource annotation tells the EJB container that it should look up a java.sql.DataSource named jdbc/TurtleDS from JNDI and pass it to the setDataSource method after creating a new instance of the bean. The setDataSource method, in turn, saves the data source in an instance variable. After injecting resources, the container checks whether there are any designated PostConstruct methods that need to be invoked before the MDB is put into the pool. In our case, we mark the initialize method with the @PostConstruct annotation:

@PostConstruct
public void initialize() {
...
connection = dataSource.getConnection();
...
}

In the initialize method, we are obtaining a java.sql.Connection from the injected data source and saving it into the connection instance variable used in processShippingRequest. At some point, the container decides that our bean should be removed from the pool and destroyed (perhaps at server shutdown). The PreDestroy callback gives us a chance to cleanly tear down bean resources before this is done. In the cleanup method marked with the @PreDestroy annotation, we tear down the database connection resource before the container retires our bean:

@PreDestroy
public void cleanup() {
...
connection.close();
connection = null;
...
}

Although database resources and their management are the primary uses of resource injection and lifecycle methods in MDBs, another important resource being used in the JMS sections are also important for MDB. We’re referring to the JMS destination and connection factory administrative objects, as well as the JMS connections. Let’s explore how these are utilized in MDBs.

4.3.8. Sending JMS messages from MDBs

Somewhat ironically, a task you’ll find yourself performing time and again in an MDB is sending JMS messages. As a simple example, suppose that we have an incomplete shipping request and we need to communicate that to ActionBazaar from ShippingRequestProcessorMDB. The easiest way to handle this notification is via JMS messages sent to an error queue that ActionBazaar listens to. Fortunately, you’ve already seen how to send a JMS message in listing 4.1. This task is even simpler and more robust in MDBs. We can inject the queue named jms/ShippingErrorQueue and the connection factory named jms/QueueConnectionFactory by using the @Resource annotation:

@Resource(name="jms/ShippingErrorQueue")
private javax.jms.Destination errorQueue;
@Resource(name="jms/QueueConnectionFactory")
private javax.jms.ConnectionFactory connectionFactory;

We can then create and destroy a shared javax.jms.Connection instance using lifecycle callbacks, just as we managed the JDBC connection in the previous section:

@PostConstruct
public void initialize() {
...
jmsConnection = connectionFactory.createConnection();
...
}
@PreDestroy
public void cleanup() {
...
jmsConnection.close();
...
}

Finally, the business method that sends the error message looks much like the rest of the JMS session code in listing 4.1:

    private void sendErrorMessage(ShippingError error) {
Session session = jmsConnection.createSession(true,
Session.AUTO_ACKNOWLEDGE);
MessageProducer producer = session.createProducer(errorQueue);
...
producer.send(message);
session.close();
}

Although we didn’t explicitly show it in our example, there is one more MDB feature you should know about: MDB transaction management. We’ll discuss EJB transactions in general in much more detail in the next chapter, so here we’ll give you the “bargain basement” version.

4.3.9. Managing MDB transactions

In our plain JMS examples, we specified whether the JMS session would be transactional when we created it. On the other hand, if you look closely at the MDB example it doesn’t indicate anything about transactions. Instead, we’re letting the container use the default transactional behavior for MDBs. By default, the container will start a transaction before the onMessage method is invoked and will commit the transaction when the method returns, unless the transaction was marked as rollback through the message-driven context. You’ll learn more about transactions in chapter 6.

This brief discussion of transaction management concludes our analysis of the basic features that MDBs offer. We’ve discussed how you can use MDBs to leverage the power of messaging without dealing with the low-level details of the messaging API. As you’ve seen, MDBs provide a host of EJB features for free, such as multithreading, resource injection, lifecycle management, and container-managed transactions. We’ve formulated our code samples so that you can use them as templates for solving real business problems. At this point, we’ll give you tips for dealing with the nuances of MDBs.

4.4. MDB best practices

Like all technologies, MDBs have some pitfalls to watch for and some best practices that you should keep in mind. This is particularly true in demanding environments where messaging is typically deployed.

Choose your messaging models carefully. Before you wade knee deep in code, consider your choice of messaging model carefully. You might find that PTP will solve your problem nine times out of ten. In some cases, though, the pub-sub approach is better, especially if you find yourself broadcasting the same message to more than one receiver (such as our system outage notification example). Luckily, most messaging code is domain independent, and you should strive to keep it that way. For the most part, switching domains should be just a matter of configuration.

Remember modularization. Because MDBs are so similar to session beans, it is natural to start putting business logic right into message listener methods. Business logic should be decoupled and modularized away from messaging-specific concerns. We followed this principle by coding the processShippingRequest method and invoking it from onMessage. An excellent practice (but one that would have made this chapter unnecessarily complicated) is to put business logic in session beans and invoke them from the onMessage method.

Make good use of message filters. There are some valid reasons for using a single messaging destination for multiple purposes. Message selectors come in handy in these circumstances. For example, if you’re using the same queue for both shipping requests and order cancellation notices, you can have the client set a message property identifying the type of request. You can then use message selectors on two separate MDBs to isolate and handle each kind of request.

Conversely, in some cases, you might dramatically improve performance and keep your code simple by using separate destinations instead of using selectors. In our example, using separate queues and MDBs for shipping requests and cancellation orders could make message delivery much faster. In this case, the client would have to send each request type to the appropriate queue.

Choose message types carefully. The choice of message type is not always as obvious as it seems. For example, it is a compelling idea to use XML strings for messaging. Among other things, this tends to promote loose coupling between systems. In our example, the Turtle server would know about the format of the XML message and not the ShippingRequest object itself.

The problem is that XML tends to bloat the size of the message, significantly degrading MOM performance. In certain circumstances, it might even be the right choice to use binary streams in the message payload, which puts the least amount of demand on MOM processing as well as memory consumption.

Be wary of poison messages. Imagine that a message is handed to you that your MDB was not able to consume. Using our example, let’s assume that we receive a message that’s not an ObjectMessage. As you can see from this code snippet, if this happens the cast in onMessage will throw a java.lang.ClassCastException:

Since onMessage will not complete normally, the container will be forced to roll back the transaction and put the message back on the queue instead of acknowledging it (in fact, since a runtime exception is thrown, the bean instance will be removed from the pool). The problem is, since we are still listening on the queue, the same message will be delivered to us again and we will be stuck in the accept/die loop indefinitely! Messages that cause this all-too-common scenario are called poison messages.

The good news is that many MOMs and EJB containers provide mechanisms that deal with poison messages, including “redelivery” counts and “dead message” queues. If you set up the redelivery count and dead message queue for the shipping request destination, the message delivery will be attempted for the specified number of times. After the redelivery count is exceeded, the message will be moved to a specially designated queue for poison messages called the “dead message” queue. The bad news is that these mechanisms are not standardized and are vendor specific.

Configure MDB pool size. Most EJB containers let you specify the maximum number of instances of a particular MDB the container can create. In effect, this controls the level of concurrency. If there are five concurrent messages to process and the pool size is set to three, the container will wait until the first three messages are processed before assigning any more instances. This is a double-edged sword and requires careful handling. If you set your MDB pool size too small, messages will be processed slowly. At the same time, it is desirable to place reasonable limits on the MDB pool size so that many concurrent MDB instances do not choke the machine. Unfortunately, at the time of this writing, setting MDB pool sizes is not standardized and is provider specific.

4.5. Summary

In this chapter, we covered basic messaging concepts, JMS, and MDBs. Messaging is an extremely powerful technology for the enterprise, and it helps build loosely coupled systems. JMS allows you to use message-oriented middleware (MOM) from enterprise Java applications. Using the JMS API to build a message consumer application can be time consuming, and MDBs make using MOM in a standardized manner through Java EE extremely easy.

Note, however, that messaging and MDBs are not right for all circumstances and can be overused. One such case is using the request/reply model (discussed in the sidebar “The request-reply model”), which entails a lot of extra complexity compared to simple PTP or pub-sub messaging. If you find yourself using this model extensively and in ways very close to synchronous messaging, it might be worth thinking about switching to a synchronous technology such as RMI, SOAP, or remote session bean calls.

A few major EJB features we touched on in this chapter are dependency injection, interceptors, timers, transaction, and security. As you’ve seen, EJB 3 largely relieves us from these system-level concerns while providing extremely robust and flexible functionality. We’ll discuss dependency injection, timers, and interceptors in the next chapter.