CHAPTER 13

image

Messaging

Most of the communications between components that you have seen so far are synchronous: one class calls another, a managed bean invokes an EJB, which calls an entity, and so on. In such cases, the invoker and the target have to be up and running for the communication to succeed, and the invoker must wait for the target to complete before proceeding. With the exception of asynchronous calls in EJB (thanks to the @Asynchronous annotation), most Java EE components use synchronous calls (local or remote). When we talk about messaging, we mean loosely coupled, asynchronous communication between components.

Message-oriented middleware ) (MOM) is software (a provider) that enables the exchange of messages asynchronously between heterogeneous systems. It can be seen as a buffer between systems that produce and consume messages at their own pace (e.g., one system is 24/7, the other runs only at night). It is inherently loosely coupled, as producers don’t know who is at the other end of the communication channel to consume the message and perform actions. The producer and the consumer do not have to be available at the same time in order to communicate. In fact, they do not even know about each other, as they use an intermediate buffer. In this respect, MOM differs completely from technologies, such as remote method invocation (RMI), which require an application to know the signature of a remote application’s methods.

Today, a typical organization has many applications, often written in different languages, that perform well-defined tasks. MOM is based on an asynchronous interaction model so it allows these applications to work independently and, at the same time, form part of an information workflow process. Messaging is a good solution for integrating existing and new applications in a loosely coupled, asynchronous way, as long as the producer and consumer agree on the message format and the intermediate destination. This communication can be local within an organization or distributed among several external services.

Understanding Messaging

MOM (Message-oriented middleware), which has been around for a while, uses a special vocabulary. When a message is sent, the software that stores the message and dispatches it is called a provider (or sometimes a broker). The message sender is called a producer, and the location where the message is stored is called a destination. The component receiving the message is called a consumer. Any component interested in a message at that particular destination can consume it. Figure 13-1 illustrates these concepts.

9781430246268_Fig13-01.jpg

Figure 13-1. MOM architecture

In Java EE, the API that deals with these concepts is called Java Message Service (JMS). It has a set of interfaces and classes that allow you to connect to a provider, create a message, send it, and receive it. JMS doesn’t physically carry messages, it’s just an API; it requires a provider that is in charge of handling messages. When running in an EJB container, Message-Driven Beans (MDBs) can be used to receive messages in a container-managed way.

At a high level, a messaging architecture consists of the following components (see Figure 13-2):

  • A provider: JMS is only an API, so it needs an underlying implementation to route messages, that is, the provider (a.k.a. a message broker). The provider handles the buffering and delivery of messages.
  • Clients: A client is any Java application or component that produces or consumes a message to/from the provider. “Client” is the generic term for producer, sender, publisher, consumer, receiver, or subscriber.
  • Messages: These are the objects that clients send to or receive from the provider.
  • Administered objects: A message broker must provide administered objects to the client (connection factories and destinations) either through JNDI lookups or injection (as you’ll see later).

9781430246268_Fig13-02.jpg

Figure 13-2. Messaging architecture

The messaging provider enables asynchronous communication by providing a destination where messages can be held until they can be delivered to a client (see Figure 13-1). There are two different types of destination, each applying to a specific messaging model:

  • The point-to-point (P2P) model: In this model, the destination used to hold messages is called a queue. When using point-to-point messaging, one client puts a message on a queue, and another client receives the message. Once the message is acknowledged, the message provider removes the message from the queue.
  • The publish-subscribe (pub-sub) model: The destination is called a topic. When using publish/subscribe messaging, a client publishes a message to a topic, and all subscribers to that topic receive the message.

Point-to-Point

In the P2P model, a message travels from a single producer to a single consumer. The model is built around the concept of message queues, senders, and receivers (see Figure 13-3). A queue retains the messages sent by the sender until they are consumed, and a sender and a receiver do not have timing dependencies. This means that the sender can produce messages and send them in the queue whenever he likes, and a receiver can consume them whenever he likes. Once the receiver is created, it will get all the messages that were sent to the queue, even those sent before its creation.

9781430246268_Fig13-03.jpg

Figure 13-3. P2P model

Each message is sent to a specific queue, and the receiver extracts the messages from the queue. Queues retain all messages sent until they are consumed or until they expire.

The P2P model is used if there is only one receiver for each message. Note that a queue can have multiple consumers, but once a receiver consumes a message from the queue, it is taken out of the queue, and no other consumer can receive it. In Figure 13-4, you can see one sender producing three messages.

9781430246268_Fig13-04.jpg

Figure 13-4. Multiple receivers

Note that P2P doesn’t guarantee messages are delivered in any particular order (the order is not defined). A provider might pick them in arrival order, or randomly, or some other way.

Publish-Subscribe

In the pub-sub model, a single message is sent by a single producer to potentially several consumers. The model is built around the concept of topics, publishers, and subscribers (Figure 13-5). Consumers are called subscribers because they first need to subscribe to a topic. The provider manages the subscribing/unsubscribing mechanism as it occurs dynamically.

9781430246268_Fig13-05.jpg

Figure 13-5. Pub-sub model

The topic retains messages until they are distributed to all subscribers. Unlike the P2P model, there is a timing dependency between publishers and subscribers; subscribers do not receive messages sent prior to their subscription, and, if the subscriber is inactive for a specified period, it will not receive past messages when it becomes active again. Note that this can be avoided, because the JMS API supports the concept of a durable subscriber, as you’ll later see.

Multiple subscribers can consume the same message. The pub-sub model can be used for broadcast-type applications, in which a single message is delivered to several consumers. In Figure 13-6, the publisher sends three messages that each subscriber will receive (in an undefined order).

9781430246268_Fig13-06.jpg

Figure 13-6. Multiple subscribers

Administered Objects

Administered objects are objects that are configured administratively, as opposed to programmatically. The message provider allows these objects to be configured, and makes them available in the JNDI namespace. Like JDBC datasources, administered objects are created only once. The two types of administered objects:

  • Connection factories: Used by clients to create a connection to a destination.
  • Destinations: Message distribution points that receive, hold, and distribute messages. Destinations can be queues (P2P) or topics (pub-sub).

Clients access these objects through portable interfaces by looking them up in the JNDI namespace or through injection. In GlassFish, there are several ways to create these objects as you’ll later see: by using the administration console, the asadmin command-line, or the REST interface. Since JMS 2.0 you can even use the @JMSConnectionFactoryDefinition and @JMSDestinationDefinition annotations to define programmatically these objects.

Message-Driven Beans

Message-Driven Beans (MDBs) are asynchronous message consumers that are executed inside an EJB container. As you’ve seen in Chapters 7 through 9, the EJB container takes care of several services (transactions, security, concurrency, message acknowledgment, etc.), while the MDB focuses on consuming messages. MDBs are stateless, meaning that the EJB container can have numerous instances, executing concurrently, to process messages coming in from various producers. Even if they look like stateless beans, client applications cannot access MDBs directly; the only way to communicate with an MDB is to send a message to the destination that the MDB is listening to.

In general, MDBs listen to a destination (queue or topic) and, when a message arrives, they consume and process it. They can also delegate business logic to other stateless session beans in a safe, transactional manner. Because they are stateless, MDBs do not maintain state across separate invocations from one message received to the next. MDBs respond to messages received from the container, whereas stateless session beans respond to client requests through an appropriate interface (local, remote, or no-interface).

Messaging Specifications Overview

Messaging in Java is mostly represented by JMS, which can be used in applications running in a standard (Java SE) or an enterprise (Java EE) environment. MDBs simply represent a way for stateless session EJBs to be message consumers and are bound to the EJB specification.

A Brief History of Messaging

Up until the late 1980s, companies did not have any easy way to link different applications. Developers had to write separate software adapters for systems to translate data from source applications into a format that the destination system could understand (and vice versa). Because of the disparity of servers’ processing capabilities and availabilities, buffers were created to de-couple the processing so that the overall time wouldn’t be prolonged. A lack of homogeneous transport protocols created low-level protocol adapters. Toward the end of the 1980s, middleware began to emerge, which solved these integration issues. The first MOMs were created as separate pieces of software that could sit in the middle of applications and manage the “plumbing” between systems. They were able to manage different platforms, different programming languages, various network protocols, and diverse hardware.

The JMS specification was first published in August 1998. It was created by the major middleware vendors to bring messaging capabilities to Java. JSR 914 went through minor changes (JMS 1.0.1, 1.0.2, and 1.0.2b) to finally reach the 1.1 version in April 2002. JMS 1.1 was integrated into J2EE 1.2 and has been a part of Java EE since. However, JMS and MDBs are not part of the Web Profile specification. This means they are only available on application servers implementing the full Java EE 7 platform.

What’s New in JMS 2.0?

