Message Driven Beans are no new concept due to the fact that they exist since EJB 2.0 but in Java EE 6 and the EJB 3.0 specification it is even more fun to use them.
In this tutorial we’re going to take a look at the specification and create an example application that transfers some objects via the Java Message Service to a Message-Driven Bean deployed on a GlassFish application server.
If you’re not interested in theory please skip to chapter 6 and directly start creating an application – otherwise we’ll begin with a short introduction into the JMS terminology and the concept of a Message-Driven-Bean..
Prerequisites
We don’t need much stuff for this tutorial, just Java, Maven und GlassFish as application server..
JMS Terminology
Here is a list of common JMS terms I am going to use in this tutorial so if you’re not familiar with them, please take a look at this chapter or at Oracle’s JMS specification docs.
-
JMS provider: An implementation of the JMS interface for a Message Oriented Middleware (MOM). Providers are implemented as either a Java JMS implementation or an adapter to a non-Java MOM.
-
JMS client: An application or process that produces and/or receives messages.
-
JMS producer/publisher: Client that creates and sends messages.
-
JMS consumer/subscriber: Client to receive messages.
-
JMS message: The container object to contains the data being transferred between JMS clients.
-
JMS queue: A staging area that contains messages that have been sent and are waiting to be read. Note that, contrary to what the name queue suggests, messages don’t have to be delivered in the order sent. A JMS queue only guarantees that each message is processed only once.
-
JMS topic: A distribution mechanism for publishing messages that are delivered to multiple subscribers.
Message Driven Beans
Before we’re going to implement some message driven beans we’re going to cover some important details about MDBs ..
Short Facts
-
Message Driven Beans are not included in EJB Lite
-
In contrast to session beans, message driven beans don’t implement local or remote interfaces
-
You can’t call methods directly on message driven beans – use JMS that’s why they are called Message Driven Beans ;)
-
Message Driven Beans must be annotated with @javax.ejb.MessageDriven or the XML equivalent
-
The MDB must implement the javax.jms.MessageListener interface
-
The MDB must be defined as public and must not be final or abstract
-
Because of the container managed lifecycle of a message driven bean, the bean must have a no-arg constructor and must not define the finalize method
Lifecycle
-
A Message Driven Bean’s lifecycle is similar to the lifecycle of a stateless session bean
-
A MDB exists and is ready to consume messages or it does not exist
-
You may use the @PostConstruct and @PreDestroy annotations – the first one is called when the container has finished injecting needed resources etc .. the second one is called when the MDB is destroyed or removed from the pool
Transaction Scope
-
We can use bean-managed-transactions (BMT) or container-managed-transactions (CMT) in our Message Driven Beans
-
Not all CMT transaction modes are available compared to a normal session bean .. only NOT_SUPPORTED and REQUIRED are allowed in a Message Driven Bean. you may use @javax.ejb.TransactionAttribute here (JavaDocs)
-
Mark a transaction for rollback using MessageDrivenContext.setRollbackOnly()
MDB Context
The MessageDrivenContext inherits EJBContext and allows you to access the Message-Driven-Bean’s runtime context.
-
getCallerPrincipal(): Returns a Principal object that identifies the caller
-
getContextData(): Returns the context data associated with this invocation or lifecycle callback
-
getTimerService(): Grants access to the EJB timer service
-
getUserTransaction(): Obtain the transaction demarcation interface. Only Message-Driven Beans using BMT can use this method.
-
isCallerInRole(): Tests if the caller has a given security role.
-
lookup(): Lookup environment entries via JNDI
-
setRollbackOnly(): Marks the current transaction as rollback. Only use this method if your MDB is using container-managed-transactions.
-
For more information, take a look at the Javadocs
Example Consumer MDB
As you can see below you don’t have much to do to create a Message Driven Bean that listens for new messages in a specified JMS queue named jms/hascode/Queue.
package com.hascode.tutorial.mdb;
import javax.ejb.MessageDriven;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
@MessageDriven(mappedName = "jms/hascode/Queue")
public class SimpleMessageMDB implements MessageListener {
@Override
public void onMessage(final Message message) {
try {
TextMessage textMessage = (TextMessage) message;
System.out.println("New message received: " + textMessage.getText());
} catch (JMSException e) {
e.printStackTrace();
}
}
}
Now that was easy … what did we do here?
-
We’ve made the bean a Message Driven Bean by annotating the class with @MessageDriven and implementing the interface MessageListener
-
We’ve specified what our MDB listens to, in this case a queue named jms/hascode/Queue using the mappedName-Parameter of @MessageDriven
-
In this class we assume that we’re getting a TextMessage from the queue .. that’s all
Example Producer MDB
In the following example we’re going to receive a TextMessage from a JSM queue, take the text from the message, enrich it with some new content and send it to another queue..
package com.hascode.tutorial.mdb;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.annotation.Resource;
import javax.ejb.MessageDriven;
import javax.jms.Connection;
import javax.jms.ConnectionFactory;
import javax.jms.Destination;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.MessageProducer;
import javax.jms.Session;
import javax.jms.TextMessage;
@MessageDriven(mappedName = "jms/hascode/Queue")
public class SimpleMessageRedirectingMDB implements MessageListener {
@Resource(name = "jms/hascode/ConnectionFactory")
private ConnectionFactory connectionFactory;
private Connection connection;
@Resource(name = "jms/hascode/EnrichedMessageQueue")
private Destination targetQueue;
@PostConstruct
private void initJMS() throws JMSException {
connection = connectionFactory.createConnection();
}
@PreDestroy
private void closeJMS() throws JMSException {
connection.close();
}
@Override
public void onMessage(final Message message) {
try {
TextMessage textMessage = (TextMessage) message;
System.out.println("New message received: " + textMessage.getText());
enrichAndPublish(textMessage);
} catch (JMSException e) {
e.printStackTrace();
}
}
private void enrichAndPublish(final TextMessage textMessage) throws JMSException {
final Session session = connection.createSession(true, Session.AUTO_ACKNOWLEDGE);
final MessageProducer producer = session.createProducer(targetQueue);
final TextMessage msg = session.createTextMessage();
msg.setText(textMessage.getText() + " I was enriched ;)");
producer.send(msg);
session.close();
}
}
What’s important here?
-
First we’re getting our ConnectionFactory and Queue instances injected using the @Resource annotation
-
We’re using the lifecycle annotations @PostConstruct and @PreDestroy to handle the initialization of our JMS connection
-
When our MDB receives a TextMessage it creates a new JMS session, a new TextMessage and sends the message to the queue jms/hascode/EnrichedMessageQueue
A running example
Now after all that theory we want some excitement and build some running stuff ..
We’re going to implement the following scenario:
-
A Message Driven Bean deployed on a GlassFish server listens on a Queue named jms/hascode/Queue
-
A standalone client as an executable jar registers to the GlassFish’s JMS service and sends a User object as an ObjectMessage to the queue
-
The Message Driven Bean received the message containing the user object and prints the user object’s information to the server logs
GlassFish Configuration
There are a few steps that we have to take so that our GlassFish server is ready to deploy and run the application that we’re going to create …
-
First we need a new domain to deploy our application so we’re creating one using asadmin
user@host:~/$ asadmin Use "exit" to exit and "help" for online help. asadmin> create-domain hascode-mdb-tutorial Enter admin user name [Enter to accept default "admin" / no password]> Using port 4848 for Admin. Using default port 8080 for HTTP Instance. Using default port 7676 for JMS. Using default port 3700 for IIOP. Using default port 8181 for HTTP_SSL. Using default port 3820 for IIOP_SSL. Using default port 3920 for IIOP_MUTUALAUTH. Using default port 8686 for JMX_ADMIN. Using default port 6666 for OSGI_SHELL. Distinguished Name of the self-signed X.509 Server Certificate is: [CN=server,OU=GlassFish,O=Oracle Corporation,L=Santa Clara,ST=California,C=US] No domain initializers found, bypassing customization step Domain hascode-mdb-tutorial created. Domain hascode-mdb-tutorial admin port is 4848. Domain hascode-mdb-tutorial allows admin login as user "admin" with no password. Command create-domain executed successfully.
-
Start the domain
asadmin> start-domain hascode-mdb-tutorial Waiting for DAS to start ........ Started domain: hascode-mdb-tutorial Domain location: /somepath/glassfishv31/glassfish/domains/hascode-mdb-tutorial Log file: /somepath/glassfishv31/glassfish/domains/hascode-mdb-tutorial/logs/server.log Admin port for the domain: 4848
-
Create the JMS resources needed using asadmin
asadmin> create-jms-resource --restype javax.jms.ConnectionFactory jms/hascode/ConnectionFactory Command create-jms-resource executed successfully. asadmin> create-jms-resource --restype javax.jms.Queue jms/hascode/Queue Command create-jms-resource executed successfully. asadmin> list-jms-resources jms/hascode/Queue jms/hascode/ConnectionFactory Command list-jms-resources executed successfully.
-
Alternatively you’re able to create the JMS resources using the web administration panel at http://localhost:4848 whatever you do .. you should see at least two similar JMS resources
Creating the Message Driven Bean
We’re creating a Message Driven Bean to listen on a queue named jms/hascode/Queue and to print out received user objects.
-
Create a new simple maven project
mvn archetype:generate
-
I am using the archetype javaee6-ejb (org.codehaus.mojo.archetypes) for this tutorial so my pom.xml looks like this
<?xml version="1.0"?> <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> <groupId>com.hascode.tutorial</groupId> <artifactId>javaee6-mdb-tutorial</artifactId> <version>0.0.1-SNAPSHOT</version> <packaging>ejb</packaging> <name>javaee6-mdb-tutorial</name> <properties> <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>javax</groupId> <artifactId>javaee-api</artifactId> <version>6.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.8.2</version> <scope>test</scope> </dependency> </dependencies> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> <compilerArguments> <endorseddirs>${endorsed.dir}</endorseddirs> </compilerArguments> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-ejb-plugin</artifactId> <version>2.3</version> <configuration> <ejbVersion>3.1</ejbVersion> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.1</version> <executions> <execution> <phase>validate</phase> <goals> <goal>copy</goal> </goals> <configuration> <outputDirectory>${endorsed.dir}</outputDirectory> <silent>true</silent> <artifactItems> <artifactItem> <groupId>javax</groupId> <artifactId>javaee-endorsed-api</artifactId> <version>6.0</version> <type>jar</type> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> </plugins> </build> </project>
-
Now add a class to represent a user object we’re going to receive via JMS named com.hascode.tutorial.mdb.User
package com.hascode.tutorial.mdb; import java.io.Serializable; public class User implements Serializable { private static final long serialVersionUID = 1L; private String name; public User() { } public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
And last but not least our Message-Driven-Bean – com.hascode.tutorial.mdb.UserMDB
package com.hascode.tutorial.mdb; import javax.ejb.MessageDriven; import javax.jms.JMSException; import javax.jms.Message; import javax.jms.MessageListener; import javax.jms.ObjectMessage; @MessageDriven(mappedName = "jms/hascode/Queue") public class UserMDB implements MessageListener { @Override public void onMessage(final Message message) { try { ObjectMessage objectMessage = (ObjectMessage) message; User user = (User) objectMessage.getObject(); System.out.println("User received - name: " + user.getName()); } catch (JMSException e) { e.printStackTrace(); } } }
Build and deploy the MDB
-
Build the jar archive using
mvn package
-
Deploy the jar file using the administrator web interface per default running at http://localhost:4848 or via command line using
user@host:~$ asadmin deploy target/javaee6-mdb-tutorial-0.0.1-SNAPSHOT.jar Application deployed successfully with name javaee6-mdb-tutorial-0.0.1-SNAPSHOT. Command deploy executed successfully.
-
Look up if the deployment was successful by using asadmin or the web interface
asadmin> list-components javaee6-mdb-tutorial-0.0.1-SNAPSHOT <ejb> Command list-components executed successfully.
-
The deployed EJB looks like this in the web administration interface
Creating the Sender
Now we want to create a standalone application to fill our JMS queue with some user input ..
-
Create a new simple maven project using your IDE or
mvn archetype:generate
-
Add dependencies needed and repositories so that the pom.xml looks similar to this one. We’ve got two sepcials here .. first we’re defining our executable main class and second we’re adding an endorsed dir to use the new version of the lookup flag in @Resource
<?xml version="1.0"?> <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> <groupId>com.hascode.tutorial</groupId> <artifactId>javaee6-mdb-client</artifactId> <version>0.0.1-SNAPSHOT</version> <name>hasCode.com MDB Client</name> <packaging>jar</packaging> <properties> <endorsed.dir>${project.build.directory}/endorsed</endorsed.dir> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <dependencies> <dependency> <groupId>org.glassfish</groupId> <artifactId>javax.ejb</artifactId> <version>3.1</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.glassfish</groupId> <artifactId>javax.jms</artifactId> <version>3.1</version> <scope>provided</scope> </dependency> </dependencies> <repositories> <repository> <id>maven2-repository.dev.java.net</id> <name>Java.net Repository for Maven</name> <url>http://download.java.net/maven/</url> </repository> <repository> <id>glassfish</id> <name>Maven Repository Glassfish</name> <layout>default</layout> <url>http://download.java.net/maven/glassfish</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories> <build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>2.3.2</version> <configuration> <source>1.6</source> <target>1.6</target> <compilerArguments> <endorseddirs>${endorsed.dir}</endorseddirs> </compilerArguments> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-dependency-plugin</artifactId> <version>2.1</version> <executions> <execution> <phase>validate</phase> <goals> <goal>copy</goal> </goals> <configuration> <outputDirectory>${endorsed.dir}</outputDirectory> <silent>true</silent> <artifactItems> <artifactItem> <groupId>javax</groupId> <artifactId>javaee-endorsed-api</artifactId> <version>6.0</version> <type>jar</type> </artifactItem> </artifactItems> </configuration> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-jar-plugin</artifactId> <version>2.3.1</version> <configuration> <archive> <manifest> <mainClass>com.hascode.tutorial.mdb.UserService</mainClass> </manifest> </archive> </configuration> </plugin> </plugins> </build> </project>
-
Create a user class com.hascode.tutorial.mdb.User
package com.hascode.tutorial.mdb; import java.io.Serializable; public class User implements Serializable { private static final long serialVersionUID = 1L; private String name; public User() { } public String getName() { return name; } public void setName(String name) { this.name = name; } }
-
Create the class to connect to the JMS server and send some object messages com.hascode.tutorial.mdb.UserService
package com.hascode.tutorial.mdb; import javax.annotation.Resource; import javax.jms.Connection; import javax.jms.ConnectionFactory; import javax.jms.JMSException; import javax.jms.MessageProducer; import javax.jms.ObjectMessage; import javax.jms.Queue; import javax.jms.Session; public class UserService { @Resource(lookup = "jms/hascode/ConnectionFactory") private static ConnectionFactory connectionFactory; @Resource(lookup = "jms/hascode/Queue") private static Queue queue; public static void main(String[] args) { User user = new User(); user.setName("Mickey"); try { Connection conn = connectionFactory.createConnection(); Session session = conn.createSession(false, Session.AUTO_ACKNOWLEDGE); MessageProducer producer = session.createProducer(queue); ObjectMessage msg = session.createObjectMessage(); msg.setObject(user); producer.send(msg); conn.close(); } catch (JMSException e) { e.printStackTrace(); } } }
-
That’s all .. now we want to send some information …
Running the Sender
-
First compile and build the sender as a jar file using
mvn package
-
We’re using GlassFish’s appclient tool to wrap our jar file and give it access to the application server’s resources that are injected into the sender..
user@host:$ appclient -client target/javaee6-mdb-client-0.0.1-SNAPSHOT.jar Jun 3, 2011 7:18:58 PM com.sun.enterprise.transaction.JavaEETransactionManagerSimplified initDelegates INFO: Using com.sun.enterprise.transaction.jts.JavaEETransactionManagerJTSDelegate as the delegate Jun 3, 2011 7:19:04 PM org.hibernate.validator.util.Version <clinit> INFO: Hibernate Validator bean-validator-3.0-JBoss-4.0.2 Jun 3, 2011 7:19:04 PM org.hibernate.validator.engine.resolver.DefaultTraversableResolver detectJPA INFO: Instantiated an instance of org.hibernate.validator.engine.resolver.JPATraversableResolver. Jun 3, 2011 7:19:04 PM com.sun.messaging.jms.ra.ResourceAdapter start INFO: MQJMSRA_RA1101: SJSMQ JMS Resource Adapter starting: REMOTE Jun 3, 2011 7:19:05 PM com.sun.messaging.jms.ra.ResourceAdapter start INFO: MQJMSRA_RA1101: SJSMQ JMSRA Started:REMOTE
-
Now look in to the server logs of your GlassFish domain .. they could be located at <dir-to-glassfish>/glassfish/domains/hascode-mdb-tutorial/server.log. If you have chosen another domain name, replace “hascode-mdb-tutorial” with your domain’s name..
[#|2011-06-03T17:32:33.426+0200|INFO|glassfish3.0.1|javax.enterprise.system.tools.admin.org.glassfish.deployment.admin|_ThreadID=26;_ThreadName=Thread-1;|javaee6-mdb-tutorial-0.0.1-SNAPSHOT was successfully deployed in 250 milliseconds.|#] [#|2011-06-03T19:19:06.000+0200|INFO|glassfish3.0.1|javax.enterprise.system.std.com.sun.enterprise.v3.services.impl|_ThreadID=31;_ThreadName=Thread-1;|User received - name: Mickey|#]
-
Voilà .. it worked ..
Tutorial Sources
I have put the source from this tutorial on my GitHub repository – download it there or check it out using Git:
git clone https://github.com/hascode/javaee6-mdb-tutorial.git
Troubleshooting
-
“Missing artifact org.glassfish:javax.jms:jar:3.1:provided” – be sure to have added the GlassFish Maven repository to your pom.xml
<repositories> <repository> <id>glassfish</id> <name>Maven Repository Glassfish</name> <layout>default</layout> <url>http://download.java.net/maven/glassfish</url> <snapshots> <enabled>false</enabled> </snapshots> </repository> </repositories>
-
“Missing artifact org.glassfish:javax.ejb:jar:3.1:provided” – same as above
-
“Caused by: com.sun.appserv.connectors.internal.api.ConnectorRuntimeException: JMS resource not created : jms/hascode/Queue” – You need to create the JMS resources on the application server .. use asadmin’s create-jms-resource as described in this tutorial
-
“java.lang.NullPointerException
at org.glassfish.appclient.client.acc.UndeployedLaunchable.newUndeployedLaunchable(UndeployedLaunchable.java:98)
at org.glassfish.appclient.client.acc.Launchable$LaunchableUtil.newLaunchable(Launchable.java:111)
at org.glassfish.appclient.client.acc.AppClientContainerBuilder.newContainer(AppClientContainerBuilder.java:155)
at org.glassfish.appclient.client.AppClientFacade.createContainerForAppClientArchiveOrDir(AppClientFacade.java:458)” – Add the endorsed directory to your pom.xml/build process and define the executable main class as described above.
Resources
Article Updates
-
2015-03-30: Formatting fixed, image captions added, links to my other Java EE tutorials and Java EE testing tutorials added.