The message-driven bean (MDB) is an asynchronous listener. Its callers are systems that say, “Check out this message; do whatever you want with it” and don’t have the decency to wait around for a response. In fact, clients of an MDB have no idea that the messages they send may be processed by an EJB at all. Messages are sent directly to some messaging system (it doesn’t matter which), where they are routed via a standard inflow mechanism to the EJB subsystem and, eventually, a backing MDB instance (Figure 8-1).
Figure 8-1. Message-driven bean listening on events via a messaging subsystem
The key to the generic plumbing shown in the figure is the Java Connector Architecture (JCA; http://jcp.org/en/jsr/detail?id=322), which provides an abstraction for the delivery of any message type. This means that EJB message-driven beans may act as a listener for events sent from any system for which there is a valid JCA inflow adaptor.
The message-driven bean was introduced in EJB 2.0 to support the processing of asynchronous messages from a Java Message Service (JMS; http://java.sun.com/products/jms/) provider. EJB 2.1 expanded the definition of the message-driven bean so that it can support any messaging system, not just JMS, via the facilities provided by JCA. EJB 3.x does not really expand on the feature set of earlier specification versions, but it does simplify configuration with the use of annotations. This topic examines both JMS-based message-driven beans as well as the expanded message-driven bean model available to EJB 3.0 developers.
JMS and Message-Driven Beans
All EJB 3.x vendors must support a JMS provider. Most vendors supply a built-in provider, though it’s possible to integrate a third-party implementation instead. Regardless, a JMS provider is an absolute necessity for supporting message-driven beans. By forcing the adoption of JMS, the EJB expert group has guaranteed that EJB developers can expect to have a working JMS provider to which messages can be both sent and received.
JMS as a Resource
JMS is a vendor-neutral API that can be used to access enterprise messaging systems. Enterprise messaging systems (aka message-oriented middleware) facilitate the exchange of messages between software applications, usually over a network. The role of JMS isn’t unlike the role of JDBC; just as JDBC provides a common API for accessing many different relational databases, JMS provides vendor-independent access to enterprise messaging systems. Although messaging products aren’t as familiar as database products, there’s no shortage of messaging systems that support JMS, including OpenJMS, HornetQ, ActiveMQ, Qpid, and SonicMQ. Software applications that use the JMS API for sending or receiving messages are portable from one JMS vendor to another.
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, and a client that receives a message is called a consumer. A single JMS client can be both a producer and a consumer.
In EJB, enterprise beans of all types can use JMS to send messages. These are later consumed by other applications or by message-driven beans. JMS facilitates sending messages from enterprise beans 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 is IBM’s MQSeries—but JMS is relatively new, and it is specifically designed to deliver a variety of message types from one Java application to another.
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 or JAX-RPC, in which each client invocation blocks the current thread until the method completes execution. This lockstep processing makes the client dependent on the availability of the EJB server, resulting in a tight coupling between the client and the enterprise bean.
JMS clients send 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 the message to other clients. There’s no effect on the client if one or more recipients are unavailable; once delivered, the sender will continue with its work. It’s the router’s responsibility to make sure that the message eventually reaches its destination. 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 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 fictional UserRegistrationEJB may want to notify arbitrary systems of a new user, but it doesn’t want to be coupled. By sending a JMS message to a system that is listening for incoming registration events, the UserRegistrationEJB can dispatch the request and continue along its way (Figure 8-2).
In this example, the applications receiving JMS messages initiated from the UserRegistrationEJB may be message-driven beans, other Java applications in the enterprise, or applications in other organizations that benefit from being notified that a new registration has been processed. Examples might include sending an email confirmation or an internal marketing application that adds customers to a catalog mailing list, and these may be plugged in or turned off at runtime without affecting the registration process itself.
Because messaging is inherently decoupled and asynchronous, the transactions and security contexts of the sender are not propagated to the receiver. For example, when the UserRegistrationEJB sends the message, the JMS provider may authenticate it, but the message’s security context won’t be propagated to the JMS client that received the message. When a JMS client receives the message, it has 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 environments with different security domains.
Figure 8-2. UserRegistrationEJB firing an event denoting that a new user has been created
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. 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. There may be a network problem, the client may be down, or there may be some other delay. Transactions are designed to be executed quickly because they lock up resources, and applications can’t tolerate the possibility of a long transaction with an unpredictable end.
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 UserRegistrationEJB’s transaction fails for any reason, the JMS provider discards the message.
JMS Messaging Models
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 topic 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, as illustrated in Figure 8-3.
Point-to-point, on the other hand, is intended for a message that is to be processed once (Figure 8-4).
Figure 8-3. Publish-and-subscribe model of messaging
Figure 8-4. Point-to-point model of messaging
Each messaging domain (i.e., pub/sub and p2p) has its own set of interfaces and classes for sending and receiving messages. This results in two different APIs, which share some common types. JMS 1.1 introduced a Unified API that allows developers to use a single set of interfaces and classes for both messaging domains. Only the Unified API is used in this topic.
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. The pub/sub messaging model is largely a push-based model, in which messages are automatically broadcast to consumers without the consumers having to request or poll the topic for new messages.
In this pub/sub messaging model, the producer sending the message is not dependent on the consumers receiving the message. 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 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, in which messages are requested from the queue instead of being pushed to the client automatically.* A queue may have multiple receivers, but only one receiver may receive each message. As shown earlier, the JMS provider takes care of doling out the messages among JMS clients, ensuring that each message is processed by only one consumer. The JMS specification does not dictate the rules for distributing messages among multiple receivers.
Which messaging model should you use?
In most cases, the decision about which model to use depends on how it’ll be used by your application. With pub/sub, any number of subscribers can be listening on a topic, and all of them will receive copies of the same message. The developer may not care if everybody is listening, or even if nobody is listening. For example, consider a developer that broadcasts stock quotes. If any particular subscriber is not currently connected and misses out on a great quote, the developer is not concerned.
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 leveraged 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.
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.
Point-to-point is more convenient when you want to process a given message exactly once. This is perhaps the most critical difference between the two models: p2p guarantees that only one consumer processes each message. This ability is extremely important when messages need to be processed separately but in tandem.
* The JMS specification does not specifically state how the p2p and pub/sub models must be implemented. Either model can use push or pull—but conceptually, pub/sub is push and p2p is pull.
Learning More About JMS
JMS (and enterprise messaging in general) represents a powerful paradigm in distributed computing. While this topic has provided a brief overview of JMS, it has presented only enough material to prepare you for the discussion of message-driven beans in the next section. To understand JMS and how it is used, you will need to study it independently.f Taking the time to learn JMS is well worth the effort.
JMS-Based Message-Driven Beans
Message-driven beans (MDBs) are stateless, server-side, transaction-aware components for processing asynchronous messages delivered via JMS. While a message-driven bean is responsible for consuming messages, its container manages the component’s environment: transactions, security, resources, concurrency, and message acknowledgment. It’s particularly important to note that the container manages concurrency. The thread safety provided by the container gives MDBs a significant advantage over traditional JMS clients, which must be custom built to manage resources, transactions, and security in a multithreaded environment. An MDB can process hundreds of JMS messages concurrently because many underlying bean instances of the MDB can execute concurrently in the container.
A message-driven bean is a complete enterprise bean, just like a session or entity bean, but there are some important differences. While a message-driven bean has a bean class, it does not have a business or component interface. These are absent because the message-driven bean responds only to asynchronous messages and not to direct client invocations.
MDBs are identified using the @javax.ejb.MessageDriven annotation or, alternatively, are described in an EJB deployment descriptor. An MDB can be deployed alone, but it’s more frequently deployed with the other enterprise beans that it references.
We’ll see later that because MDBs can receive messages from arbitrary messaging formats, the configuration must be very flexible to be able to describe the proprietary properties that different providers will have. JCA-based MDBs don’t necessarily use JMS as the message service, so this requirement is very important. To facilitate this, the @MessageDriven.activationConfig() attribute takes an array of @ActivationConfigProperty annotations. These annotations are simply a set of generic name/value pairs that describe the configuration of your MDB.
For a detailed treatment of JMS, see Java Message Service.
The property names and values used in the activationConfig() attribute to describe the messaging service vary depending on the type of message service used, but EJB 3.x defines a set of fixed properties for JMS-based message-driven beans. These properties are acknowledgeMode, messageSelector, destinationType, and subscriptionDurability.
An MDB can declare a message selector, which allows an MDB to be more discerning about the messages it receives from a particular topic or queue. Message selectors use Message properties as criteria in conditional expressions.^ These conditional expressions use Boolean logic to declare which messages should be delivered. A message selector is declared using the standard property name, messageSelector, in an activation configuration element:
Message selectors are based on message properties, which are additional headers that can be assigned to a message; they allow vendors and developers to attach information to a message that isn’t part of the message’s body. The Message interface provides several methods for reading and writing properties. Properties can have a String value or one of several primitive values (boolean, byte, short, int, long, float, double). The naming of properties, together with their values and conversion rules, is strictly defined by JMS.
Here’s how a JMS producer would go about setting a MessageFormat property on a Message:
t Message selectors are also based on message headers, which are outside the scope of this topic.
The message selectors are based on a subset of the SQL-92 conditional expression syntax that is used in the WHERE clauses of SQL statements. They can become fairly complex, including the use of literal values, Boolean expressions, unary operators, and so on.
A JMS acknowledgment means that the JMS client notifies the JMS provider (message router) when a message is received. In EJB, it’s the MDB container’s responsibility to send an acknowledgment when it receives a message. Acknowledging a message tells the JMS provider that an MDB container has received and processed the message. Without an acknowledgment, the JMS provider does not know whether the MDB container has received the message, and unwanted redeliveries can cause problems.
The acknowledgment mode is set using the standard acknowledgeMode activation configuration property, as shown in the following code snippet:
Two values can be specified for acknowledgment mode: Auto-acknowledge and Dups-ok-acknowledge. Auto-acknowledge tells the container that it should send an acknowledgment to the JMS provider immediately after the message is given to an MDB instance to process. Dups-ok-acknowledge tells the container that it doesn’t have to send the acknowledgment immediately; anytime after the message is given to the MDB instance will be fine. With Dups-ok-acknowledge, it’s possible for the MDB container to delay acknowledgment for so long that the JMS provider assumes the message was not received and sends a “duplicate” message. Obviously, with Dups-ok-acknowledge, your MDBs must be able to handle duplicate messages correctly.
Auto-acknowledge avoids duplicate messages because the acknowledgment is sent immediately. Therefore, the JMS provider won’t send a duplicate. Most MDBs use Auto-acknowledge to avoid processing the same message twice. Dups-ok-acknowledge exists because it can allow a JMS provider to optimize its use of the network. In practice, though, the overhead of an acknowledgment is so small, and the frequency of communication between the MDB container and the JMS provider is so high, that Dups-ok-acknowledge doesn’t have a big impact on performance.
Having said all of this, the acknowledgment mode is ignored most of the time—in fact, it is ignored unless the MDB executes with bean-managed transactions or with the container-managed transaction attribute NotSupported. In all other cases, transactions are managed by the container, and acknowledgment takes place within the context of the transaction. If the transaction succeeds, the message is acknowledged. If the transaction fails, the message is not acknowledged. When using container-managed transactions with a Required transaction attribute, the acknowledgment mode is usually not specified; however, it is included in the deployment descriptor for the sake of discussion.
When a JMS-based MDB uses a javax.jms.Topic, the deployment descriptor must declare whether the subscription is Durable or NonDurable. A Durable subscription outlasts an MDB container’s connection to the JMS provider, so if the EJB server suffers a partial failure, shuts down, or otherwise disconnects from the JMS provider, the messages that it would have received are not lost. The provider stores any messages that are delivered while the container is disconnected; the messages are delivered to the container (and from there to the MDB) when the container reconnects. This behavior is commonly referred to as store-and-forward messaging. Durable MDBs are tolerant of disconnections, whether intentional or the result of a partial failure.
If the subscription is NonDurable, any messages the bean would have received while it was disconnected are lost. Developers use NonDurable subscriptions when it is not critical for all messages to be processed. Using a NonDurable subscription improves the performance of the JMS provider but significantly reduces the reliability of the MDBs.
When the destination type is javax.jms.Queue, durability is not a factor, because of the nature of queue-based messaging systems. With a queue, messages may be consumed only once, and they remain in the queue until they are distributed to one of the queue’s listeners.
Message-driven beans also have a context object that is similar in functionality to that of the javax.ejb.SessionContext described.This object may be injected using the @javax.annotation.Resource annotation: @Resource MessageDrivenContext context;
The MessageDrivenContext simply extends the EJBContext; it does not add any new methods. Only the transactional methods that MessageDrivenContext inherits from EJBContext are available to message-driven beans. The home methods—getEJBHome() and getEJBLocalHome()—throw a RuntimeException if invoked, because MDBs do not have home interfaces or EJB home objects. The security methods— getCallerPrincipal() and isCallerInRole()—also throw a RuntimeException if invoked on a MessageDrivenContext. When an MDB services a JMS message, there is no “caller,” so there is no security context to be obtained from the caller. Remember that JMS is asynchronous and doesn’t propagate the sender’s security context to the receiver—that wouldn’t make sense, since senders and receivers tend to operate in different environments.
MDBs usually execute in a container-initiated or bean-initiated transaction, so the transaction methods allow the MDB to manage its context. The transaction context is not propagated from the JMS sender; it is either initiated by the container or initiated by the bean explicitly using javax.jta.UserTransaction.
Message-driven beans also have access to their own JNDI ENCs, which provide the MDB instances access to environment entries, other enterprise beans, and resources. For example, we may take advantage of the ENC to obtain references to an EntityManager, another EJB, or a JMS ConnectionFactory and Queue.
MDBs usually implement the javax.jms.MessageListener interface, which defines the onMessage() method. This method processes the JMS messages received by a bean.
Although MDBs usually implement this interface, we will see later in this topic that MDBs can integrate with other messaging systems besides JMS, which define a different interface contract.
Taskflow and integration for B2B: onMessage()
The onMessage() method is where all the business logic goes. As messages arrive, the container passes them to the MDB via the onMessage() method. When the method returns, the MDB instance is ready to process a new message.
JMS is frequently used as an integration point for business-to-business (B2B) applications, so it’s easy to imagine a message coming from an external business partner (perhaps a third-party processor or auditing system).
Like a session bean, the MDB can access any other session bean and use that bean to complete a task. An MDB can manage a process and interact with other beans as well as resources. For example, it is commonplace for an MDB to use the Java Persistence API (JPA) to access a database based on the contents of the message it is processing.
Sending messages from a message-driven bean
An MDB can also send messages using JMS. This hypothetical deliverTicket() method sends the ticket information to a destination defined by the sending JMS client:
Every message type has two parts: a message header and a message body (aka the payload). The message header contains routing information and may also have properties for message filtering and other attributes. One of these attributes may be JMSReplyTo. The message’s sender may set the JMSReplyTo attribute to any destination accessible to its JMS provider. In the case of the reservation message, the sender set the JMSReplyTo attribute to the queue to which the resulting ticket should be sent. Another application can access this queue to read tickets and distribute them to customers or store the information in the sender’s database.
You can also use the JMSReplyTo address to report business errors. For example, if the cabin is already reserved, the preceding EJB might send an error message to the JMSReplyTo queue explaining that the reservation could not be processed. Including this type of error handling is left as an exercise for the reader.
The Lifecycle of a Message-Driven Bean
Just as session beans have well-defined lifecycles, so does the message-driven bean. The MDB instance’s lifecycle has two states: Does Not Exist and Method-Ready Pool. The Method-Ready Pool is similar to the instance pool used for stateless session beans.§ Figure 8-5 illustrates the states and transitions that an MDB instance goes through in its lifetime.
§Some vendors may not pool MDB instances but may instead create and destroy instances with each new message. This is an implementation-specific decision that should not affect the specified lifecycle of the stateless bean instance.
Figure 8-5. MDB lifecycle
The Does Not Exist State
When an MDB instance is in the Does Not Exist state, it is not an instance in the memory of the system. In other words, it has not been instantiated yet.
The Method-Ready Pool
MDB instances enter the Method-Ready Pool as the container needs them. When the EJB server is first started, it may create a number of MDB instances and enter them into the Method-Ready Pool (the actual behavior of the server depends on the implementation). When the number of MDB instances handling incoming messages is insufficient, more can be created and added to the pool.
Transitioning to the Method-Ready Pool
When an instance transitions from the Does Not Exist state to the Method-Ready Pool, three operations are performed on it. First, the bean instance is instantiated by invoking the Class.newInstance() method on the bean implementation class. Second, the container injects any resources that the bean’s metadata has requested via an injection annotation or XML deployment descriptor.
You must always provide a default constructor. A default constructor is a constructor with no parameters. The container instantiates instances of the bean class using Class.newInstance(), which requires a no-arg constructor. If no constructors are defined, the no-arg constructor is implicit.
Finally, the EJB container will invoke the PostConstruct callback if there is one. The bean class may or may not have a method that is annotated with @javax.ejb.PostConstruct. If it is present, the container will call this annotated method after the bean is instantiated. This @PostConstruct annotated method can be of any name and visibility, but it must return void, have no parameters, and throw no checked exceptions. The bean class may define only one @PostConstruct method (but it is not required to do so).
MDBs are not subject to activation, so they can maintain open connections to resources for their entire lifecyclesJ The @PreDestroy method should close any open resources before the stateless session bean is evicted from memory at the end of its lifecycle.
Life in the Method-Ready Pool
When a message is delivered to an MDB, it is delegated to any available instance in the Method-Ready Pool. While the instance is executing the request, it is unavailable to process other messages. The MDB can handle many messages simultaneously, delegating the responsibility of handling each message to a different MDB instance. When a message is delegated to an instance by the container, the MDB instance’s MessageDrivenContext changes to reflect the new transaction context. Once the instance has finished, it is immediately available to handle a new message.
Transitioning out of the Method-Ready Pool: The death of an MDB instance
Bean instances leave the Method-Ready Pool for the Does Not Exist state when the server no longer needs them—that is, when the server decides to reduce the total size of the Method-Ready Pool by evicting one or more instances from memory. The process begins by invoking an @PreDestroy callback method on the bean instance. Again, as with @PostConstruct, this callback method is optional to implement and its signature must return a void type, have zero parameters, and throw no checked exceptions. A @PreDestroy callback method can perform any cleanup operation, such as closing open resources.
II The duration of a stateless bean instance’s life is assumed to be very long. However, some EJB servers may actually destroy and create instances with every method invocation, making this strategy less attractive. Consult your vendor’s documentation for details on how your EJB server handles stateless instances.
As with @PostConstruct, @PreDestroy is invoked only once: when the bean is about to transition to the Does Not Exist state. During this callback method, the MessageDriven Context and access to the JNDI ENC are still available to the bean instance. Following the execution of the @PreDestroy method, the bean is dereferenced and eventually garbage-collected.
Connector-Based Message-Driven Beans
Although the JMS-based MDB has proven very useful, it has limitations. Perhaps the most glaring limitation is that EJB vendors are able to support only a small number of JMS providers (usually only one). In pre-EJB 2.1 days, most vendors supported only their own JMS provider and no others. Obviously, this limits your choices: if your company or a partner company uses a JMS provider that is not supported by your EJB vendor, you will not be able to process messages from that JMS provider.*
The root of the problem is complex and requires a fairly deep understanding of transaction management. In a nutshell, the delivery of the message by the JMS provider to the MDB, and all the work performed by the MDB (e.g., using JDBC, invoking methods on other beans, etc.), must be part of the same transaction, which is initiated by the EJB container. This requires that the EJB container have prior knowledge that message delivery is imminent so that it can initiate a transaction before the message is actually delivered. Unfortunately, the JMS API doesn’t support this kind of functionality. So in the early days of EJB, JMS providers had to perform custom integration with each and every EJB vendor. Custom integration was expensive (business-wise), so old EJB 2.0 vendors generally choose to integrate with very few JMS providers.
Another limitation with JMS-based MDBs is that you are tied to the JMS programming model; no other messaging systems are supported. Although JMS is very useful, it’s not the only messaging system available. SOAP, email, CORBA messaging, proprietary messaging systems used in ERP systems (SAP, PeopleSoft, etc.), and legacy messaging systems are examples of other non-JMS messaging systems.
EJB 3.x (and 2.1) supports an expanded, more open definition of message-driven beans that allows them to service any kind of messaging system from any vendor. The only requirement is that new types of message-driven beans adhere to the message-driven bean lifecycle. EJB vendors can build custom code to support a new messaging system (something other than JMS), but they must also support any message-driven bean type that’s based on JCA 1.6.
#A workaround is to use a JMS gateway, which routes messages from one JMS provider to another, but this is a custom solution outside the EJB specification.
The JCA provides a standard Service Provider Interface (SPI) that allows any EIS to plug into any Java EE container system. Version 1.0 of the connector architecture applies only to request/reply resources in which the Java EE component (EJB or servlet/ JSP) initiates the request. The current version of the connector architecture (1.6), which is required by JEE6 and higher, is much more general and can work with any asynchronous messaging systems. In such systems, the Java EE component waits for messages to arrive instead of initiating an interaction with an EIS; the EIS initiates the interaction by delivering a message.
JCA defines a messaging contract specifically tailored to message-driven beans. It defines the contracts between an EJB container and an asynchronous Connector so that message-driven beans automatically process incoming messages from the EIS. MDBs based on an asynchronous Connector can implement a specific messaging interface defined by the Connector itself. Instead of implementing the javax.jms.MessageListener interface, the MDB implements some other type of interface that is specific to the EIS.
For example, consider a hypothetical Email Connector that allows MDBs to process email—similar to how JMS-based MDBs process JMS messages. The Email Connector is purchased from Vendor X and is delivered in a JAR file called a Resource ARchive (RAR). The RAR contains all the Connector code and deployment descriptors necessary to plug into the EJB container system. It also defines a messaging interface that the developer uses to create an email MDB. Here is a possible email messaging interface that must be implemented by an email MDB:
The bean class that implements this interface is responsible for processing email messages delivered by the Email Connector. The following code shows an MDB that implements the EmailListener interface and processes email:
In this example, the container calls onMessage() to deliver a JavaMail Message object, which represents an incoming email message. However, the messaging interfaces used by a Connector-based MDB don’t have to use onMessage(). The method name and method signature can be whatever is appropriate to the EIS; it can even have a return type. For example, a Connector might be developed to handle request/reply-style messaging for SOAP. This connector might use the ReqRespListener defined by the Java API for XML Messaging (JAXM), which is a SOAP messaging API defined by Sun Microsystems that is not part of the Java EE platform:
In this interface, onMessage() has a return type of SOAPMessage. This means the EJB container and Connector are responsible for coordinating the reply message back to the sender (or to some destination defined in the deployment descriptor). In addition to supporting different method signatures, the messaging interface may have several methods for processing different kinds of messages using the same MDB.
There’s no limit to the new kinds of message-driven beans that EJB container systems can support. The real beauty of all of this is that Connector-based MDBs are completely portable across EJB vendors—because all vendors must support them. If you use a Connector-based MDB with EJB Vendor A and later change to EJB Vendor B, you can continue to use the same Connector-based MDB with no portability problems.
The activation configuration properties used with non-JMS-based MDBs depend on the type of Connector and its requirements. Let’s see an example of this:
We talked about @ActivationConfigProperty annotations before. As you can see from the preceding example, any name/value pair is supported within this annotation, so it can easily support the email-specific configuration for this Connector type.
Message linking is a feature that allows the messages being sent by any enterprise bean to be routed to a specific message-driven bean in the same deployment. By using message linking, you can orchestrate a flow of messages between components in the same application. This is achieved by assigning logical names to destinations instead of using a real JCA endpoint; we may think of message destination references as virtual end-points (see Figure 8-6).
Figure 8-6. Sending events to the message-link abstraction, which delegates to a real destination
When we discussed this method earlier in the topic, we never really mentioned where a user registration message was being sent; it could go to an emailing system or some other vendor listening in. However, message linking makes sure that the message goes directly to an explicit message-driven bean that we deploy.
For example, consider the following stateless session bean:
Session Beans Should Not Receive Messages
Session beans respond to calls from EJB clients, and they cannot be programmed to respond to JMS messages; this is instead the motivation behind the message-driven bean component type. It’s impossible to write a session or entity bean that is driven by incoming messages, though it is possible to develop a session bean that can consume a JMS message from a business method; in this case, an EJB client must call the method first. For example, when the business method on a HypotheticalEJB is called, it sets up a JMS session and then attempts to read a message from a queue:
The message consumer is used to proactively fetch a message from the queue. Although this operation has been programmed correctly, it is dangerous because a call to the MessageConsumer.receive() method blocks the thread until a message becomes available. If a message is never delivered, the thread is blocked indefinitely! If no one ever sends a message to the queue, the MessageConsumer just sits 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 MessageConsumer 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 none is waiting, thus avoiding a prolonged thread block. However, this operation is still dangerous. 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.
The moral of the story is simple: don’t write convoluted code trying to force session beans to receive messages. If you need to receive messages, use a message-driven bean. MDBs are specially designed to consume JMS messages.
The JMS APIs
We’ve alluded to some examples of JMS code earlier. Now let’s get a better understanding of the underlying mechanics.
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. A JMS connection factory makes the connection to the provider possible, and the destination address is identified by a Topic object. Both the connection factory and the Topic object may be obtained either by using @javax.annotation.Resource to inject these objects directly (into a JEE managed object such as an EJB or Servlet) or by using a manual JNDI lookup.
The ConnectionFactory is similar to a DataSource in JDBC. Just as the DataSource provides a JDBC connection to a database, the ConnectionFactory provides a JMS connection to a message router.*
The Topic object itself represents a network-independent destination to which the message will be addressed. In JMS, messages aren’t sent directly to applications; they’re sent to topics or queues. 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 a virtual channel. A queue is another type of destination that we’ll discuss in detail later.
Connection and Session
The ConnectionFactory is used to create a Connection, which is an actual connection to the JMS provider:
Once you have a Connection, you can use it to create a Session. A Session allows you to group the actions of sending and receiving messages. In this case, you need only a single Session. Using multiple Sessions is helpful if you wish to produce and consume messages in different threads. Session objects use a single-threaded model, which prohibits concurrent access to a single Session from multiple threads. The thread that creates a Session is usually the thread that uses that Session’s producers and consumers
(i.e., MessageProducer and MessageConsumer objects). If you wish to produce and consume messages using multithreading, you must create a different Session object for each thread.
The EJB 3.1 specification’s Section 13.3.5 stipulates that 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 acknowledge Mode, but since they are supposed to be ignored, it should not matter what you use.
It’s good programming practice to close a Connection after it has been used:
The Session is used to create a MessageProducer, which sends messages from the caller to the destination specified by the Topic object. Any JMS clients that subscribe to that topic will receive a copy of the message:
In JMS, a message is a Java object with two parts: a header and a message body. The header is composed of delivery information and metadata, and the message body carries the application data, which can take several forms: text, Serializable objects, byte streams, etc. The JMS API defines several message types (TextMessage, MapMessage, ObjectMessage, and others) and provides methods for delivering messages to and receiving from other applications.
For example, we can send a new user registration using a MapMessage:
The attributes of MapMessage (userId, firstname, and email) can be accessed by name from those JMS clients that receive it. As an alternative, we could use the ObjectMessage type, which would allow us to send the entire User object as the message using Java serialization:
In addition to TextMessage, MapMessage, and ObjectMessage, JMS provides two other message types: StreamMessage and BytesMessage. StreamMessage can take the contents of an I/O stream as its payload. BytesMessage can take any array of bytes, which it treats as opaque data.
Example: The StatusUpdateEJBs
To get a better idea of how JMS is used, we can create a Java application whose sole purpose is to broadcast out a generic status; listeners on the target topic can then take whatever action they’d like. Perhaps one consumer would like to log the status update, while another might act as an adapter to a third-party service such as Facebook or Twitter. The post-conditions are not the concern of the message producer, whose responsibility is fulfilled once the status is published to the target JMS endpoint.
To act as listeners for these updates, we may easily plug in some MDBs as subscribers to the topic.
The application client for unit testing can take the form of any Java class. We use a JUnit test environment.
First, we have a simple value object to encapsulate the status update:
Our client will make use of the JMS and Naming APIs:
And using one method, we can publish to our topic:
Here we obtain the TopicConnectionFactory and Topic from the JNDI Context, which is created with vendor-specific properties.
Once the client has the TopicConnectionFactory and Topic, it creates a TopicConnection and a TopicSession; from there we may further create a developer through which we’ll push messages.
It’s worth noting that putting all of this logic in one location is guaranteed not to scale. In a real-world scenario, we should set up the session or developer once and reuse it within the same thread.
Now that we’ve broadcasted our current status, we should make some listeners to take action.
Create a base listener
Because we’ll make a couple of MDBs to listen for status updates, we should centralize some of the logic to extract the contents from the incoming JMS message. For this we declare a class that implements MessageListener:
public abstract class StatusUpdateBeanBase implements MessageListener
This will contain a contract to be fulfilled by subclasses:
The real meat of this class is done in onMessage, where we unwrap the status and delegate along to updateStatus:
Now we can make some MDBs to take advantage of the preceding logic, taking more specialized action:
This simple MDB does nothing more than log out the new status update it has received. The activation configuration properties denote that this listener is for a JMS Topic and that the topic can be found in JNDI at the “destination” property value.
Surely we can do something more interesting. Remember that producers don’t know or care who is listening to messages sent to a Topic, so we can create another listener and attach it. This one will propagate our status update to another service entirely. Twitter provides an external HTTP-based API, and we’ll leverage the Twitter4J library (http://yusuke.homeip.net/twitter4j/en/index.html) to access it:
Surely we can do something more interesting. Remember that producers don’t know or care who is listening to messages sent to a Topic, so we can create another listener and attach it. This one will propagate our status update to another service entirely. Twitter provides an external HTTP-based API, and we’ll leverage the Twitter4J library (http://yusuke.homeip.net/twitter4j/en/index.html) to access it:
Note that the activation configuration properties are the same as in the logging MDB. We’re listening to the same Topic and receiving the same messages:
MDBs, like any EJB component type, receive lifecycle events. In this one, we’ll initialize the Twitter client:
This leaves us with the simple task of using the Twitter client to update the status as detailed in the incoming message.