JMS 1.1 didn’t change for more than a decade. Since Java EE 5 the APIs have slowly been modernized to fit the language changes (annotations, generics . . .) except for JMS. It was time for JMS to follow the same path, make use of annotations and simplify its API. In fact, several changes have been made to the JMS API to make it simpler and easier to use:

  • Connection, Session and other objects with a close() method now implement the java.jang.AutoCloseable interface to allow them to be used in a Java SE 7 try-with-resources statement
  • A new "simplified API" has been added which offers a simpler alternative to the standard and legacy APIs
  • A new method getBody has been added to allow an application to extract the body directly from a Message without the need to cast it first to an appropriate subtype
  • A set of new unchecked exceptions have been created, all extending from JMSRuntimeException
  • New send methods have been added to allow an application to send messages asynchronously

What’s New in EJB 3.2?

MDBs were introduced in EJB 2.0 and were improved with EJB 3.0 and the general Java EE 5 paradigm of “ease of use.” They were not internally modified as they continued to be message consumers, but the introduction of annotations and configuration by exception made them much easier to write. The new EJB 3.2 specification (JSR 345) brought some changes to MDBs by adding more portable configuration (more on that later).

As you’ve seen in Chapter 7, asynchronous calls are now possible within stateless session beans (using the @Asynchronous annotation). In previous versions of Java EE, it was impossible to have asynchronous calls between EJBs. Therefore, the only possible solution was to use JMS and MDBs—expensive, as many resources had to be used (JMS destinations, connections, factories, etc.) just to call a method asynchronously. Today asynchronous calls are possible between session beans without the need for MDBs, allowing them to focus on integrating systems through messaging.

Reference Implementation

Open Message Queue (OpenMQ) is the reference implementation of JMS. It has been open source since 2006 and can be used in stand-alone JMS applications or embedded in an application server. OpenMQ is the default messaging provider for GlassFish and, as this book is being written, is reaching version 5.0. It also adds many nonstandard features such as the Universal Message Service (UMS), wildcard topic destinations, XML message validation, clustering, and more.

Java Messaging Service API

JMS is a standard Java API that allows applications to create, send, receive, and read messages asynchronously. It defines a common set of interfaces and classes that allow programs to communicate with other message providers. JMS is analogous to JDBC: the latter connects to several databases (Derby, MySQL, Oracle, DB2, etc.), and JMS connects to several providers (OpenMQ, MQSeries, SonicMQ, etc.).

The JMS API has evolved ever since its creation. For historical reasons JMS offers three alternative sets of interfaces for producing and consuming messages. These very different interfaces evolved in JMS 1.0, 1.1 and 2.0 and are referred to as legacy API, classic API and simplified API.

JMS 1.0 made a clear difference between the point-to-point and publish-subscribe model. It defined two domain-specific APIs, one for point-to-point (queues) and one for pub-sub (topics). That’s why you can find QueueConnectionFactory and TopicConnectionFactory API instead of the generic ConnectionFactory for example. Note also the different vocabulary; a consumer is called a receiver in P2P and a subscriber in pub-sub.

The JMS 1.1 API (referred to as the classic API) provided a unified set of interfaces that can be used with both P2P and pub-sub messaging. Table 13-1 shows the generic name of an interface (e.g., Session) and the legacy names for each model (QueueSession, TopicSession).

Table 13-1. Interfaces Depending on JMS Version

Table13-1.jpg

But JMS 1.1 was still a verbose and low-level API compared to JPA or EJB. JMS 2.0 introduces a simplified API that offers all the features of the classic API but requires fewer interfaces and is simpler to use. Table 13-1 highlights the differences between these APIs (all located under the javax.jms package).

I will not discuss the legacy API but I need to introduce the classic API; first of all because you will still find millions of lines of code using the JMS 1.1 classic API and second, because technically the simplified API relies on the classical one.

Classic API

The JMS classic API provides classes and interfaces for applications that require a messaging system (see Figure 13-7). This API enables asynchronous communication between clients by providing a connection to the provider, and a session where messages can be created and sent or received. These messages can contain text or other different kinds of objects.

9781430246268_Fig13-07.jpg

Figure 13-7. JMS Classic API

ConnectionFactory

Connection factories are administered objects that allow an application to connect to a provider by creating a Connection object programmatically. A javax.jms.ConnectionFactory is an interface that encapsulates the configuration parameters that have been defined by an administrator.

To use an administered object such as a ConnectionFactory, the client needs to perform a JNDI lookup (or use injection). For example, the following code fragment obtains the JNDI InitialContext object and uses it to look up a ConnectionFactory by its JNDI name:

Context ctx = new InitialContext();
ConnectionFactory ConnectionFactory =
                 (ConnectionFactory) ctx.lookup("jms/javaee7/ConnectionFactory");

The methods available in this interface (see Listing 13-1) are createConnection methods that return a Connection object and new JMS 2.0 createContext methods that return a JMSContext. You can create a Connection or a JMSContext either with the default user identity or by specifying a username and password.

Listing 13-1.  ConnectionFactory Interface

public interface ConnectionFactory {
 
  Connection createConnection() throws JMSException;
  Connection createConnection(String userName, String password) throws JMSException;
  JMSContext createContext();
  JMSContext createContext(String userName, String password);
  JMSContext createContext(String userName, String password, int sessionMode);
  JMSContext createContext(int sessionMode);
}

Destination

A destination is an administered object that contains provider-specific configuration information such as the destination address. But this configuration is hidden from the JMS client by using the standard javax.jms.Destination interface. Like the connection factory, a JNDI lookup is needed to return such objects:

Context ctx = new InitialContext();
Destination queue = (Destination) ctx.lookup("jms/javaee7/Queue");

Connection

The javax.jms.Connection object, which you create using the createConnection() method of the connection factory, encapsulates a connection to the JMS provider. Connections are thread-safe and designed to be shareable, as opening a new connection is resource intensive. However, a session (javax.jms.Session) provides a single-threaded context for sending and receiving messages, using a connection to create one or more sessions. Once you have a connection factory, you can use it to create a connection as follows:

Connection connection = connectionFactory.createConnection();

Before a receiver can consume messages, it must call the start() method. If you need to stop receiving messages temporarily without closing the connection, you can call the stop() method:

connection.start();
connection.stop();

When the application completes, you need to close any connections created. Closing a connection also closes its sessions and its producers or consumers:

connection.close();

Session

You create a session from the connection using the createSession() method. A session provides a transactional context in which a set of messages to be sent or received are grouped in an atomic unit of work, meaning that, if you send several messages during the same session, JMS will ensure that either they all are sent or none. This behavior is set at the creation of the session:

Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);

The first parameter of the method specifies whether or not the session is transactional. In the code, the parameter is set to true, meaning that the request for sending messages won’t be realized until either the session’s commit() method is called or the session is closed. If the parameter was set to false, the session would not be transactional, and messages would be sent as soon as the send() method is invoked. The second parameter means that the session automatically acknowledges messages when they have been received successfully. A session is single-threaded and is used to create messages, producers, and consumers.

Messages

To communicate, clients exchange messages; one producer will send a message to a destination, and a consumer will receive it. Messages are objects that encapsulate information and are divided in three parts (see Figure 13-8):

  • A header: contains standard information for identifying and routing the message.
  • Properties: are name-value pairs that the application can set or read. Properties also allow destinations to filter messages based on property values.
  • A body: contains the actual message and can take several formats (text, bytes, object, etc.).

9781430246268_Fig13-08.jpg

Figure 13-8. Structure of a JMS message

Header

The header has predefined name-value pairs, common to all messages that both clients and providers use to identify and route messages. They can be seen as message metadata as they give information about the message. Each field has associated getter and setter methods defined in the javax.jms.Message interface. Some header fields are intended to be set by a client, but many are set automatically by the send() or the publish() method. Table 13-2 describes each JMS message header field.

Table 13-2. Fields Contained in the Header

Field Description Set By
JMSDestination This indicates the destination to which the message is being sent. send() or publish() method
JMSDeliveryMode JMS supports two modes of message delivery. PERSISTENT mode instructs the provider to ensure the message is not lost in transit due to a failure. NON_PERSISTENT mode is the lowest-overhead delivery mode because it does not require the message to be logged to a persistent storage. send() or publish() method
JMSMessageID This provides a value that uniquely identifies each message sent by a provider. send() or publish() method
JMSTimestamp This contains the time a message was handed off to a provider to be sent. send() or publish() method
JMSCorrelationID A client can use this field to link one message with another such as linking a response message with its request message. Client
JMSReplyTo This contains the destination where a reply to the message should be sent. Client
JMSRedelivered This Boolean value is set by the provider to indicate whether a message has been redelivered. Provider
JMSType This serves as a message type identifier. Client
JMSExpiration When a message is sent, its expiration time is calculated and set based on the time-to-live value specified on the send() method. send() or publish() method
JMSPriority JMS defines a 10-level priority value, with 0 as the lowest priority and 9 as the highest. send() or publish() method

