JBoss.org Community Documentation

Chapter 38. Message Driven POJOs (JBoss specific)

The idea of Message Driven POJOs is to give a message consumer (an MDB), a typed interface that message producers can send messages through. Both the publisher and subscriber would be typed interfaces. This further facilitates the removal of all the lookups and bootstrap code you have to do to obtain and send a message and receive and dispatch a JMS message. With regular JMS you have to :

  • Get a connectionfactory
  • Get a connection
  • Get a destination
  • ... and so on

For the Message Driven POJOs, you just do:

  • Get a producer
  • Invoke on producer

Model :

Message Driven POJOs will have the same model as Stateless/Stateful beans. There is a bean class tagged as @org.jboss.ejb3.annotation.Consumer that must implement one or more @org.jboss.ejb3.annotation.Producer interfaces. Just as a stateless bean is tagged as @Stateless and implements one or more @Remote or @Local interfaces. Take a look at org.jboss.tutorial.consumer.bean.ExampleConsumerBean

				
@Consumer(activationConfig =
{@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
      @ActivationConfigProperty(propertyName = "destination", propertyValue = "queue/tutorial/example")})
@Depends ("jboss.messaging.destination:service=Queue,name=tutorial")
public class ExampleConsumerBean implements ExampleProducerRemote, ExampleProducerLocal, ExampleProducerXA
{
...
   public void method1(String msg, int val)
   {
      System.out.println("method1(" + msg + ", " + val + ")");
   }

   public void method2(String msg, Map<String, String> map)
   {
      System.out.println("method2: " + msg);
      for (String key : map.keySet())
      {
         System.out.println("method2 key/val: " + key + ":" + map.get(key));
      }
   }
				
			

Here's one of the @Producer interfaces :

				
@Producer
public interface ExampleProducerRemote extends ExampleProducer
{
...
				
			

You can see in this example that the ExampleConsumerBean implements the @Producer interfaces and defines the methods which can receive JMS messages. These interfaces will be used by clients(JMS Publishers) to send messages to the consumer via JMS.

Note

For each @Producer interface the @Consumer implements, there will be a proxy that implements that @Producer registered in JNDI under the fully qualified name of that @Producer interface.

Let's now look at the client org.jboss.tutorial.consumer.client.Client

				
public static void main(String[] args) throws Exception
   {
      InitialContext ctx = new InitialContext();
      ExampleProducerRemote remote = (ExampleProducerRemote) ctx.lookup(ExampleProducerRemote.class.getName());

      // you can typecast the returned proxy to obtain a ProducerManager interface that allows you to manage
      // interaction with JMS.
      ProducerManager manager = ((ProducerObject) remote).getProducerManager();


      // connect - internally creates a JMS connection
      manager.connect();

      try
      {
         // Call method1
         remote.method1("Remote method1 called", 1);
         System.out.println("Remote method1 called");

         // Call method2
         Map<String, String> map = new HashMap<String, String>();
         map.put("hello", "world");
         map.put("great", "ejb3");

         remote.method2("Remote method2 called", map);
         System.out.println("Remote method2 called");
      }
      finally
      {
         // instead of typecasting, you can use a helper class that does everything for you.
         ProducerConfig.close(remote);
      }
				
			

When the @Consumer is deployed by the EJB3 container, it looks for all of its @Producer interfaces and registers each one of them in JNDI under their fully qualified class name. The client looks up the ExampleProducerRemote from the JNDI and uses the returned proxy to send the message. The returned proxy can be cast to org.jboss.ejb3.mdb.ProducerObject. It then gets a org.jboss.ejb3.mdb.ProducerManager, that manages the JMS connection for this proxy. To start being able to send messages to the Queue, the client calls connect on the ProducerManager. When the client calls method1() on the proxy, this method call is converted into a JMS message and published to the Queue of the Consumer. The consumer will receive the message and invoke its method1 method.

Producer default values :

The proxy registered in JNDI will know how to contact the JMS Queue/Topic to publish messages. You can specify explicitly through the connectionFactory attribute of the @Producerannotation what the JMS ConnectionFactory JNDI name is, or you can rely on defaults.

Note

The default value for the ConnectionFactory JNDI name is "ConnectionFactory". If you additionally tag the producer as @ProducerLocal instead of @Producer, then "java:/ConnectionFactory" will be used.

@ProducerLocal :

If you tag a producer as @ProducerLocal, the proxy will lookup the connection factory via the default InitialContext when connect() is called. Otherwise, the ConnectFactory reference will be embedded directly within the proxy.

@MessageProperties :

The methods defined in a Producer are turned into JMS messages. The default message properties are a Time To Live of 0, a Priority of 4, and a delivery mode of PERSISTENT. You can override these default values in a couple of ways.

  • You can use the @MessageProperties anntotation and tag the Producer class directly to override the values:
    						
    @Producer
    @MessageProperties(delivery=DeliveryMode.NON_PERSISTENT, timeToLive=1000, priority=1)
    public interface ExampleProducer
    {
    ...
    						
    					
    In this configuration, all method calls on ExampleProducer will use the JMS message properties defined with the @MessageProperties annotation on the interface.
  • You can specify @MessageProperties on a per method basis :
    						
    public interface ExampleProducer
    {
       void method1(String msg, int val);
    
       @MessageProperties(delivery = DeliveryMode.NON_PERSISTENT)
       void method2(String msg, Map<String, String> map);
    }
    						
    					
    So, in the above example, method1() uses the default message properties, and method2() overrides the defaults via the @MessageProperties annotation attached to it.