Developing JMS applications

In the following sections, we will introduce the core components used in the Enterprise to leverage JMS programming. Our first round will be with message-driven beans that are part of the Java EE specification. Next, we will add a JBoss-specific component named message-driven POJO to our list of deliverables.

Message-driven beans

Message-driven beans (MDBs) are stateless, server-side, transaction-aware components, for processing asynchronous JMS messages.

One of the most important aspects of message-driven beans is that they can consume and process messages concurrently. This capability provides a significant advantage over traditional JMS clients, which must be custom-built to manage resources, transactions, and security in a multi-threaded environment. The message-driven bean containers manage concurrency automatically so that the bean developer can focus on the business logic of processing the messages. The MDB can receive hundreds of JMS messages from various applications and process all of them at the same time, as numerous instances of the MDB can execute concurrently in the container.

A message-driven bean is classified as an Enterprise bean, just like a session or an entity bean, but there are some important differences. First, the message-driven bean does not have component interfaces. This is because the message-driven bean is not accessible through the Java RMI API and responds only to asynchronous messages.

Just as the entity and session beans have well-defined life cycles, so does the MDB bean. The MDB instance's life cycle has two states: Does not Exist and Method ready Pool.

Message-driven beans

When a message is received, the EJB container checks to see if any MDB instance is available in the pool. If a bean is available in the free pool, JBoss uses that instance. After an MDB instance's onMessage() method returns, the request is complete and the instance is placed back in the free pool. This results in the best response time, as the request is served without waiting for a new instance to be created.

If no bean instances are handy, the container checks if there is room for more MDBs in the pool, by comparing the MDB's MaxSize attribute with the pool size.

If MaxSize value has still not been reached, a new MDB is initialized. The creation sequence, as pointed out in the previous diagram, is just the same as stateless bean. On the other hand, failure to create a new instance will imply that the request will be blocked until an active MDB completes a method call or the transaction times out.

Configuring message-driven beans

You can control the pool size of MDBs from the deployejb3-interceptors-aop.xml file, in a similar manner to that explained for session beans. Locate the domain Message Driven Bean:

<domain name="Message Driven Bean" extends="Intercepted Bean" inheritBindings="true">
<annotation expr="!class(@org.jboss.ejb3.annotation.Pool)">
@org.jboss.ejb3.annotation.Pool (value="StrictMaxPool", maxSize=15, timeout=10000)
</annotation>
</domain>

By default, message-driven beans use the StrictMaxPool strategy, allowing for a maxSize of 15 elements in the pool.

Creating a sample application

We will now create a sample application that sends messages over a queue. Have you deployed the sample destinations (sample-destinations-service.xml) that we illustrated before? If not, it's now time to do it.

The consumer of this queue will be a message-driven bean written using EJB 3.0 specifications. Let's first create a new EJB project. From the File menu, select New | Other | EJB | EJB project. Choose a name for your project (for example, JMSExample) and make sure you have selected the JBoss 5 runtime and configuration. Also the EJB module version needs to be 3.0.

Once your project is ready, let's add a new message-driven bean from the same path: New | Other | EJB | EJB 3 Message Driven Bean. The job of this component will be invoking a stored procedure on an external system; we will call it com.packtpub.jms.MDBProcessor.

Creating a sample application

Click Finish. Eclipse will create the MDBProcessor class, bearing the annotation @MessageDriven. The core properties of the MDB will be nested into the @MessageDriven annotation marked as @ActivationConfigProperty.

@MessageDriven(name = "MessageMDBSample", activationConfig = { @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), [1] @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/exampleQueue}) [2]

Here we have connected the bean to a queue destination [1], whose JNDI name is queue/exampleQueue [2]. Let's have a look at the complete code:

package com.packtpub.jms;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import javax.annotation.Resource;
import javax.ejb.MessageDriven;
import javax.ejb.ActivationConfigProperty;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import javax.sql.DataSource;
import java.sql.Types;
@MessageDriven(activationConfig =
{
@ActivationConfigProperty(propertyName="destinationType", propertyValue="javax.jms.Queue"),
@ActivationConfigProperty(propertyName="destination", propertyValue="queue/exampleQueue")
})
public class MDBProcessor implements MessageListener {
@Resource(mappedName = "java:/MySqlDS") [1]
private DataSource datasource;
public void onMessage(Message msg) [2]
{
Connection connection = null;
TextMessage text = (TextMessage)msg;
try {
connection = datasource.getConnection();
String sql = "{ ? = call hello (?) }";
CallableStatement cs = connection.prepareCall(sql);
cs.registerOutParameter(1, Types.VARCHAR);
cs.setString(2,text.getText());
cs.execute();
String retMess = cs.getString(1);
System.out.println(retMess);
}
catch (Exception e1) {
e1.printStackTrace();
}
finally {
try {
connection.close();
}
catch (SQLException e1) {
e1.printStackTrace();
}
}
}
}

In our MDB, we inject a datasource as a resource [1] that will be necessary for setting up a database connection with MySQL. The datasource has already been defined in Chapter 5, Developing JPA Entities, so you should not need any further configuration.

Tip

Warning:

As per Java EE specification, a resource is accessible through JNDI, using the name parameter. However, JBoss requires the mappedName element.

Once a new message is posted to exampleQueue, the container recalls the onMessage() method [2] of our MDB. The job of the onMessage() method will be recalling a stored procedure named hello on the MySQL database. This procedure actually returns a greeting message built with the parameter, which is passed to the procedure.

Here is a sample MySQL stored procedure that can be used for testing this example (just add this DDL from your MySQL client):

CREATE FUNCTION hello (s CHAR(20))
RETURNS CHAR(50) DETERMINISTIC
RETURN CONCAT('Hello, ',s,'!'),

Tip

Why not just output System.out.println from the MDB?

The last example does not produce anything more than a System.out, so you may wonder why we are adding extra steps to yield the same result. Although the reader can easily turn the example into a Hello World one, we do believe that with a small effort the reader can enjoy projects that are closer to real-world programming.

For example, the MDB processor that gets injected into a datasource can be used as a template if you need to manage your storage with plain JDBC from your Enterprise Java Bean.

The MDB is complete. You can deploy it just like other projects. Right-click on the label JBoss 5.0 Server in the JBoss Server View. Select Add and remove projects. Once your project is added, you can deploy it by clicking on the project label and selecting Full publish.

Creating a sample application

Deployment should take just a minute. If you take a look at the deploy folder of your JBoss server, you should notice a file named JMSExample.jar that contains the compiled MDB processor.

Now we need to create a message producer. A standard Java class is what you need. Add a new class from the menu and name it com.packtpub.jms.QueueSender.

package com.packtpub.jms;
import java.util.Properties;
import javax.jms.*;
import javax.naming.*;
public class QueueSender {
public static void main(String[] args) throws Exception {
new QueueSender().example();
}
public void example() throws Exception
{
String destinationName = "queue/exampleQueue";
Context ic = null;
ConnectionFactory cf = null;
Connection connection = null;
try {
ic = getInitialContext(); [1]
cf = (ConnectionFactory)ic.lookup("/ConnectionFactory"); [2]
Queue queue = (Queue)ic.lookup(destinationName);
connection = cf.createConnection();
Session session = connection.createSession(false, Session.AUTO_ACKNOWLEDGE); [3]
MessageProducer sender = session.createProducer(queue);
TextMessage message = session.createTextMessage("Frank");
sender.send(message); [4]
System.out.println("The message was successfully sent to the " + queue.getQueueName() + " queue");
}
catch(Exception ne){
ne.printStackTrace();
}
finally{
if(ic != null) {
try {
ic.close();
}
catch(Exception ignore){ }
}
closeConnection(connection);
}
}
public static Context getInitialContext( )
throws javax.naming.NamingException {
Properties p = new Properties( );
p.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
p.put(Context.URL_PKG_PREFIXES, " org.jboss.naming:org.jnp.interfaces");
p.put(Context.PROVIDER_URL, "jnp://localhost:1099");
return new javax.naming.InitialContext(p);
}
private void closeConnection(Connection con){
try {
if (con != null) {
con.close();
}
}
catch(JMSException jmse) {
System.out.println("Could not close connection " + con +" exception was " +jmse);
}
}
}