Properties

In addition to the header fields, the javax.jms.Message interface supports property values, which are just like headers, but explicitly created by the application, instead of being standard across messages. This provides a mechanism for adding optional header fields to a message that a client will choose to receive or not via selectors. Property values can be boolean, byte, short, int, long, float, double, and String. The code to set and get properties looks like this:

message. set FloatProperty("orderAmount", 1245.5f);
message. get FloatProperty("orderAmount");

Body

The body of a message is optional, and contains the data to send or receive. Depending on the interface that you use, it can contain different formats of data, as listed in Table 13-3.

Table 13-3. Types of Messages

Interface Description
StreamMessage A message whose body contains a stream of Java primitive values. It is filled and read sequentially.
MapMessage A message whose body contains a set of name-value pairs where names are strings and values are Java primitive types.
TextMessage A message whose body contains a string (for example, it can contain XML).
ObjectMessage A message that contains a serializable object or a collection of serializable objects.
BytesMessage A message that contains a stream of bytes.

It is possible to create your own message format, if you extend the javax.jms.Message interface. Note that, when a message is received, its body is read-only. Depending on the message type, you have different methods to access its content. A text message will have a getText() and setText() method, an object message will have a getObject() and setObject(), and so forth:

textMessage.setText("This is a text message");
textMessage.getText();
bytesMessage.readByte();
objectMessage.getObject();

Note that since JMS 2.0, the new method <T> T getBody(Class<T> c) returns the message body as an object of the specified type.

Sending and Receiving a Message with Classic API

Now let’s take a look at a simple example to get an idea of how to use the classic JMS API to send and receive a message. JMS employs producers, consumers, and destinations. The producer sends a message to the destination, where the consumer is waiting for the message to arrive. Destinations can be of two kinds: queues (for point-to-point communication) and topics (for publish-subscribe communication). In Listing 13-2, a producer sends a text message to a queue to which the consumer is listening.

Listing 13-2.  The Producer Class Produces a Message into a Queue using the Classic API

public class Producer {
 
  public static void main(String[] args) {
 
    try {
      // Gets the JNDI context
      Context jndiContext = new InitialContext();
 
      // Looks up the administered objects
      ConnectionFactory connectionFactory = (ConnectionFactory)
                        jndiContext.lookup(" jms/javaee7/ConnectionFactory ");
      Destination queue = (Destination) jndiContext.lookup(" jms/javaee7/Queue ");
 
      // Creates the needed artifacts to connect to the queue
      Connection connection = connectionFactory.createConnection();
      Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
      MessageProducer producer = session.createProducer(queue);
 
      // Sends a text message to the queue
      TextMessage message = session.createTextMessage("Text message sent at " + new Date());
      producer.send (message);
 
      connection.close();
 
    } catch ( NamingException | JMSException e ) {
      e.printStackTrace();
    }
  }
}

The code in Listing 13-2 represents a Producer class that has a main() method only. In this method, the first thing that occurs is that a JNDI context is instantiated and used to obtain a ConnectionFactory and a Destination. Connection factories and destinations (queues and topics) are called administered objects; they have to be created and declared in the message provider (in our case, OpenMQ in GlassFish). They both have a JNDI name (e.g., the queue is called jms/javaee7/Queue) and need to be looked up in the JNDI tree.

When the two administered objects are obtained, the Producer class uses the ConnectionFactory to create a Connection from which a Session is obtained. With this session, a MessageProducer and a message are created on the destination queue (session.createProducer(queue)). The producer then sends this message (of type text). Note that this main class catches the JNDI NamingException as well as the checked JMSException.

Fortunately, once you’ve written this code to send a message, the code to receive it looks almost the same. In fact, the first lines of the Consumer class in Listing 13-3 are exactly the same: create a JNDI context, lookup for the connection factory and the destination, and then connect. The only differences are that a MessageConsumer is used instead of a MessageProducer, and that the receiver enters an infinite loop to listen to the queue (you’ll later see that this loop can be avoided by using the more standard message listener). When the message arrives, it is consumed and the content displayed.

Listing 13-3.  The Consumer Class Consumes a Message from a Queue using the Classic API

public class Consumer {
 
  public static void main(String[] args) {
 
    try {
      // Gets the JNDI context
      Context jndiContext = new InitialContext();
 
      // Looks up the administered objects
      ConnectionFactory connectionFactory = (ConnectionFactory)
                        jndiContext.lookup(" jms/javaee7/ConnectionFactory ");
      Destination queue = (Destination) jndiContext.lookup(" jms/javaee7/Queue ");
 
      // Creates the needed artifacts to connect to the queue
      Connection connection = connectionFactory.createConnection();
      Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
      MessageConsumer consumer = session.createConsumer(queue);
 
      connection.start();
 
      // Loops to receive the messages
      while (true) {
        TextMessage message = (TextMessage) consumer.receive() ;
        System.out.println("Message received: " + message.getText());
      }
 
    } catch ( NamingException | JMSException e ) {
      e.printStackTrace();
    }
  }
}

Simplified API

As you can see, the code in Listing 13-2 and 13-3 is quite verbose and low level. You need several artifacts to be able to produce or consume a message (ConnectionFactory, Connection, Session . . .). On top of that you also need to deal with the JMSException which is a checked exception (JMSException has several sub classes). This API was created with JMS 1.1 in 2002 and was not changed until JMS 2.0.

JMS 2.0 introduces a new simplified API, which consists mainly of three new interfaces (JMSContext, JMSProducer and JMSConsumer). These interfaces rely internally on the ConnectionFactory and other classic APIs but leave the boilerplate code aside. Thanks to the new JMSRuntimeException, which is an unchecked exception, the code to send or receive a message is now much easier to write and read (code examples to follow).

Figure 13-9 shows a simplified class diagram of this new API. Note that the legacy, classic, and simplified APIs are all under the javax.jms package.

9781430246268_Fig13-09.jpg

Figure 13-9. JMS Simplified API

The simplified API provides the same messaging functionality as the classic API but requires fewer interfaces and is simpler to use. These main interfaces are:

  • JMSContext: active connection to a JMS provider and a single-threaded context for sending and receiving messages
  • JMSProducer: object created by a JMSContext that is used for sending messages to a queue or topic
  • JMSConsumer: object created by a JMSContext that is used for receiving messages sent to a queue or topic

JMSContext

The JMSContext is the main interface in the simplified JMS API introduced by JMS 2.0. It combines the functionality of two separate objects from the JMS 1.1 classic API: a Connection (the physical link to the JMS provider) and a Session (a single-threaded context for sending and receiving messages).

A JMSContext may be created by the application by calling one of several createContext methods on a ConnectionFactory (see Listing 13-1) and then closed (i.e., application-managed). Alternatively, if the application is running in a container (EJB or Web), the JMSContext can be injected using the @Inject annotation (i.e., container-managed).

When an application needs to send messages it uses the createProducer method to create a JMSProducer, which provides methods to configure and send messages. Messages may be sent either synchronously or asynchronously. To receive messages, an application can use one of several createConsumer methods to create a JMSConsumer. Table 13-4 shows you a subset of the JMSContext API.

Table 13-4. Subset of the JMSContext API

Property Description
void start() Starts (or restarts) delivery of incoming messages
void stop() Temporarily stops the delivery of incoming messages
void close() Closes the JMSContext
void commit() Commits all messages done in this transaction and releases any locks currently held
void rollback() Rolls back any messages done in this transaction and releases any locks currently held
BytesMessage createBytesMessage() Creates a BytesMessage object
MapMessage createMapMessage() Creates a MapMessage object
Message createMessage() Creates a Message object
ObjectMessage createObjectMessage() Creates an ObjectMessage object
StreamMessage createStreamMessage() Creates a StreamMessage object
TextMessage createTextMessage() Creates a TextMessage object
Topic createTopic(String topicName) Creates a Topic object
Queue createQueue(String queueName) Creates a Queue object
JMSConsumer createConsumer(Destination destination) Creates a JMSConsumer for the specified destination
JMSConsumer createConsumer(Destination destination, String messageSelector) Creates a JMSConsumer for the specified destination, using a message selector
JMSProducer createProducer() Creates a new JMSProducer object which can be used to configure and send messages
JMSContext createContext(int sessionMode) Creates a new JMSContext with the specified session mode

JMSProducer

A JMSProducer is used to send messages on behalf of a JMSContext. It provides various send methods to send a message to a specified destination. An instance of JMSProducer is created by calling the createProducer method on a JMSContext. It also provides methods to allow send options, message properties and message headers (see Figure 13-8) to be specified prior to sending the message. Table 13-5 shows a subset of the JMSProducer API.

Table 13-5. Subset of the JMSProducer API

