Message-driven Beans (MDBs)

A Message -driven Bean (MDB) is a stateless, transaction-aware component (service end-point) that consumes JMS messages asynchronously, and they are managed by a Java EE application server and EJB container. MDBs are similar to EJBs because they are complete enterprise beans, similar to stateless session beans. The EJB container is responsible for managing the lifecycle of the endpoint. MDB live inside the EJB container and the container looks after the environment, the security, and the resources, including transactions and concurrency. Most importantly, the container will also handle message acknowledgement and concurrency.

MDBs process messages concurrently and have a thread safety designed by default. There are no remote or local bean interfaces for MDBs, which means the application does not invoke methods on MDB directly, because they respond only to asynchronous messages. MDBs are scalable to hundreds and thousands of asynchronous JMS messages.

In Java EE 7, a message-driven bean is declared with an annotation: @javax.ejb.MessageDriven. This annotation accepts an array of annotations for a configuration called @javax.ejb.ActivationConfigProperty.

Here is an example of a MDB:

package je7hb.jms.essentials;
import javax.ejb.*;
import javax.jms.*;

@MessageDriven(
  mappedName = "jms/demoQueue",
  activationConfig = {
    @ActivationConfigProperty(
      propertyName="acknowledgeMode",
      propertyValue = "Auto-acknowledge"
      ),
    @ActivationConfigProperty(
      propertyName="messageSelector",
      propertyValue = "SpecialCode = 'Barbados'"
      ),
  }
)
public class PayloadCheckMDB implements MessageListener {
    @EJB
    private PayloadCheckReceiverBean receiver;

    public void onMessage(Message message) {
        try {
            TextMessage textMsg = (TextMessage)message;
            String text = textMsg.getText();
            receiver.addMessage(text);
        }
        catch (Exception e) {
            e.printStackTrace(System.err);
        }
    }
}

The class PayloadCheckMDB is annotated with @MessageDriven and it extends the MessageListener JMS interface. PayloadCheckReceiverBean, which is an EJB, is injected into the MDB. We do this because MDBs are asynchronous by default, and therefore we want to avoid recording the state in the message bean.

The @MessageDriven annotation accepts a mappedName parameter which identifies the connection; the MessageDestination that this MDB connects to. The activation configuration key-value pairs define the acknowledgment mode for the JMS session. There is also a messageSelector which filters the JMS messages that this MDB will receive.

Note

Please note the different package name for @javax.ejb.MessageDriven, which is a big clue that message-driven beans are truly enterprise beans!

Inside the onMessage() method, we retrieve the text of the Message instance and save it into the receiver bean.

Here is the code for the receiver bean:

public class PayloadCheckReceiverBean {
    private CopyOnWriteArrayList<String> messages =
        new CopyOnWriteArrayList<>();

    public void addMessage(String text) {
        messages.add(text);
    }

    public List<String> getMessages() {
        return messages;
    }
}

The PayloadCheckReceiverBean is a singleton and a stateless session EJB which uses a concurrent collection to store messages. It is a singleton because we want to preserve its state for the duration of the unit test that follows.

To complete the picture, we need a revised version of the earlier JMS sender EJB. Here is the code snippet for that bean:

package je7hb.jms.essentials;

/* ... similar to the previous ... */

@Stateless
public class PayloadCheckSenderBean {
    @Inject
    @JMSConnectionFactory("jms/demoConnectionFactory")
    JMSContext context;

    @Resource(mappedName = "jms/demoQueue")
    private Queue inboundQueue;

    List<String> messages = new ArrayList<>();

    public void sendPayloadMessage( String payload ) {
        sendPayloadMessage(payload,null); }

    public void sendPayloadMessage( 
        String payload, String code ) {
        JMSProducer producer = 
         context.createProducer();
         if (code != null ) {
             producer.setProperty("SpecialCode", code);
        }
        producer.send(inboundQueue, payload);
        messages.add(payload);
    }
    public List<String> getMessages() {
        return messages;
    }
}

The other EJB, PayloadCheckSenderBean, now has a refactored overloaded method, sendPayloadMessage() , which sends a message with a JMS header property or not. Choosing the two argument version of the method adds a property to the outgoing message with the key SpecialCode. This is the form for the message selector in the MDB.

Here is the full unit to exercise PayloadCheckMDB:

package je7hb.jms.essentials;

/* ... similar to the previous ... */

@RunWith(Arquillian.class)
public class PayloadCheckMDBTest {

    @Deployment
    public static JavaArchive createDeployment() {
        JavaArchive jar = ShrinkWrap.create(JavaArchive.class)
            .addClasses(
                    PayloadCheckMDB.class,
                    PayloadCheckSenderBean.class,
                    PayloadCheckReceiverBean.class)
            .addAsManifestResource(
                    EmptyAsset.INSTANCE,
        ArchivePaths.create("beans.xml"));
        return jar;
    }

