MDB

From NovaOrdis Knowledge Base
Jump to navigation Jump to search

Internal

Relevance

EAP 6.4.10

Overview

A Message-Driven Bean (MDB) is an asynchronous message consumer, invoked by the container as a result of the arrival of a message at the JMS destination serviced by the message-driven bean.

To a client, an MDB is a message consumer that implements some business logic running on the server. A client accesses an MDB by sending messages to the JMS destination associated with the MDB container of that type. MDBs are anonymous. They have no client-visible identity. MDB instances have no conversational state either, meaning they do not maintain state for a specific client. All bean instances are equivalent when they are not involved in servicing a client message. An MDB instance is created by the container to handle the processing of the messages for which the MDB is the consumer. Its lifetime is controlled by the container.

Non-JEE messaging system can send messages to be consumed by MDBs. This is made possible by the Message Provider Pluggability Contract, part of the JCA.

Diagrams

API

@MessageDriven Annotation

An MDB must be annotated with the @javax.ejb.MessageDriven annotation, or denoted in its deployment descriptor as a message driven bean.

activationConfig

The configuration of the MDB in the operational environment is specified in the "activationConfig" element of the @MessageDriven annotation.

@MessageDriven(activationConfig =  {
    @ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Topic"),
    @ActivationConfigProperty(propertyName="destination", propertyValue="/jms/queue/playground"),
    @ActivationConfigProperty(propertyName="subscriptionDurability", propertyValue="Durable"),
    @ActivationConfigProperty(propertyName="messageSelector", propertyValue="JMSType = 'car' AND color = 'blue' AND weight > 2500"),
})