Property Description
get/set[Type]Property Sets and returns a message property where [Type] is the type of the property and can be Boolean, Byte, Double, Float, Int, Long, Object, Short, String
JMSProducer clearProperties() Clears any message properties set
Set<String> getPropertyNames() Returns an unmodifiable Set view of the names of all the message properties that have been set
boolean propertyExists(String name) Indicates whether a message property with the specified name has been set
get/set[Message Header] Sets and returns a message header where [Message Header] can be DeliveryDelay, DeliveryMode, JMSCorrelationID, JMSReplyTo, JMSType, Priority, TimeToLive
JMSProducer send(Destination destination, Message message) Sends a message to the specified destination, using any send options, message properties and message headers that have been defined
JMSProducer send(Destination destination, String body) Sends a TextMessage with the specified body to the specified destination

JMSConsumer

A JMSConsumer is used to receive messages from a queue or topic. It is created with one of the createConsumer methods on a JMSContext by passing a Queue or a Topic. As you will later see, a JMSConsumer can be created with a message selector so it can restrict messages delivered.

A client may receive a message synchronously or asynchronously as they arrive. For asynchronous delivery, a client can register a MessageListener object with a JMSConsumer. As messages arrive, the provider delivers them by calling the MessageListener's onMessage method. Table 13-6 shows you a subset of the JMSConsumer API.

Table 13-6. Subset of the JMSConsumer API

Property Description
void close() Closes the JMSConsumer
Message receive() Receives the next message produced
Message receive(long timeout) Receives the next message that arrives within the specified timeout interval
<T> T receiveBody(Class<T> c) Receives the next message produced and returns its body as an object of the specified type
Message receiveNoWait() Receives the next message if one is immediately available
void setMessageListener(MessageListener listener) Sets the MessageListener
MessageListener getMessageListener() Gets the MessageListener
String getMessageSelector() Gets the message selector expression

Writing Message Producers

The new JMS simplified API allows you to write producers and consumers in a less verbose manner than with the classic API. But it still needs both of the administered objects: ConnectionFactory and Destination. Depending if you are running outside or inside a container (EJB, Web or ACC) you will either use JNDI lookups or injection. As you’ve seen previously, the JMSContext API is the central API to produce and consume messages. If your application runs outside a container you will need to manage the lifecycle of the JMSContext (by creating and closing it programmatically). If you run inside a container you can just inject it and leave the container to manage its lifecycle.

Producing a Message outside a Container

A message producer (JMSProducer) is an object created by the JMSContext and is used to send messages to a destination. The following steps explain how to create a producer that sends a message to a queue (see Listing 13-4) outside any container (in a pure Java SE environment):

  • Obtain a connection factory and a queue using JNDI lookups
  • Create a JMSContext object using the connection factory (notice the try-with-resources statement that will automatically close the JMSContext object)
  • Create a javax.jms.JMSProducer using the JSMContext object
  • Send a text message to the queue using the JMSProducer.send() method

Listing 13-4.  The Producer Class Produces a Message into a Queue

public class Producer {
 
  public static void main(String[] args) {
 
    try {
      // Gets the JNDI context
      Context jndiContext = new InitialContext();
 
      // Looks up the administered objects
      ConnectionFactory connectionFactory = (ConnectionFactory)
                        jndiContext.lookup(" jms/javaee7/ConnectionFactory ");
      Destination queue = (Destination) jndiContext.lookup(" jms/javaee7/Queue ");
 
      // Sends a text message to the queue
      try ( JMSContext context = connectionFactory.createContext() ) {
        context.createProducer().send (queue, "Text message sent at " + new Date());
      }
 
    } catch ( NamingException e) {
      e.printStackTrace();
    }
  }
}

If you compare the code in Listing 13-4 with the one using the classic API in Listing 13-2, you will notice that the code is less verbose. Exception handling is also neater as the new JMSRuntimeException is used in the new API and is an unchecked exception.

Producing a Message inside a Container

Connection factories and destinations are administered objects that reside in a message provider and have to be declared in the JNDI namespace, which is why you use the JNDI API to look them up. When the client code runs inside a container, dependency injection can be used instead. Java EE 7 has several containers: EJB, servlet, and application client container (ACC). If the code runs in one of these containers, the @Resource annotation can be used to inject a reference to that resource by the container. With Java EE 7, using resources is much easier, as you don’t have the complexity of JNDI or are not required to configure resource references in deployment descriptors. You just rely on the container injection capabilities.

Table 13-7 lists the attributes that belong to the @Resource annotation.

Table 13-7. API of the @javax.annotation.Resource Annotation

Element Description
name The JNDI name of the resource (the name is implementation specific and not portable)
type The Java type of the resource (e.g., javax.sql.DataSource or javax.jms.Topic)
authenticationType The authentication type to use for the resource (either the container or the application)
shareable Whether the resource can be shared
mappedName A product-specific name that the resource should map to
lookup The JNDI name of a resource that the resource being defined will be bound to. It can link to any compatible resource using the portable JNDI names
description Description of the resource

To use the @Resource annotation let’s take the example of the producer in Listing 13-4, change it to a stateless session bean and use injection instead of JNDI lookups. In Listing 13-4, both the connection factory and the queue are looked up using JNDI. In Listing 13-5, the JNDI name is on the @Resource annotation. When the ProducerEJB runs in a container, references of ConnectionFactory and Queue are injected at initialization.

Listing 13-5.  The ProducerEJB Running inside a Container and using @Resource

@Stateless
public class ProducerEJB {
 
  @Resource (lookup = " jms/javaee7/ConnectionFactory ")
  private ConnectionFactory connectionFactory;
  @Resource (lookup = " jms/javaee7/Queue ")
  private Queue queue;
 
  public void sendMessage() {
 
    try ( JMSContext context = connectionFactory.createContext() ) {
      context.createProducer().send (queue, "Text message sent at " + new Date());
    }
  }
}

The code in Listing 13-5 is simpler than the one in Listing 13-4 because it doesn’t deal with JNDI lookups or the JNDI NamingException. The container injects the administered objects once the EJB is initialized.

Producing a Message inside a Container with CDI

When the producer is executed in a container (EJB or Servlet container) with CDI enabled, it can inject the JMSContext. The container will then manage its lifecycle (no need to create or close the JMSContext). This can be done thanks to the @Inject and @JMSConnectionFactory annotations.

The annotation javax.jms.JMSConnectionFactory may be used to specify the JNDI lookup name of the ConnectionFactory used to create the JMSContext (see Listing 13-6). If the JMSConnectionFactory annotation is omitted, then the platform default JMS connection factory will be used.

Listing 13-6.  A Managed Bean Producing a Message using @Inject

public class Producer {
 
  @Inject
  @JMSConnectionFactory (" jms/javaee7/ConnectionFactory ")
  private JMSContext context;
  @Resource (lookup = " jms/javaee7/Queue ")
  private Queue queue;
 
  public void sendMessage() {
    context.createProducer().send (queue, "Text message sent at " + new Date());
  }
}

The code in Listing 13-6 is quite minimalist. The container does all the work of injecting the needed components and managing their lifecycle. As a developer you just need one line of code to send a message.

The annotation javax.jms.JMSPasswordCredential can also be used to specify a user name and password for when the JMSContext is created:

@Inject
@JMSConnectionFactory("jms/connectionFactory")
@JMSPasswordCredential(userName="admin",password="mypassword")
private JMSContext context;

Writing Message Consumers

A client uses a JMSConsumer to receive messages from a destination. A JMSConsumer is created by passing a Queue or Topic to the JMSContext’s createConsumer() method. Messaging is inherently asynchronous, in that there is no timing dependency between producers and consumers. However, the client itself can consume messages in two ways:

  • Synchronously: A receiver explicitly fetches the message from the destination by calling the receive() method.
  • Asynchronously: A receiver decides to register to an event that is triggered when the message arrives. It has to implement the MessageListener interface, and, whenever a message arrives, the provider delivers it by calling the onMessage() method.

Figure 13-10 illustrates these two types of consumer.

9781430246268_Fig13-10.jpg

Figure 13-10. Synchronous and asynchronous consumers

Synchronous Delivery

A synchronous consumer needs to start a JMSContext, loop to wait until a new message arrives, and request the arrived message using one of its receive() methods (see Table 13-6). There are several variations of receive() that allow a client to pull or wait for the next message. The following steps explain how you can create a synchronous consumer that consumes a message from a queue (see Listing 13-7):

  • Obtain a connection factory and a topic using JNDI lookups (or injection)
  • Create a JMSContext object using the connection factory
  • Create a javax.jms.JMSConsumer using the JSMContext object
  • Loop and call the receive() method (or in this case receiveBody) on the consumer object. The receive() methods block if the queue is empty and wait for a message to arrive. Here, the infinite loop waits for other messages to arrive

Listing 13-7.  The Consumer Class Consumes Messages in a Synchronous Manner

public class Consumer {
 