    @EJBPayloadCheckSenderBean sender;
    @EJBPayloadCheckReceiverBean receiver;

    @Test
    public void shouldFireMessageAtMDB() throws Exception {
        sender.sendPayloadMessage("hello");
        sender.sendPayloadMessage("Wanda", "Barbados");
        sender.sendPayloadMessage("world");
        sender.sendPayloadMessage("Fish", "Barbados");
        Thread.sleep(1000);
        List<String> messages = receiver.getMessages();
        assertEquals(2, messages.size());
        assertTrue(messages.contains("Wanda"));
        assertTrue(messages.contains("Fish"));
    }
}

In this Arquillian integration test, we inject two EJBs, PayloadCheckSenderBean and PayloadCheckReceiverBean, into the class. We do this to get around the fact that it is not possible to inject the MDB directly into a test because the CDI (and EJB) container prohibits this dependency on an asynchronous entity.

Inside the test method shouldFireMessageAtMDB() , we send four messages to the MDB. Two messages have the special property and the others have not. We deliberately delay for a bit because we are dealing with asynchronous behaviors, network I/O, and an embedded server. The singleton EJB PayloadCheckReceiverBean should have two messages in its store. We also use the assertTrue() call instead of the assertEquals() because we can never be sure of order of the messages because of Java concurrency and multiple threads. The list collection could be [Wanda,Fish] or [Fish,Wanda]. We only care about the message delivery.

Tip

The order of messages

Messaging systems are more flexible and serve better throughput if they can cope with out-of-order messages. The JMS standard does not guarantee the order of the messages and how they will be delivered. The order of messages is completely at the discretion of the JMS provider implementation. Some providers like IBM MQ Series and Oracle WebLogic JMS have a feature that allows administrators to define the order of the messages. The OpenMQ product inside of the GlassFish server delivers messages in FIFO order, but then again, when it is tied to asynchronous MDBs inside a server with Java threads, your results will vary. Typically, the message payload itself will have an order ID or candidate keys of columns that the application can use to re-order the message itself in a group of messages.

JMS 2.0 adds the standard configuration of JMS connections for MDBs. It is now possible to configure the JNDI name of the message destination through annotations. The developer can also configure the connection factory, client ID, and the name of the durable subscription through annotations.

Activation configuration property

As we have seen, MDBs are asynchronous and are bound to a JMS connection. The connection can be local to the application server, and most implementations are external. In other words, an application can use another message queue implementation, and it will normally be a remote service. MDBs are allowed to be binded to different JMS providers, both local and remote. This is where the activation configuration properties and the corresponding annotation @ActivationConfigProperty help out.

Here is a table of the important key names for the @ActivationConfigProperty annotation:

Configuration Name

Description

destinationLookup

New in JMS 2.0, this property specifies the lookup name for an MDB of Queue or Topic defined by an administrator that messages will be read from.

The setting can also be set in an ejb-jar.xml file.

destinationFactoryLookup

New in JMS 2.0, this property specifies the lookup name for an MDB of ConnectionFactory defined by an administrator that messages will be read from.

The setting can also be set in an ejb-jar.xml file.

acknowledgeMode

Specifies the session acknowledgement for an MDB. The allowed values are Auto-acknowledge and Dups-ok-acknowledge. The default is AUTO_ACKNOWLEDGE.

messageSelector

Specifies the conditional expression that allows an MDB to select specific JMS messages. The default is to allow all messages through for reception.

destinationType

Specifies whether the destination is a queue or a topic. The valid values are javax.jms.Queue or javax.jms.Topic.

subscriptionDurability

Specifies if the MDB is part of a durable subscription or not. The valid values are Durable or NonDurable. The default value is the non-durable variety.

clientId

New in JMS 2.0, this optional property sets the client identifier for connecting to the JMS provider.

subscriptionName

New in JMS 2.0, this optional property sets the name of the durable or non-durable subscription.

Message selectors

The message selector is an important facility for MDB to receive only messages that are interesting from the JMS queue or topic. Message selectors use javax.jms.Message header properties in conditional expressions.

The expressions are based on a subset of the SQL-92 conditional expression syntax that is used in the WHERE clauses of database SQL statements. Hence, they are very familiar to database administrators. Message selectors are allowed to use Boolean expressions, unary operators, and literal values. They can be become quite complex.

Here is an example of a more advanced message selector:

@ActivationConfigProperty(
  propertyName="messageSelector",
  propertyValue = 
    "(SpecialCode = 'Barbados' AND VerNum > 2)" +
    "OR (WorkRole='Admin')"
),

Obviously, a JMS message header would have one or two of these additional properties defined in order for this MDB to receive the data.

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

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