Thursday, September 25, 2008

JMS on JBoss using EJB3 MDB

Introduction

This simple JMS application has you submit a form which sends a message to a JBoss queue. A Message-Driven Bean (MDB) will be listening on that queue destination and receives the mssage and processes it. What makes this lesson different than this one is that this MDB example runs in an EJB3 container and the MDB is exposed using a simple annotation. To appreciate this simple example, it will really help to view the 'old way' of having to do things in the link above. With the EJB3 approach there is a lot less configuration. We don not need to configure the jboss-web.xml, destinations-service.xml, ejb-jar.xml, or the jboss.xml.
The Setup
Download the source Download the source above and unzip it into your project directory of choice. For eclipse users the source comes with an eclipse project so you can just import that project file and be good to go. To build using ant, type 'ant all' or if you want to build and deploy to jboss type 'ant deploy' to build and deploy the ear to JBoss.

Java5 The Java5 JDK needs to be set as your environment. JAVA_HOME env variable needs to be defined pointing to yth the JDK home directory.

JBoss 4.2+ - Download JBoss 4.2 here http://labs.jboss.com/portal/jbossas/download. (This should also run on 4.0.5 if it's patched to support EJB3 using the JEMS installer.)

Running the example If you've built the project usingg ant (ant deploy), the ear will be deployed to jboss and you simply go to your jboss home dir/bin and start jboss with run.sh (Nix flavors) or run.bat (Windows). If you want to just see the app run you can download the ear above and put the ear into your jboss/default/deploy directory. Once jboss is started, go the following url: http://localhost:8080/rr-jms

LongProcessMessageBean
This is our Message Driven Bean. Notice the EJB3 annotations. The annotation tells the container to deploy this as an Message Driven Bean and it also creates the queue based off our annotation. We are also using Resource injection with the @Resource annotation which injects our MessageDrivenContext message which we use if their was some business logic problem and we need to do a rollback in the catch block ( context.setRollbackOnly() .) All this MDB does is mimic calling some business process (doLongProcess). You'll see in your jboss console the println statements when this gets called. (Of course in real life you'd be using Logging and not stupid println statements.)

import javax.annotation.Resource;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.ejb.MessageDrivenContext;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.ObjectMessage;

import net.learntechnology.dto.ProcessDTO;
import net.learntechnology.util.LongProcessService;
import net.learntechnology.util.ProcessResult;

@MessageDriven(name="LongProcessMessageBean", activationConfig = {
@ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"),
@ActivationConfigProperty(propertyName="destination", propertyValue="queue/examples/OurSampleQueue")

})
public class LongProcessMessageBean implements MessageListener {

@Resource
private MessageDrivenContext context;

public void onMessage(Message message) {
String name = null;
try {
if (message instanceof ObjectMessage) {
ObjectMessage objMessage = (ObjectMessage) message;
Object obj = objMessage.getObject();
if (obj instanceof ProcessDTO) {
name = ((ProcessDTO)obj).getName();
System.out.println("****************************************************");
System.out.println("LongProcessMessageBean.onMessage(): Received message. NAME: "+name);
System.out.println("****************************************************");
System.out.println("Now calling LongProcessService.doLongProcess");

ProcessResult result = LongProcessService.doLongProcess((ProcessDTO)obj);
} else {
System.err.println("Expecting ProcessDTO in Message");
}
} else {
System.err.println("Expecting Object Message");
}
System.out.println("*******************************************");
System.out.println("Leaving LongProcessMessageBean.onMessage(). NAME: "+name );
System.out.println("*******************************************");
} catch (Throwable t) {
t.printStackTrace();
context.setRollbackOnly();
}
}
}
index.jsp form
A simple JSP page. All it does is submit some text to our Servlet (AppServlet).

Your name: lt;input type="text" name="name" size="40">






AppServlet
Our AppServlet takes the request of text submitted from index.jsp and passes it to our JmsProducer. Our JmsProducer will need to know the jndi names of the connection factory and the queue name, so we are defining them here as well and passing them to our JmsProducer class.