  public static void main(String[] args) {
 
    try {
      // Gets the JNDI context
      Context jndiContext = new InitialContext();
 
      // Looks up the administered objects
      ConnectionFactory connectionFactory = (ConnectionFactory)
                        jndiContext.lookup(" jms/javaee7/ConnectionFactory ");
      Destination queue = (Destination) jndiContext.lookup(" jms/javaee7/Queue ");
 
      // Loops to receive the messages
      try ( JMSContext context = connectionFactory.createContext() ) {
        while (true) {
          String message = context.createConsumer(queue).receiveBody(String.class) ;
        }
      }
 
    } catch ( NamingException e) {
      e.printStackTrace();
    }
  }
}

Again, if you compare the code in Listing 13-7 with the one using the classic API in Listing 13-3, you will see how the new simplified API is easier to use and more expressive.

image Note  Just like producers that can use injection with @Resource, @Inject or @JMSConnectionFactory when executed inside a container (see Listing 13-5 and 13-6), consumers can benefit from the same functionalities. Here I am just showing how a consumer can receive a message in a pure Java SE environment, but you can guess the simplifications made to the code if running inside a container and using injection.

Asynchronous Delivery

Asynchronous consumption is based on event handling. A client can register an object (including itself) that implements the MessageListener interface. A message listener is an object that acts as an asynchronous event handler for messages. As messages arrive, the provider delivers them by calling the listener’s onMessage() method, which takes one argument of type Message. With this event model, the consumer doesn’t need to loop indefinitely to receive a message. MDBs use this event model (more on that later).

The following steps describe the process used to create an asynchronous message listener (see Listing 13-8):

  • The class implements the javax.jms.MessageListener interface, which defines a single method called onMessage()
  • Obtain a connection factory and a topic using JNDI lookups (or injection)
  • Create a javax.jms.JMSConsumer using the JSMContext object
  • Call the setMessageListener() method, passing an instance of a MessageListener interface (in Listing 13-8, the Listener class itself implements the MessageListener interface)
  • Implement the onMessage() method and process the received message. Each time a message arrives, the provider will invoke this method, passing the message

Listing 13-8.  The Consumer Is a Message Listener

public class Listener implements MessageListener {
 
  public static void main(String[] args) {
 
    try {
      // Gets the JNDI context
      Context jndiContext = new InitialContext();
 
      // Looks up the administered objects
      ConnectionFactory connectionFactory = (ConnectionFactory)
                        jndiContext.lookup("jms/javaee7/ConnectionFactory");
      Destination queue = (Destination) jndiContext.lookup("jms/javaee7/Queue");
 
      try (JMSContext context = connectionFactory.createContext()) {
        context.createConsumer(queue). setMessageListener(new Listener()) ;
      }
 
    } catch (NamingException e) {
      e.printStackTrace();
    }
  }
 
  public void onMessage (Message message) {
    System.out.println("Async Message received: " + message.getBody(String.class) );
  }
}

Reliability Mechanisms

You’ve seen how to connect to a provider, create different types of messages, send them to queues or topics, and receive them. But what if you rely heavily on JMS and need to ensure reliability or other advanced features? JMS defines several levels of reliability to ensure your message is delivered, even if the provider crashes or is under load, or if destinations are filled with messages that should have expired. The mechanisms for achieving reliable message delivery are as follows:

  • Filtering messages: Using selectors you can filter messages you want to receive
  • Setting message time-to-live: Set an expiration time on messages so they are not delivered if they are obsolete
  • Specifying message persistence: Specify that messages are persistent in the event of a provider failure
  • Controlling acknowledgment: Specify various levels of message acknowledgment
  • Creating durable subscribers: Ensure messages are delivered to an unavailable subscriber in a pub-sub model
  • Setting priorities: Set the priority for delivering a message

Filtering messages

Some messaging applications need to filter the messages they receive. When a message is broadcast to many clients, it becomes useful to set criteria so that it is only consumed by certain receivers. This eliminates both the time and bandwidth the provider would waste transporting messages to clients that don’t need them.

You’ve seen that messages are composed of three parts: header, properties, and body (see Figure 13-8). The header contains a fixed number of fields (the message metadata), and the properties are a set of custom name-value pairs that the application can use to set any values. Selection can be done on those two areas. Producers set one or several property values or header fields, and the consumer specifies message selection criteria using selector expressions. Only messages that match the selector are delivered. Message selectors assign the work of filtering messages to the JMS provider, rather than to the application.

A message selector is a string that contains an expression. The syntax of the expression is based on a subset of the SQL92 conditional expression syntax and looks like this:

context.createConsumer(queue, " JMSPriority < 6 ").receive();
context.createConsumer(queue, " JMSPriority < 6 AND orderAmount < 200 ").receive();
context.createConsumer(queue, " orderAmount BETWEEN 1000 AND 2000 ").receive();

In the preceding code, a consumer is created with the JMSContext.createConsumer() method, passing a selector string. This string can use header fields (JMSPriority < 6) or custom properties (orderAmount < 200). The producer sets these properties into the message as follows:

context.createTextMessage(). setIntProperty("orderAmount", 1530) ;
context.createTextMessage(). setJMSPriority(5) ;

Selector expression can use logical operators (NOT, AND, OR), comparison operators (=, >, >=, <, <=, <>), arithmetic operators (+, -, *, /), expressions ([NOT] BETWEEN, [NOT] IN, [NOT] LIKE, IS [NOT] NULL), and so on.

Setting Message Time-to-Live

Under heavy load, a time-to-live can be set on messages to ensure that the provider will remove them from the destination when they become obsolete, by either using the JMSProducer API or setting the JMSExpiration header field. The JMSProducer has a setTimeToLive() method that takes a number of milliseconds:

context.createProducer(). setTimeToLive (1000).send(queue, message);

Specifying Message Persistence

JMS supports two modes of message delivery: persistent and nonpersistent. Persistent delivery ensures that a message is delivered only once to a consumer, whereas nonpersistent delivery requires a message be delivered once at most. Persistent delivery (which is the default) is more reliable, but at a performance cost, as it prevents losing a message if a provider failure occurs. The delivery mode can be specified by using the setDeliveryMode() method of the JMSProducer interface:

context.createProducer(). setDeliveryMode (DeliveryMode.NON_PERSISTENT).send(queue, message);

Controlling Acknowledgment

So far, the scenarios we’ve explored have assumed that a message is sent and received without any acknowledgment. But sometimes, you will want a receiver to acknowledge the message has been received (see Figure 13-11). An acknowledgment phase can be initiated either by the JMS provider or by the client, depending on the acknowledgment mode.

9781430246268_Fig13-11.jpg

Figure 13-11. A consumer acknowledging a message

In transactional sessions, acknowledgment happens automatically when a transaction is committed. If a transaction is rolled back, all consumed messages are redelivered. But in nontransactional sessions, an acknowledgment mode must be specified:

  • AUTO_ACKNOWLEDGE: The session automatically acknowledges the receipt of a message
  • CLIENT_ACKNOWLEDGE: A client acknowledges a message by explicitly calling the Message.acknowledge() method
  • DUPS_OK_ACKNOWLEDGE: This option instructs the session to lazily acknowledge the delivery of messages. This is likely to result in the delivery of some duplicate messages if the JMS provider fails, so it should be used only by consumers that can tolerate duplicate messages. If the message is redelivered, the provider sets the value of the JMSRedelivered header field to true

The following code uses the @JMSSessionMode annotation to set the acknowledgment mode to the JMSContext on the producer. The consumer explicitly acknowledges the message by calling the acknowledge() method:

// Producer
@Inject
@JMSConnectionFactory("jms/connectionFactory")
@JMSSessionMode(JMSContext.AUTO_ACKNOWLEDGE)
private JMSContext context;
...
context.createProducer().send(queue, message);
 
// Consumer
message. acknowledge ();

Creating Durable Consumers

The disadvantage of using the pub-sub model is that a message consumer must be running when the messages are sent to the topic; otherwise, it will not receive them. By using durable consumers, the JMS API provides a way to keep messages in the topic until all subscribed consumers receive them. With durable subscription, the consumer can be offline for some time, but, when it reconnects, it receives the messages that arrived during its disconnection. To achieve this, the client creates a durable consumer using the JMSContext:

context. createDurableConsumer (topic, "javaee7DurableSubscription").receive();

At this point, the client program starts the connection and receives messages. The name javaee7DurableSubscription is used as an identifier of the durable subscription. Each durable consumer must have a unique ID, resulting in the declaration of a unique connection factory for each potential, durable consumer.

Setting Priorities

You can use message priority levels to instruct the JMS provider to deliver urgent messages first. JMS defines ten priority values, with 0 as the lowest and 9 as the highest. You can specify the priority value by using the setPriority() method of the JMSProducer:

context.createProducer(). setPriority (2).send(queue, message);

Most of these methods return the JMSProducer to allow method calls to be chained together, allowing a fluid programming style. For example:

context.createProducer().setPriority(2)
                        .setTimeToLive(1000)
                        .setDeliveryMode(DeliveryMode.NON_PERSISTENT)
                        .send(queue, message);

Writing Message-Driven Beans

Until now, this chapter showed how asynchronous messaging provides loose coupling and increased flexibility between systems, using the JMS API. MDBs provide this standard asynchronous messaging model for enterprise applications running in an EJB container.

An MDB is an asynchronous consumer that is invoked by the container as a result of the arrival of a message. To a message producer, an MDB is simply a message consumer, hidden behind a destination to which it listens.

MDBs are part of the EJB specification, and their model is close to stateless session beans as they do not have any state and run inside an EJB container. The container listens to a destination and delegates the call to the MDB upon message arrival. Like any other EJB, MDBs can access resources managed by the container (other EJBs, JDBC connections, JMS resources, Entity Manager etc.).

Why use MDBs when you can use stand-alone JMS clients, as you’ve seen previously? Because of the container, which manages multithreading, security, and transactions, thereby greatly simplifying the code of your JMS consumer. It also manages incoming messages among multiple instances of MDBs (available 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. A simple consumer MDB is described in Listing 13-9.

Listing 13-9.  A Simple MDB

@MessageDriven(mappedName = "jms/javaee7/Topic")
public class BillingMDB implements MessageListener {
 
  public void onMessage (Message message) {
    System.out.println("Message received: " + message.getBody(String.class));
  }
}

The code in Listing 13-9 (omitting exception handling for clarity) shows that MDBs relieve the programmer of all mechanical aspects of processing the types of messages explained so far. An MDB implements the MessageListener interface and the onMessage() method, but no other code is needed to connect to the provider or start message consumption. MDBs also rely on the configuration-by-exception mechanism, and only a few annotations are needed to make it work (see the @MessageDriven annotation).

Anatomy of an MDB

MDBs are different from session beans, as they do not implement a local or remote business interface but instead implement the javax.jms.MessageListener interface. Clients cannot invoke methods directly on MDBs; however, like session beans, MDBs have a rich programming model that includes a life cycle, callback annotations, interceptors, injection, and transactions. Taking advantage of this model provides applications with a high level of functionality.

It is important to be aware that MDBs are not part of the EJB Lite model, meaning that they cannot be deployed in a simple web profile application server, but still need the full Java EE stack.

The requirements to develop an MDB class are as follows:

  • The class must be annotated with @javax.ejb.MessageDriven or its XML equivalent in a deployment descriptor
  • The class must implement, directly or indirectly, the MessageListener interface
  • The class must be defined as public, and must not be final or abstract
  • The class must have a public no-arg constructor that the container will use to create instances of the MDB
  • The class must not define the finalize() method.

The MDB class is allowed to implement other methods, invoke other resources, and so on. MDBs are deployed in a container and can be optionally packaged with an ejb-jar.xml file. Following the “ease of use” model of Java EE 7, an MDB can be simply an annotated POJO, eliminating most of the configuration. However, if you still need to customize the JMS configuration, you can use the elements of the @MessageDriven and @ActivationConfigProperty annotations (or XML equivalent).

@MessageDriven

MDBs are one of the simplest kinds of EJBs to develop, as they support the smallest number of annotations. The @MessageDriven annotation (or XML equivalent) is mandatory, as it is the piece of metadata the container requires to recognize that the Java class is actually an MDB.

The API of the @MessageDriven annotation, shown in Listing 13-10, is very simple, and all elements are optional.

Listing 13-10.  @MessageDriven Annotation API

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

The name element specifies the name of the MDB (which by default is the name of the class). messageListenerInterface specifies which message listener the MDB implements (if the MDB implements multiple interfaces, it tells the EJB container which one is the MessageListener interface). The mappedName element is the JNDI name of the destination that the MDB should be listening to. description is just a string, used to give a description of the MDB once deployed. The activationConfig element is used to specify configuration properties and takes an array of @ActivationConfigProperty annotations.

@ActivationConfigProperty

JMS allows configuration of certain properties such as message selectors, acknowledgment mode, durable subscribers, and so on. In an MDB, these properties can be set using the @ActivationConfigProperty annotation. This optional annotation can be provided as one of the parameters for the @MessageDriven annotation, and, compared to the JMS equivalent, the @ActivationConfigProperty is very basic, consisting of a name-value pair (see Listing 13-11).

Listing 13-11.  ActivationConfigProperty Annotation API

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

The activationConfig property allows you to provide standard and nonstandard (provider–specific) configuration. The code in Listing 13-12 sets the acknowledge mode and the message selector.

Listing 13-12.  Setting Properties on MDBs

@MessageDriven(mappedName = "jms/javaee7/Topic", activationConfig = {
  @ActivationConfigProperty (propertyName = " acknowledgeMode ",
                            propertyValue = "Auto-acknowledge"),
  @ActivationConfigProperty (propertyName = " messageSelector ",
                            propertyValue = "orderAmount < 3000")
})
public class BillingMDB implements MessageListener {
 
  public void onMessage(Message message) {
    System.out.println("Message received: " + message.getBody(String.class));
  }
}

Each activation property is a name-value pair that the underlying messaging provider understands and uses to set up the MDB. Table 13-8 lists some standard properties you can use.

Table 13-8. Activation Properties for OpenMQ

Property Description
acknowledgeMode The acknowledgment mode (default is AUTO_ACKNOWLEDGE)
messageSelector The message selector string used by the MDB
destinationType The destination type, which can be TOPIC or QUEUE
destinationLookup The lookup name of an administratively-defined Queue or Topic
connectionFactoryLookup The lookup name of an administratively defined ConnectionFactory
destination The name of the destination.
subscriptionDurability The subscription durability (default is NON_DURABLE)
subscriptionName The subscription name of the consumer
shareSubscriptions Used if the message-driven bean is deployed into a clustered
clientId Client identifier that will be used when connecting to the JMS provider

Dependencies Injection

Like all the other EJBs that you’ve seen in Chapter 7, MDBs can use dependency injection to acquire references to resources such as JDBC datasources, EJBs, or other objects. Injection is the means by which the container inserts dependencies automatically after creating the object. These resources have to be available in the container or environment context, so the following code is allowed in an MDB:

@PersistenceContext
private EntityManager em;
@Inject
private InvoiceBean invoice;
@Resource (lookup = "jms/javaee7/ConnectionFactory")
private ConnectionFactory connectionFactory;

The MDB context can also be injected using the @Resource annotation:

@Resource private MessageDrivenContext context;

MDB Context

The MessageDrivenContext interface provides access to the runtime context that the container provides for an MDB instance. The container passes the MessageDrivenContext interface to this instance, which remains associated for the lifetime of the MDB. This gives the MDB the possibility to explicitly roll back a transaction, get the user principal, and so on. The MessageDrivenContext interface extends the javax.ejb.EJBContext interface without adding any extra methods.

If the MDB injects a reference to its context, it will be able to invoke the methods listed in Table 13-9.

Table 13-9. Methods of the MessageDrivenContext Interface

Method Description
getCallerPrincipal Returns the java.security.Principal associated with the invocation
getRollbackOnly Tests whether the current transaction has been marked for rollback
getTimerService Returns the javax.ejb.TimerService interface
getUserTransaction Returns the javax.transaction.UserTransaction interface to use to demarcate transactions. Only MDBs with bean-managed transaction (BMT) can use this method
isCallerInRole Tests whether the caller has a given security role
Lookup Enables the MDB to look up its environment entries in the JNDI naming context
setRollbackOnly Allows the instance to mark the current transaction as rollback. Only MDBs with BMT can use this method

Life Cycle and Callback Annotations

The MDB life cycle (see Figure 13-12) is identical to that of the stateless session bean: either the MDB exists and is ready to consume messages or it doesn’t exist. Before exiting, the container first creates an instance of the MDB and, if applicable, injects the necessary resources as specified by metadata annotations (@Resource, @Inject, @EJB, etc.) or deployment descriptor. The container then calls the bean’s @PostConstruct callback method, if any. After this, the MDB is in the ready state and waits to consume any incoming message. The @PreDestroy callback occurs when the MDB is removed from the pool or destroyed.

9781430246268_Fig13-12.jpg

Figure 13-12. MDB life cycle

This behavior is identical to that of stateless session beans (see Chapter 8 for more details about callback methods), and, like other EJBs, you can add interceptors with the @javax.ejb.AroundInvoke annotation.

MDB as a Consumer

As explained in the “Writing Message Consumers” section earlier in this chapter, consumers can receive a message either synchronously, by looping and waiting for a message to arrive, or asynchronously, by implementing the MessageListener interface. By nature, MDBs are designed to function as asynchronous message consumers. MDBs implement a message listener interface, which is triggered by the container when a message arrives.

Can an MDB be a synchronous consumer? Yes, but this is not recommended. Synchronous message consumers block and tie up server resources (the EJBs will be stuck looping without performing any work, and the container will not be able to free them). MDBs, like stateless session beans, live in a pool of a certain size. When the container needs an instance, it takes one out of the pool and uses it. If each instance goes into an infinite loop, the pool will eventually empty, and all the available instances will be busy looping. The EJB container can also start generating more MDB instances, growing the pool and eating up more and more memory. For this reason, session beans and MDBs should not be used as synchronous message consumers. Table 13-10 shows you the different receiving modes for MDBs and session beans.

Table 13-10. MDB Compared with Session Beans

Enterprise Beans Producer Synchronous Consumer Asynchronous Consumer
Session beans Yes Not recommended Not possible
MDB Yes Not recommended Yes

MDB as a Producer

MDBs are capable of becoming message producers, something that often occurs when they are involved in a workflow, as they receive a message from one destination, process it, and send it to another destination. To add this capability, the JMS API must be used.

A destination and a connection factory can be injected by using the @Resource and @JMSConnectionFactory annotations or via JNDI lookup, and then methods on the javax.jms.JMSContext object can be invoked to create and send a message. The code of the BillingMDB (see Listing 13-13) listens to a topic (jms/javaee7/Topic), receives messages (onMessage() method), and sends a new message to a queue (jms/javaee7/Queue).

Listing 13-13.  A MDB Consuming and Producing Messages

@MessageDriven(mappedName = " jms/javaee7/Topic ", activationConfig = {
        @ActivationConfigProperty(propertyName  = "acknowledgeMode",
                                  propertyValue = "Auto-acknowledge"),
        @ActivationConfigProperty(propertyName  = "messageSelector",
                                  propertyValue = "orderAmount BETWEEN 3 AND 7")
})
public class BillingMDB implements MessageListener {
 
  @Inject
  @JMSConnectionFactory("jms/javaee7/ConnectionFactory")
  @JMSSessionMode(JMSContext.AUTO_ACKNOWLEDGE)
  private JMSContext context;
  @Resource(lookup = " jms/javaee7/Queue ")
  private Destination printingQueue ;
 
  public void onMessage(Message message) {
    System.out.println("Message received: " + message.getBody(String.class));
    sendPrintingMessage();
  }
 
  private void sendPrintingMessage () throws JMSException {
    context.createProducer().send (printingQueue, "Message has been received and resent");
  }
}

This MDB uses most of the concepts introduced thus far. First, it uses the @MessageDriven annotation to define the JNDI name of the topic it is listening to (mappedName = "jms/javaee7/Topic"). In this same annotation, it defines a set of properties, such as the acknowledge mode and a message selector using an array of @ActivationConfigProperty annotations, and it implements MessageListener and its onMessage() method.

This MDB also needs to produce a message. Therefore, it is injected with the two administered objects required: a connection factory (using JMSContext) and a destination (the queue named jms/javaee7/Queue). Finally, the business method that sends messages (the sendPrintingMessage() method) looks like what you’ve seen earlier: a JMSProducer is created and used to create and send a text message. For better readability, exception handling has been omitted in the entire class.

Transactions

MDBs are EJBs (see Chapter 7 and 8 for more information). MDBs can use BMTs or container-managed transactions (CMTs); they can explicitly roll back a transaction by using the MessageDrivenContext.setRollbackOnly() method, and so on. However, there are some specifics regarding MDBs that are worth explaining.

When we talk about transactions, we always think of relational databases. However, other resources are also transactional, such as messaging systems. If two or more operations have to succeed or fail together, they form a transaction (see Chapter 9). With messaging, if two or more messages are sent, they have to succeed (commit) or fail (roll back) together. How does this work in practice? The answer is that messages are not released to consumers until the transaction commits. 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 for rollback with setRollbackOnly()).