We will not detail the steps in the client, which is a standard JMS queue sender. Just recap all that you need to provide. First, you need to provide the environment details of your JMS server [1]. In this case, we are creating the InitialContext with a set of properties, just to show you an alternative to placing the file jndi.properties in the classpath. The ConnectionFactory will then be looked up in the JNDI tree [2]. The Factory is used to set up a javax.jms.Connection object and this one in turn is used to start a new JMS session [3]. A MessageProducer is then created to actually send [4] a TextMessage to the queue.

I guess you are impatient to test your latest creation. Reach the option Run As | Java Application, either from the Package Explorer or the Editor, and test your messaging application. Successful output should produce the following:

Creating a sample application

Tip

You surely have noticed some boring EJB 3 warnings that appear in the server logs. This message is related to EJB life cycle callbacks and can be issued by the container under some circumstances related to the EJB 3 interceptor's running mode (AOP and container managed interception). This stuff is generally useless for the bean developer and you can get rid of it by adding the @SuppressWarning annotation before the onMessage() method:

@SuppressWarnings(value={"all"})
public void onMessage(Message msg) {}

Creating MDB singletons

Sometimes it is appropriate to have exactly one instance of a class; this is called a singleton. Making an MDB singleton with EJB 3.0 is trivial; you can just add the maxSession property to your annotations. In the container's language, maxSession is intended as the maximum number of instances to be created.

@ActivationConfigProperty(propertyName = "maxSession", propertyValue = "1")

If you want this behavior across all your MDBs, then you have to modify the deployejb3-interceptors-aop.xml file by adding the following property to your MDB domain:

<annotation expr="!class(@org.jboss.annotation.ejb.DefaultActivationSpecs)">
@org.jboss.annotation.ejb.DefaultActivationSpecs ({@javax.ejb.ActivationConfigProperty(propertyName = "maxSession", propertyValue = "1")})
</annotation>

Message-driven POJOs

Message-driven POJOs are not part of Java EE specification, but rather a specific element of the JBoss suite. The term message-driven POJO is not new in the Java Enterprise system. You can find some queries about it in the Spring context. However, JBoss MDP is different from Spring implementation because it is a plain POJO implementation and is not constrained by any framework contract.

You can think about JBoss MDP as a half-blood relative of session beans and message-driven beans. They are semantically similar to session beans because they expose the business contract of the POJO, so they are a typed component. In spite of this, they also have some peculiarities of MDBs because they are driven by a JMS transport (queue or topic). Therefore, they inherit all the characteristics of JMS messaging.

Tip

MDB or message-driven POJOs?

Choosing the right interface for your JMS-driven application depends on the characteristics of your project. For example, if your priority is to have loosely coupled and portable interfaces, then you should stick to MDB. On the other hand, if you need to expose a business contract to your JMS client, then message-driven POJOs are a safe bet. Message-driven POJOs also carry another advantage versus MDB—they are just POJOs. You just define Java methods, expose them through a producer interface, and your invocations are turned into JMS messages underneath.

As a practical example, we will rebuild our MDB processor using a message-driven POJO. Message-driven POJOs are unknown to Eclipse IDE, so you have to resort again to a simple Java class. Go to New | Class and name it com.packpub.jms.POJOProcessor. The implementation of the MDP follows here:

package com.packtpub.jms;
import java.sql.CallableStatement;
import java.sql.Connection;
import java.sql.SQLException;
import org.jboss.ejb3.annotation.Consumer;
import org.jboss.ejb3.annotation.CurrentMessage;
import javax.annotation.Resource;
import javax.ejb.*;
import javax.jms.*;
import javax.sql.DataSource;
import java.sql.Types;
@Consumer(activationConfig = { [1]
@ActivationConfigProperty(
propertyName = "destinationType",
propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(
propertyName = "destination",
propertyValue = "queue/pojoQueue") }) [2]
public class POJOProcessor implements POJOProcessorItf { [3]
@Resource(mappedName = "java:/MySqlDS")
private DataSource datasource;
public void callProcedure(String param) {
Connection connection = null;
try {
connection = datasource.getConnection();
String sql = "{ ? = call hello (?) }";
CallableStatement cs = connection.prepareCall(sql);
cs.registerOutParameter(1, Types.VARCHAR);
cs.setString(2,param);
cs.execute();
String retMess = cs.getString(1);
System.out.println(retMess);
}
catch (Exception e1) {
e1.printStackTrace();
}
finally {
try {
connection.close();
}
catch (SQLException e1) {
e1.printStackTrace();
}
}
}
}

The first difference with a plain MDB is that a POJO message bean is tagged by an @Consumer [1] annotation. Actually, the MDP class will consume the messages that are produced by the business interface [3]. The bean is targeted at a destination named pojoQueue [2] (you have to provide this destination by adding a new entry in the destinations-service.xml file).

The implemented interface, named com.packtpub.jms.POJOProcessorItf follows:

package com.packtpub.jms;
import org.jboss.ejb3.annotation.Producer;
@Producer [1]
public interface POJOProcessorItf {
public void callProcedure(String param);
}

As we just said, the @Producer annotation [1] acts as a producer of messages that will be consumed by the implementation classes. A message-driven POJO doesn't require anything else. Just pack your compiled bean class and the interface in an archive, and deploy it to JBoss:

JMSExample.jar
+---com
¦ +---packtpub
¦ +---jms
| +---POJOProcessor.class
| +---POJOProcessorItf.class
+---META-INF

Now the only missing thing is a client invoking our callProcedure method. For this purpose, add another Java class named com.packtpub.jms.POJOClient.

import java.util.Properties;
import javax.naming.Context;
import org.jboss.ejb3.mdb.ProducerConfig;
import org.jboss.ejb3.mdb.ProducerManager;
import org.jboss.ejb3.mdb.ProducerObject;
import com.packtpub.jms.POJOProcessorItf;
public class POJOClient {
public static void main(String[] args) throws Exception
{
Context ctx = getInitialContext();
POJOProcessorItf proc =
(POJOProcessorItf) ctx.lookup(POJOProcessorItf.class.getName()); [1]
ProducerManager manager = ((ProducerObject) proc).getProducerManager(); [2]
manager.connect(); [3]
try
{
proc.callProcedure("John"); [4]
}
catch (Exception exc)
{
exc.printStackTrace();
}
finally {
ProducerConfig.close(proc); [5]
}
}
}

When you deploy an MDP, a proxy object named with the producer's classname [1] will be bound in the JNDI context. However, we don't directly deal with the proxy, but with the class org.jboss.ejb3.mdb.ProducerManager that manages the JMS connection for this proxy [2]. The ProducerManager starts a JMS connection with the proxy [3] and, through the typed interface, invokes the method callProcedure [4]. When all operations are complete, the connection with the proxy is closed [5].

Tip

In the MDP business interface, you expose the business contract just like a session bean. However, the transport protocol will be JMS and not RMI. Sometimes you might need to recover the message properties, as you may need the message ID or any other property of the message. In this case, you can simply inject the message with the @CurrentMessage annotation. Your message details will also be available in the MDP implementation class.

public class POJOProcessor implements POJOProcessorItf {
@CurrentMessage
private Message currentMessage;
}
..................Content has been hidden....................

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