public class AppServlet extends HttpServlet {
private static final String QUEUE_CONNECTION_FACTORY = "ConnectionFactory";
private static final String LONG_PROCESS_QUEUE = "queue/examples/OurSampleQueue";

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doPost(request, response);
}

public void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("in doPost of AppServlet..");
String name = request.getParameter("name");
System.out.println("name: "+name );
ProcessDTO processDTO = new ProcessDTO();
processDTO.setName(name);
JmsProducer.sendMessage(processDTO, QUEUE_CONNECTION_FACTORY, LONG_PROCESS_QUEUE);
request.setAttribute("started",true);
RequestDispatcher dispatcher = getServletContext().getRequestDispatcher("/index.jsp");
dispatcher.forward(request, response);
}
}
JmsProducer
This is our JMS client code that sends the message to the queue. We pushed the Connection lookup and the Destination lookup into another class called 'ServiceLocator' which is shown next.

public class JmsProducer {
private JmsProducer() {}
public static void sendMessage(Serializable payload, String connectionFactoryJndiName,
String destinationJndiName) throws JmsProducerException {
try {
ConnectionFactory connectionFactory = null;
Connection connection = null;
Session session = null;
Destination destination = null;
MessageProducer messageProducer = null;
ObjectMessage message = null;
System.out.println("In sendMessage of JmsProducter,
getting ConnectionFactory for jndi name: "+connectionFactoryJndiName );
connectionFactory = ServiceLocator.getJmsConnectionFactory(
connectionFactoryJndiName);

connection = connectionFactory.createConnection();
session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
destination = ServiceLocator.getJmsDestination(destinationJndiName);
messageProducer = session.createProducer(destination);
message = session.createObjectMessage(payload);
messageProducer.send(message);
System.out.println("Message sent to messageProducer");
messageProducer.close();
session.close();
connection.close();
} catch (JMSException je) {
throw new JmsProducerException(je);
} catch (ServiceLocatorException sle) {
throw new JmsProducerException(sle);
}
}
}
ServiceLocator
This is a simple utility class used to look up our Connection and Destination via a JNDI lookup. In real life we might use a cache as well to store these lookups.

public class ServiceLocator {
private ServiceLocator() {}
public static ConnectionFactory getJmsConnectionFactory(String jmsConnectionFactoryJndiName)
throws ServiceLocatorException {
ConnectionFactory jmsConnectionFactory = null;
try {
Context ctx = new InitialContext();
jmsConnectionFactory = (ConnectionFactory) ctx.lookup(jmsConnectionFactoryJndiName);
} catch (ClassCastException cce) {
throw new ServiceLocatorException(cce);
} catch (NamingException ne) {
throw new ServiceLocatorException(ne);
}
return jmsConnectionFactory;
}
public static Destination getJmsDestination(String jmsDestinationJndiName)
throws ServiceLocatorException {
Destination jmsDestination = null;
try {
Context ctx = new InitialContext();
jmsDestination = (Destination) ctx.lookup(jmsDestinationJndiName);
} catch (ClassCastException cce) {
throw new ServiceLocatorException(cce);
} catch (NamingException ne) {
throw new ServiceLocatorException(ne);
}
return jmsDestination;
}
}
You might be wondering how to set the IntialContext with the proper host name to do its lookups. The best place to do that is in a jndi.properties file (which this app uses). Our jndi.properties file looks like this:

#jndi.properties
java.naming.factory.initial=org.jnp.interfaces.NamingContextFactory
java.naming.factory.url.pkgs=org.jboss.naming:org.jnp.interfaces
java.naming.provider.url=localhost:1099
application.xml
Necessary for our ear deployment to jboss.


xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/application_1_4.xsd"
version="1.4">
rr-jms-ejb3 Example EAR



rr-jms-webapp.war
rr-jms



conf.jar


common.jar



rr-jms-ejb.jar


Google