Even though MDBs are transactional, they cannot execute in the client’s transaction context, as they don’t have a client. Nobody explicitly invokes methods on MDBs, they just listen to a destination and consume messages. There is no context passed from a client to an MDB, and similarly the client transaction context cannot be passed to the onMessage() method. Table 13-11 compares CMTs with session beans and MDBs.

Table 13-11. MDB Transactions Compared with Session Beans

Transaction Attribute Session Beans MDB
NOT_SUPPORTED Yes Yes
REQUIRED Yes Yes
MANDATORY Yes No
REQUIRES_NEW Yes No
SUPPORTS Yes No
NEVER Yes No

In CMTs, MDBs can use the @javax.ejb.TransactionAttribute annotation on business methods with the two following attributes:

  • REQUIRED (the default): If the MDB invokes other enterprise beans, the container passes the transaction context with the invocation. The container attempts to commit the transaction when the message listener method has completed
  • NOT_SUPPORTED: If the MDB invokes other enterprise beans, the container passes no transaction context with the invocation

Handling Exceptions

In the snippets of code in this chapter, exception handling has been omitted, as the JMS API can be verbose in dealing with exceptions. The classic API defines 12 different exceptions, all inheriting from javax.jms.JMSException. The simplified API defines 10 runtime exceptions all inheriting from javax.jms.JMSRuntimeException.

It is important to note that JMSException is a checked exception (see the discussion on application exception in the “Exception Handling” section in Chapter 9) and JMSRuntimeException is unchecked. The EJB specification outlines two types of exceptions:

  • Application exceptions: Checked exceptions that extend Exception and do not cause the container to roll back
  • System exceptions: Unchecked exceptions that extend RuntimeException and cause the container to roll back

Throwing a JMSRuntimeException will cause the container to roll back, but throwing a JMSException will not. If a rollback is needed, the setRollBackOnly() must be explicitly called or a system exception (such as EJBException) rethrown:

public void onMessage(Message message) {
  try {
    System.out.println("Message received: " + message.getBody(String.class));
  } catch (JMSException e) {
    context.setRollBackOnly();
  }
}

Putting It All Together

In this chapter we have covered the basic concepts of messaging (P2P and pub-sub models), administered objects (connection factories and destinations), learned how to connect to a provider, to produce and consume messages, to use some reliability mechanisms, and to use container-managed components (MDBs) to listen to destinations. So now let’s see how these concepts work together through an example; we’ll compile and package it with Maven, and deploy it to GlassFish.

This example uses a stand-alone class (OrderProducer) that sends messages to a topic (called jms/javaee7/Topic). These messages are objects representing a customer purchase order of books and CDs. The purchase order (OrderDTO) has several attributes, including the total amount of the order. The consumers that listen to the topic are OrderConsumer and ExpensiveOrderMDB (see Figure 13-13). The OrderConsumer receives any order, but the MDB only consumes orders that have a total amount greater than $1,000 (using a selector).

9781430246268_Fig13-13.jpg

Figure 13-13. Putting It All Together

Because Maven needs to structure the code based on the final packaging artifacts, the ExpensiveOrderMDB and the OrderDTO will get packaged in one jar file (chapter13-MDB-1.0.jar file) and then deployed to GlassFish. The OrderProducer, OrderConsumer and, again, the OrderDTO will run in a Java SE environment.

Writing the OrderDTO

The object that will be sent in the JMS message is a POJO that needs to implement the Serializable interface. The OrderDTO class, shown in Listing 13-14, gives some information about the order, including its total amount; it is the object that will be set into a JMS ObjectMessage and sent from the OrderProducer to the topic and then consumed by the OrderConsumer and ExpensiveOrderMDB.

Listing 13-14.  The OrderDTO Is Passed in a JMS ObjectMessage

public class OrderDTO implements Serializable {
 
  private Long orderId;
  private Date creationDate;
  private String customerName;
  private Float totalAmount ;
 
// Constructors, getters, setters
}

Writing the OrderProducer

The OrderProducer, shown in Listing 13-15, is a stand-alone client that uses the JMS simplified API to send an ObjectMessage to the jms/javaee7/Topic topic. It looks up for the necessary connection factory and destination, and in the main() method creates an instance of an OrderDTO class. Note that the totalAmount of the order is an argument passed to the class (args[0]). The JSMProducer sets the orderAmount property in the message for selection later, and the order is then sent to the topic.

Listing 13-15.  The OrderProducer Sends an OrderDTO

public class OrderProducer {
 
  public static void main(String[] args) throws NamingException {
 
    // Creates an orderDto with a total amount parameter
    Float totalAmount = Float.valueOf(args[0]);
    OrderDTO order = new OrderDTO(1234l, new Date(), "Betty Moreau", totalAmount );
 
    // Gets the JNDI context
    Context jndiContext = new InitialContext();
 
    // Looks up the administered objects
    ConnectionFactory connectionFactory = (ConnectionFactory)
                      jndiContext.lookup(" jms/javaee7/ConnectionFactory ");
    Destination topic = (Destination) jndiContext.lookup(" jms/javaee7/Topic ");
 
    try (JMSContext jmsContext = connectionFactory.createContext()) {
     // Sends an object message to the topic
     jmsContext.createProducer(). setProperty("orderAmount", totalAmount) . send(topic, order) ;
    }
  }
}