The following configuration aspects can be specified (a working example that can be used to experiment with various values of the following parameters can be found here: https://github.com/NovaOrdis/playground/tree/master/jee/ejb/mdb-full):

destinationType

The type of JMS destination to be associated with the MDB is specified as the value of the "destinationType" property. It can be either javax.jms.Queue or javax.jms.Topic.

destination

The destination JNDI Name of the JMS destination to be associated with the MDB is specified as the value of the "destination" property.


Associating queue with more than one MDB should be avoided. IF there are multiple JMS consumers for a queue, JMS does not define how messages are distributed between the queue receivers

subscriptionDurability

If the destination the MDB is associated with is a topic, the subscription durability can be specified with the "subscriptionDurability" property. Valid values are "Durable" and "NonDurable". If the subscription durability is not specified, a non-durable subscription is assumed.

acknowledgeMode

The MDBs must not attempt to use the JMS API for message acknowledgment. Message acknowledgment is automatically handled by the container. If the MDB uses container-managed transaction demarcation, message acknowledgment is handled automatic as part of the transaction commit. If bean-managed transaction demarcation is used, the message receipt cannot be part of the bean-managed transaction, and in this case, the receipt is acknowledged by the container. If bean-managed transaction demarcation is used, MDB can indicate whether JMS AUTO_ACKNOWLEDGE or DUPS_OK_ACKNOWLEDGE semantics should apply, by using the "acknowledgeMode" property of the activationConfig element. If the value of "acknowledgeMode" is not specified, AUTO_ACKNOWLEDGE semantics is assumed. Otherwise, explicit values are "Auto-acknowledge" and "Dups-ok-acknowledge".

messageSelector

MDB may use activationConfig "messageSelector" property to specify a JMS selector to be used in determining which message a JMS message-driven bean is to receive. An example is provided above.

maxSession

This is a WildFly-specific property. It represents the number of concurrent sessions created by the resource adapter. The default value, if not specified, is 15.

providerAdapterJNDI

This is a WildFly-specific property. The default value, if not specified, is "java:/DefaultJMSProvider".

useJNDI

TODO See <pooled-connection-factory> configuration and explanations in Configuring_a_Remote_HornetQ_JMS_Server_as_a_Resource_Adapter"

hA

The "hA" configuration parameter is required to specify that the MDB container wants to use a highly available inbound connection and to turn on HA on the underlying connection factory. This parameter is required even the underlying pooled connection factory was configured to be highly averrable for outbound connections. For more details see:

Pooled Connection Factory High Availability

Aslo see:

Configuring_a_Remote_HornetQ_JMS_Server_as_a_Resource_Adapter

Redelivery on Failure and DLQ

All DLQ-releated activation configuration attributes listed below are disabled, and a configuration value specified in the annotation will be discarded. For more details configuring redelivery on failure and DLQ, see

MDB Failure Handling - Redelivery and Dead Letter Queue Configuration
useDLQ

Disabled. See Redelivery on Failure and DLQ.

dLQJNDIName

Disabled. See Redelivery on Failure and DLQ.

dLQHandler

Disabled. See Redelivery on Failure and DLQ.

dLQMaxResent

Disabled. See Redelivery on Failure and DLQ.

mappedName

messageListenerInterface

@ResourceAdapter Annotation

The @ResourceAdapter is useful when deploying an MDB with a different JMS provider than the one bundled with JBoss instance.

For example, for ActiveMQ:

import org.jboss.ejb3.annotation.ResourceAdapter;
...
@ResourceAdapter("activemq-ra.rar")
...

The default value is, implicitly:

@ResourceAdapter("jms-ra.rar")

The Maven dependency required to import ResourceAdapter annotation is (the version may vary):

<dependency>
    <groupId>org.jboss.ejb3</groupId>
    <artifactId>jboss-ejb3-ext-api</artifactId>
    <version>2.1.0.redhat-1</version>
</dependency>

An example of how to deploy a remote HornetQ cluster as a resource adapter, and the use of @ResourceAdapter annotation to support that, are described here:

Configuring a Remote HornetQ JMS Server as a Resource Adapter

@PoolClass

@PoolClass(value=StrictMaxPool.class, maxSize=10, timeout=10000)

More details about configuring the pool:

http://www.jboss.org/jbossejb3/docs/reference/build/reference/en/html/session-bean-config.html

Code

An MDB must implement the appropriate listener interface for the messaging type the MDB supports. If the MDB implements javax.jms.MessageListener interface, that distinguishes the MDB as a JMS MDB. If the class does not implement the interface, the MDB must specify the message listener interface using the @MessageDriven annotation, with its "messageListenerInterface" element, or the messaging-type deployment descriptor element. The class must have a public constructor with no arguments.

The MessageDrivenBean interface, which was required by previous EJB versions, is currently optional. The main role of this interface was to allow the MDB to access container services, and currently, this need is fulfilled by the optionally injected MessageDriventContext.

The onMessage() method of the message listener is called by the container when a message arrives. The method must contain the business logic to handle the message.

It is the container's responsibility to insure that only one thread can be executing an instance at any time.

Deployment Descriptor

All configuration elements allowed by the annotation have deployment descriptor equivalents (activation-config, messaging-type, etc.) Activation configuration properties specified in the deployment descriptor are added to those specified by the annotation. If a property of the same name is specified in both places, the deployment descriptor value takes precedence over the value specified in the annotation.

Examples

The simplest possible working MDB example is available here:

https://github.com/NovaOrdis/playground/tree/master/jee/ejb/mdb

Another example that showcases most of the configuration and API details discussed in this article is available here:

https://github.com/NovaOrdis/playground/tree/master/jee/ejb/mdb-full

An MDB that listens on a topic over a durable subscription

https://github.com/NovaOrdis/playground/tree/master/jee/ejb/mdb-on-durable-subscription

Maven

<dependency>
    <groupId>javax</groupId>
    <artifactId>javaee-api</artifactId>
    <version>6.0</version>
    <scope>provided</scope>
</dependency>

Relationship with the Container and Lifecycle

It is the container's responsibility to instantiate the MDB instances, manage their life cycle, notify the MDB instances when bean action may be necessary and provide security, concurrency, transactions and other services.

The MDB instances need not concern about scalability and the concurrent processing of a large number of messages: it is the container's responsibility to instantiate a large enough MDB instance number and handle the message distribution to them. All MDB instances are equivalent, a client message can be delivered to any available instance. Once instantiated, the container will allow all MDB instances to execute concurrently, thus allowing for the concurrent processing of a stream of messages. No guarantees are made as to the exact order in which messages are delivered. MDB should be prepared to handle messages that are out of sequence.

Lifecycle

The container instantiate the MDB by reflection, via its no-argument public constructor. Then it injects the MessageDrivenContext instance and other resources that have been requested. More details are available in the "Dependency Injection" section. Then int calls the method annotated with @PostConstruct, as described below.

The MDB may choose to be notified of lifecycle events by annotating methods with two specific annotations:

@PostConstruct

The @PostConstruct method calls occurs before the first onMessage() invocation on the MDB, but after the dependency injection has been performed by the container.

@PreDestroy

The @PreDestroy method is invoked at the time the MDB is removed from the pool or destroyed. Note that if the MDB implements the MessageDrivenBean interface, this annotation can only be applied to the ejbRemove() method.

Dependency Injection

An MDB may use dependency injection mechanisms to acquire references to other objects in the environment. If the MDB uses dependency injection, the container will inject these reference after the bean instance is created and before the first invocation of the onMessage() method.

MessageDrivenContext

The MDB may declare interest in accessing services from the container by requesting a MessageDrivenContext to be injected into it (or, alternatively, by implementing the MessageDrivenBean interface):

@Resource
private MessageDrivenContext context; 

The MessageDrivenContext allows the MDB instance to interact with its transactional, security and JNDI contexts, among other things:

  • Rollback the current transaction with setRollbackOnly(), or test the rollback status of the current transaction with getRollbackOnly(). Only MDBs with container-managed transaction can use this method.
  • Get access to the javax.transaction.UserTransaction instance with getUserTransaction(). The UserTransaction object then can be used to interact with the on-going transaction and obtain the transaction status. Only MDBs with bean-managed transaction can use this method, otherwise an IllegalStateException is thrown on invocation.
  • Get the java.security.Principal instance associated with the invocation with getCallerPrincipal().
  • Access the JNDI via lookup().
  • Get a javax.ejb.TimerService instance with getTimerService().
  • Retrieve any interceptor/web service context associated with this invocation, with getContextData().

Transactional Context

The bean's message listener and timeout callback methods are invoked in the scope of the transaction determined by the transaction attribute specified in the bean's annotations or deployment descriptor. By default, without declaring anything, an MDB will be executed with the context of a REQUIRED container managed transaction.

If the bean uses container-managed transaction demarcation, valid transaction attributes for the listener method are REQUIRED or NOT_SUPPORTED:

@TransactionAttribute(TransactionAttributeType.REQUIRED)
public class MyMDB implements MessageListener {
...
}

For more details on @TransactionAttribute see

@TransactionAttribute

If the bean uses bean-managed transaction demarcation, it must get the UserTransaction object from its context and demarcate the transaction by invoking its methods. In this case, the message receipt that causes the bean to be invoked is not part of the transaction. If the message receipt is to be part of the transaction, container-managed demarcation with the REQUIRED transaction attribute must be used.

Failure Handling

Failure Handling

Security Context

The container enforces declarative security, so only the calls with appropriate security credentials can be made into the MDBs. The MDB have access to their security context via the MessageDrivenContext's security related methods, such as getCallerPrincipal().

@RunAs annotation can be used to define a run-as identity for the bean. The identity applies to the bean's message listener methods and timeout methods.

Interceptors

Method annotated with @AroundInvoke are supported for MDBs. These interceptor methods may be defined on the bean class or on an interceptor class and apply to the handling of the invocation of the bean's message listener methods.

Concurrency

EJB Concepts - Concurrency

Client's Access to MDBs

Clients cannot access a specific MDB, they can only send messages to destinations serviced by MDBs.

A client looks up the destination in JNDI. The client's JNDI name space may be configured to include destinations serviced by MDBs installed in multiple containers located on multiple machines on a network. The actual locations of the MDBs are transparent to the clients sending messages into them.

WildFly MDB Support

The generic configuration of the MDB containers deployed within a WildFly instance is available in the ejb3 subsystem:

EJB3 Subsystem Configuration - MDB Container

WildFly MDB Troubleshooting

Configuration Details

Configuration details can be displayed at boot time by turning TRACE on "org.jboss.as.ejb3". This will provide information on pool size, security domain, etc.

<logger category="org.jboss.as.ejb3">
    <level name="ALL"/>
</logger>
2017-03-29 12:51:07,747 DEBUG [org.jboss.as.ejb3] (MSC service thread 1-1) Using pool config StrictMaxPoolConfig{name=mdb-strict-max-pool, maxPoolSize=77, timeoutUnit=MINUTES, timeout=5} to create pool for MDB MDB
2017-03-29 12:51:07,752 INFO  [org.jboss.as.ejb3] (MSC service thread 1-1) WFLYEJB0042: Started message driven bean 'MDB' with 'activemq-ra.rar' resource adapter
2017-03-29 12:51:07,754 TRACE [org.jboss.as.ejb3] (MSC service thread 1-4) Using security domain: other for EJB MDB


Consumer Count

An MDB container creates by default 15 sessions on a destination, and that number should translate into the destination's "consumer-count".