Writing the OrderConsumer

The OrderConsumer, shown in Listing 13-16, is also a stand-alone JMS client listening to the jms/javaee7/Topic topic and using the JMSConsumer API to receive all the OrderDTO (there are no selectors).

Listing 13-16.  The OrderConsumer Consumes all the OrderDTO Messages

public class OrderConsumer {
 
  public static void main(String[] args) throws NamingException {
 
    // Gets the JNDI context
    Context jndiContext = new InitialContext();
 
    // Looks up the administered objects
    ConnectionFactory connectionFactory = (ConnectionFactory)
                      jndiContext.lookup(" jms/javaee7/ConnectionFactory ");
    Destination topic = (Destination) jndiContext.lookup(" jms/javaee7/Topic ");
 
    // Loops to receive the messages
    try (JMSContext jmsContext = connectionFactory.createContext()) {
      while (true) {
        OrderDTO order = jmsContext.createConsumer(topic).receiveBody(OrderDTO.class) ;
        System.out.println("Order received: " + order);
      }
    }
  }
}

Writing the ExpensiveOrderMDB

The ExpensiveOrderMDB class (see Listing 13-17) is an MDB annotated with @MessageDriven that listens to the jms/javaee7/Topic destination. This MDB is only interested in orders greater than $1,000, using a message selector (orderAmount > 1000). At message arrival, the onMessage() method consumes it, casts it to an OderDTO (getBody(OrderDTO.class)), and gets the body of the message. For this example, only the message is displayed (System.out.println), but other processing could have also been done (by delegating to a stateless session bean for example).

Listing 13-17.  The ExpensiveOrderMDB only Consumes Orders with an Amount Greater than $1000

@MessageDriven(mappedName = " jms/javaee7/Topic ", activationConfig = {
        @ActivationConfigProperty(propertyName  = "acknowledgeMode",
                                  propertyValue = "Auto-acknowledge"),
        @ActivationConfigProperty(propertyName  = " messageSelector ",
                                  propertyValue = " orderAmount > 1000 ")
})
public class ExpensiveOrderMDB implements MessageListener {
 
  public void onMessage (Message message) {
    try {
      OrderDTO order = message.getBody(OrderDTO.class) ;
      System.out.println("Expensive order received: " + order.toString());
    } catch (JMSException e) {
      e.printStackTrace();
    }
  }
}

Compiling and Packaging with Maven

The ExpensiveOrderMDB and the OrderDTO should be packaged together in a jar file so they can be deployed to GlassFish. Because MDBs use annotations from the EJB package (@MessageDriven) and the JMS API (ConnectionFactory, Destination, etc.) the pom.xml shown in Listing 13-18 uses the glassfish-embedded-all dependency (which contains all the Java EE 7 APIs). This dependency has the scope provided because GlassFish, as an EJB container and a JMS provider, provides these APIs at runtime. Maven should be informed that you are using Java SE 7 by configuring the maven-compiler-plugin.

Listing 13-18.  The pom.xml to Build and Package the MDB

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns=" http://maven.apache.org/POM/4.0.0 "
         xmlns:xsi=" http://www.w3.org/2001/XMLSchema-instance "
         xsi:schemaLocation=" http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd ">
  <modelVersion>4.0.0</modelVersion>
 
  <parent>
    <artifactId>chapter13</artifactId>
    <groupId>org.agoncal.book.javaee7</groupId>
    <version>1.0</version>
  </parent>
 
  <groupId>org.agoncal.book.javaee7.chapter13</groupId>
  <artifactId>chapter13-mdb</artifactId>
  <version>1.0</version>
 
  <dependencies>
    <dependency>
      <groupId>org.glassfish.main.extras</groupId>
      <artifactId> glassfish-embedded-all </artifactId>
      <version>4.0</version>
      <scope>provided</scope>
    </dependency>
  </dependencies>
 
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>2.5.1</version>
        <configuration>
          <source> 1.7 </source>
          <target>1.7</target>
        </configuration>
      </plugin>
    </plugins>
  </build>
</project>

To compile and package the classes, open a command-line interpreter in the directory that contains the pom.xml file, and enter the following Maven command:

$ mvn package

Go to the target directory, where you should see the file chapter13-MDB-1.0.jar. If you open it, you will see that it contains the class file for the ExpensiveOrderMDB and OrderDTO.

Creating the Administered Objects

The administered objects required to send and receive messages need to be created in the JMS provider. Each one has a JNDI name, allowing clients to obtain a reference of the object through a JNDI lookup:

  • The connection factory is called jms/javaee7/ConnectionFactory
  • The topic is called jms/javaee7/Topic

As these objects are created administratively, GlassFish needs to be up and running. Once you’ve made sure that the asadmin command-line is in your path, execute the following command in the console:

$ asadmin create-jms-resource --restype javax.jms.ConnectionFactory
                                        jms/javaee7/ConnectionFactory
$ asadmin create-jms-resource --restype javax. jms.Topic jms/javaee7/Topic

GlassFish’s web console can be used to set up the connection factory and the queue. Note, however, that in my experience the easiest and quickest way to administer GlassFish is through the asadmin script. Use another command to list all the JMS resources and ensure that the administered objects are created successfully:

$ asadmin list-jms-resources
jms/javaee7/Topic
jms/javaee7/ConnectionFactory

Since JMS 2.0, there is a programmatic way to declare administered objects. The idea is to annotate any managed bean (managed bean, EJB, MDB . . .) with @JMSConnectionFactoryDefinition and @JMSDestinationDefinition, deploy the bean, and the container will make sure to create the factory and the destination. This mechanism is similar to the one you saw in Chapter 8 Listing 8-15 with the @DataSourceDefinition annotation. Listing 13-19 shows the ExpensiveOrderMDB with two definition annotations.

Listing 13-19.  The ExpensiveOrderMDB Defining Administered Objects Programmatically

@JMSConnectionFactoryDefinition (name = "jms/javaee7/ConnectionFactory",
                           className = "javax.jms.ConnectionFactory")
@JMSDestinationDefinition (name = "jms/javaee7/Topic",
                     className = "javax.jms.Topic")
public class ExpensiveOrderMDB implements MessageListener {...}

Deploying the MDB on GlassFish

Once the MDB is packaged in the jar, it needs to be deployed into GlassFish. This can be done in several ways, including via the web administration console. However, the asadmin command-line does the job simply: open a command-line, go to the target directory where the chapter13-MDB-1.0.jar file is, make sure GlassFish is still running, and enter the following command:

$ asadmin deploy chapter13-MDB-1.0.jar

If the deployment is successful, the following command should return the name of the deployed jar and its type (ejb in the example):

$ asadmin list-components
chapter13-MDB-1.0 <ejb>

Running the Example

The MDB is deployed on GlassFish and is listening to the jms/javaee7/Topic destination, waiting for a message to arrive. It’s time to run the OrderConsumer and OrderProducer clients. These classes are stand-alone applications with a main method that has to be executed outside of GlassFish, in a pure Java SE environment. Enter the following command to run the OrderConsumer:

$ java –cp target/classes OrderConsumer

The consumer loops indefinitely and waits for an order to arrive. Now enter the following command to send a message with an order of $2,000:

$ java –cp target/classes OrderProducer 2000

Because the amount is greater than $1,000 (the amount defined in the selector message), the OrderExpensiveMDB and OrderConsumer should both receive the message. Check the GlassFish logs to confirm this. If you pass a parameter lower than $1,000, the MDB will not receive the message, just the OrderConsumer:

$ java –cp target/classes OrderProducer 500

Summary

This chapter showed that integration with messaging is a loosely coupled, asynchronous form of communication between components. MOM can be seen as a buffer between systems that need to produce and consume messages at their own pace. This is different from the RPC architecture (such as RMI) in which clients need to know the methods of an available service.

The first section of this chapter concentrated on the JMS API (classical and simplified) and its vocabulary. The asynchronous model is a very powerful API that can be used in a Java SE or a Java EE environment, and is based on P2P and pub-sub, connection factories, destinations, connections, sessions, messages (header, properties, body) of different types (text, object, map, stream, bytes), selectors, and other reliability mechanisms such as acknowledgment or durability.

Java EE has a special enterprise component to consume messages: MDBs. The second section of this chapter showed how MDBs could be used as asynchronous consumers and how they rely on their container to take care of several services (life cycle, interceptors, transactions, security, concurrency, message acknowledgment, etc.).

This chapter also showed how to put these pieces together with Maven, GlassFish, and OpenMQ, and gave an example with a stand-alone sender and an MDB receiver.

The following chapters will demonstrate other technologies used to interoperate with external systems: SOAP web services and RESTful web services.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.117.7.131