Corporate Info | News | Solutions | Products | Partners | Services | Events | Download | How To Buy
Introduction to BEA WebLogic Server 5.1: Previous Topic | Next Topic | Contents

JMS (Java Message Service)

Java Message Service (JMS) allows Java programs to exchange messages with other Java programs sharing a messaging system. A messaging system accepts messages from "producer" clients and delivers them to "consumer" clients.

Messaging systems, sometimes called Message-Oriented Middleware (MOM), enable Java clients to use their services by supplying a Java layer called a JMS Provider, which implements JMS for the specific product.

WebLogic JMS implements the JMS specification version 1.0.1, which is online at http://www.javasoft.com/products/jms/docs.html. WebLogic JMS includes a full-featured messaging system, which can be configured by setting properties in the weblogic.properties file, from the WebLogic Console, or programmatically, using the JMS interfaces.

You can use WebLogic JMS with the other WebLogic Server APIs and facilities, such as Enterprise Java Beans, JDBC connection pools, HTTP Servlets, JSP pages and RMI. JMS operations can participate in transactions with other Java APIs that use the Java Transaction API.

JMS Messaging Models

JMS supports two messaging models: point-to-point (PTP) and publish/subscribe. The terms "message producer" and "message consumer" describe clients that send and receive messages in either of the models, although each model has its own specific terms for producers and consumers.

Point-to-Point Messaging

The point-to-point messaging model is based on message Queues. A QueueSender (producer) sends a message to a specified Queue. A QueueReceiver (consumer) receives messages from the Queue. A Queue can have multiple QueueSenders and QueueReceivers, but an individual message can only be delivered to one QueueReceiver. If multiple QueueReceivers are listening for messages on a Queue, WebLogic JMS determines which will receive the next message. If no QueueReceivers are listening on the Queue, messages remain in the Queue until a QueueReceiver attaches to the Queue.

Publish/Subscribe Messaging

The publish/subscribe messaging model is organized around Topics. TopicPublishers (producers) send messages to a Topic. TopicSubscribers (consumers) retrieve messages from a Topic. Unlike the point-to-point model, many TopicSubscribers can receive the same message.

A durable subscriber is a feature of publish/subscribe messaging. A durable subscriber allows you to create a named TopicSubscriber, usually associated with a user or application. JMS retains messages until all TopicSubscribers have received them.

JMS Persistence

Messages can be persistent or non-persistent. WebLogic JMS writes persistent messages to a database via a JDBC connection pool you assign to JMS in the weblogic.properties file. A persistent message is not considered sent until it has been stored in the database. Persistent messages are guaranteed to be delivered at least once. Non-persistent messages are not stored in a database and so they may be lost during a failure. A non-persistent message is guaranteed to be delivered at most once.

JMS Classes and Interfaces

JMS defines several objects that allow you to send and receive messages. JMS objects are subclassed from common parent classes to provide Queue- and Topic-specific versions of the classes.

This section describes the most important JMS classes. See the javax.jms javadocs for complete descriptions of all JMS classes.

ConnectionFactory

You access JMS initially using a ConnectionFactory, which is bound in the WebLogic Server JNDI tree. You look up a QueueConnectionFactory or a TopicConnectionFactory and then use it to create a Connection.

Connection

A Connection (QueueConnection or TopicConnection) manages all of the messaging activity between a JMS client and a JMS provider. It is also a factory for Session objects. A new Connection is stopped-no messages flow until you start the Connection by calling its start() method.

Session

A Session (QueueSession or TopicSession) is a context for producing and consuming messages. Sessions create message consumers and producers and manage the flow of messages, including the ability to group send and receive operations into transactions.

Message Producer

A message producer (QueueSender or TopicProducer) transmits messages from a JMS client to a destination (Queue or Topic). A message producer is associated with the destination Queue or Topic when it is created.

Destination

A destination (Queue or Topic) is the object that receives and distributes messages. Destinations are bound in the JNDI tree and can be defined in the weblogic.properties file or in the WebLogic Console. An application can create a TemporaryQueue or TemporaryTopic that exists only as long as the Connection that creates it.

Message Consumer

A message consumer (QueueReceiver or TopicSubscriber) receives messages from a destination. A consumer is created by calling the CreateReceiver() or CreateSubscriber() method of the Session. Messages can be received asynchronously by providing a class that implements the MessageListener interface. Messages are forwarded to the onMessage() method of the MessageListener. Messages can be received synchronously by calling a receive() method on the consumer. There are receive() methods that return immediately if no message is waiting, wait for a specified period of time, or wait indefinitely for a message.

Message

JMS messages have three parts. The message header contains fields that the JMS system uses to describe and deliver messages. Message headers are also available to applications. The message properties section contains application-defined properties that can be attached to a message. These properties, and the message headers, can be referenced in selectors, which allow a consumer to filter for messages using criteria resembling an SQL where clause. The message body holds the contents of the message.

JMS supports five types of message bodies. The simplest of these is a TextMessage which holds a single Java String value. A BytesMessage holds a stream of uninterpreted bytes and a StreamMessage holds a stream of Java primitive types. BytesMessage and StreamMessage are written and read using methods similar to those of the java.io.DataOutputStream and java.io.DataInputStream classes. A MapMessage holds name/value pairs similar to a Hashtable. The values can be read randomly by specifying the name, or they can be returned in an Enumeration. An ObjectMessage holds any serializable Java object.

Using JMS

Some JMS objects are "administered," which means they must be configured in WebLogic Server before they can be used by applications. The administered objects are QueueConnectionFactories, TopicConnectionFactories, Queues, and Topics. If you plan to use persistent messages, you must also specify a JDBC connection pool where JMS will store data. If you want to use Enterprise JavaBeans and JMS persistence in the same transaction, the two must use the same connection pool.

JMS administered objects can be configured using the WebLogic Console, but to make them permanent, you define them in the weblogic.properties file. See Configuring WebLogic JMS for instructions for configuring JMS in your WebLogic Server.

Sending Messages

To send messages to a JMS Queue or Topic, you use JNDI to look up a ConnectionFactory and the destination Queue or Topic. Using the ConnectionFactory, you create a series of JMS objects, including a Connection, Session, producer (QueueSender or TopicPublisher), and a Message. Then you start the Connection and begin sending messages. Although JMS requires several objects, the send process is very straightforward.

The EmpTrans.java example is an HTTP Servlet that demonstrates how to send messages to a JMS Queue. This example is one part of a rudimentary workflow system. The servlet displays an HTML form and collects data for a transaction against the Emp table. It creates a JMS MapMessage containing the data entered and sends the message to the EmpXactQueue Queue, using the persistent delivery mode so that the message is saved in the JMS database.

The JMS code is in the sendRequest() method of the EmpTrans.java example.

Listing 8-1 EmpTrans.sendRequest()

    private boolean sendRequest(
      int xactType, int empno,
      String ename, String job,
      int mgr, java.sql.Date hiredate,
      float sal, float comm, int deptno)
      {

        boolean result = true;

        Context ctx = null;
        QueueConnectionFactory factory;
        QueueConnection qconnection = null;
        QueueSession qsession = null;
        QueueSender qsender = null;
        Queue queue;
        MapMessage message;
        Hashtable ht = new Hashtable();

        ht.put(Context.SECURITY_PRINCIPAL, "guest");
        ht.put(Context.SECURITY_CREDENTIALS, "guest");
        try {
          ctx = new InitialContext(ht);
          factory = (QueueConnectionFactory)
            ctx.lookup("javax.jms.QueueConnectionFactory");
          qconnection = factory.createQueueConnection();
          qsession = qconnection.createQueueSession(false,
                            Session.AUTO_ACKNOWLEDGE);

          queue = (Queue) ctx.lookup("jms.queue.EmpXactQueue");
          qsender = qsession.createSender(queue);
          message = qsession.createMapMessage();
          qconnection.start();

          message.setInt("xactType", xactType);
          switch (xactType) {
          case 1: // hire
            message.setString("ename", ename);
            message.setString("job", job);
            message.setInt("mgr", mgr);
            message.setString("hiredate", hiredate.toString());
            message.setFloat("sal", sal);
            message.setFloat("comm", comm);
            message.setInt("deptno", deptno);
            break;
          case 2: // termination
            message.setInt("empno", empno);
            break;
          case 3: // salary adjustment
            message.setInt("empno", empno);
            message.setFloat("sal", sal);
            break;
          }
           // persistent, no priority or expiration
          qsender.send(message, DeliveryMode.PERSISTENT,
           0, 0);
        }
        catch (NamingException e) {
          result = false;
          resultMessage = "JNDI error on " + e.getMessage();
        }
        catch (JMSException je) {
          result = false;
          resultMessage = "JMS Error: " + je.getMessage();
        }
        try { qsender.close();     } catch (Exception e) {};
        try { qsession.close();    } catch (Exception e) {};
        try { qconnection.close(); } catch (Exception e) {};
        try { ctx.close();         } catch (Exception e) {};

        return result;
      }

You use the same process, but with different object names, to send messages to Topics. See the WebLogic JMS developers guide, Using WebLogic JMS,for more about other message types, using header fields and properties, and other message delivery options.

Receiving Messages

Setting up a client to receive JMS messages is nearly the same as setting up to send messages. You look up a ConnectionFactory and a Queue or Topic with JNDI, create a Connection, a Session, and then a message consumer. Then you start the Connection and receive messages.

The EmpQueueReader.java example is the Java client application that reads the messages sent by the EmpTrans HTTP Servlet. This client application reads a message, displays the transaction it holds, and asks if the transaction should be approved. If the user approves the transaction, the application applies the transaction using the Emp Enterprise JavaBean.

Here is the JMS code from the EmpQeueuReader.java example:

Listing 8-2 EmpQueueReader.java

    public class EmpQueueReader
    {
      public final static String JNDI_FACTORY =
                     "weblogic.jndi.WLInitialContextFactory";
      public final static String JMS_FACTORY =
                     "javax.jms.QueueConnectionFactory";
      public final static String QUEUE =
                     "jms.queue.EmpXactQueue";

      static QueueConnectionFactory factory;
      static QueueConnection qconnection;
      static QueueSession qsession;
      static QueueReceiver qreceiver;
      static Queue queue;
      static Context ctx;
      private boolean quit = false;

      /**
       * Create all the necessary objects for receiving
       * messages from a JMS queue.
       */
      public static void main(String[] args)
        throws NamingException, JMSException, RemoteException
      {

        Message message;

        Hashtable ht = new Hashtable();
        ht.put(Context.SECURITY_PRINCIPAL, "guest");
        ht.put(Context.SECURITY_CREDENTIALS, "guest");
        ht.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY);
        ht.put(Context.PROVIDER_URL, "t3://localhost:7001");

        ctx = new InitialContext(ht);
        factory = (QueueConnectionFactory) ctx.lookup(JMS_FACTORY);
        qconnection = factory.createQueueConnection();
        qsession = qconnection.createQueueSession(false,
                                                  Session.AUTO_ACKNOWLEDGE);
        queue = (Queue) ctx.lookup(QUEUE);
        qreceiver = qsession.createReceiver(queue);
        qconnection.start();
        while (true) { // get messages until the queue is empty
          message = qreceiver.receiveNoWait();
          if (message == null)
            break;
          if (message instanceof MapMessage) {
            switch (((MapMessage) message).getInt("xactType")) {
            case 1:  // hire
              doHire((MapMessage) message);
              break;
            case 2: // termination
              doTerm((MapMessage) message);
              break;
            case 3: // salary adjustment
              doSal((MapMessage) message);
              break;
            }
          }
        }
        qsession.close();
        qconnection.close();
      }

By using the receiveNoWait() method in a while loop, this example reads and processes all of the messages currently in the Queue and then quits.

Different message types can be mixed on the same Queue or Topic. This example uses instanceof to make sure that it has received a MapMessage. If another message type happens to get into the Queue, this example ignores it.

The switch statement passes the message to a method that handles the transaction type. These methods use the MapMessage get Type() methods to retrieve the transaction data from the Message. Then they use the Emp Enterprise JavaBean to add, remove, or update an Employee record. Here is the code that handles hire transactions:

Listing 8-3 EmpQueueReceiver.doHire()

      static void doHire(MapMessage message)
        throws JMSException, NamingException, RemoteException
      {
        boolean response;
        Emp e;

        System.out.println("\n\n\n=== Hire request ===");
        System.out.println("Employee name ...... : " +
                           message.getString("ename"));
        System.out.println("Job title .......... : " +
                           message.getString("job"));
        System.out.println("Manager ............ : " +
                           message.getInt("mgr"));
        System.out.println("Hire date .......... : " +
                           message.getString("hiredate"));
        System.out.println("Salary ............. : " +
                           message.getFloat("sal"));
        System.out.println("Commission ......... : " +
                           message.getFloat("comm"));
        System.out.println("Department no ...... : " +
                           message.getInt("deptno"));
        System.out.println("===========================");
        response = Confirm("Approve this request?");
        if (response) {
          try {
            EmpBeanHome home = (EmpBeanHome) ctx.lookup("EmpBeanHome");
            e = home.create(NextEmpno(),
                            message.getString("ename"),
                            message.getString("job"),
                            message.getInt("mgr"),
                            java.sql.Date.valueOf(
                 message.getString("hiredate")),
                            message.getFloat("sal"),
                            message.getFloat("comm"),
                            message.getInt("deptno"));
          }
          catch (CreateException ce) {
            System.out.println("EJB CreateException: " +
                   ce.getMessage());
          }
          System.out.println("Created new employee.");
        }
        else
          System.out.println("Ignoring request.");
      }

Running the EmpTrans Example

The EmpTrans example has three parts: the Emp Enterprise JavaBean, an HTTP Servlet, and a Java client application. In addition to setting up these separate applications, you must configure WebLogic Server for JMS.

Notes: This example requires the Emp EJB and depends upon the Emp table in the Cloudscape Demo database that is installed with WebLogic Server. If you want to use a different database, you must edit the EmpTrans.java and EmpQueueReader.java source files. The client application is written to be executed at a command line on the same computer running WebLogic Server. If you want to run the client on a different computer, change the URL in the EmpQueueReader.java file before you compile.

Here are steps to set up and run the EmpTrans example:

1. Set up your development environment as described in Setting your development environment at http://www.weblogic.com/docs51/techstart/environment.html.

2. Set up the Emp Enterprise JavaBean. See Running the Emp Example for instructions.

3. Compile the EmpTrans.java servlet:

   Windows NT:

       javac -d %SERVLET_CLASSES% EmpTrans.java

   UNIX:

       javac -d $SERVLET_CLASSES EmpTrans.java

4. Compile the EmpQueueReader.java client program:

   Windows NT:

       javac -d %CLIENT_CLASSES% EmpQueueReader.java

   UNIX:

       javac -d $CLIENT_CLASSES EmpQueueReader.java

5. Add or change the following properties in your weblogic.properties file:

   - Register the EmpTrans servlet by adding this property:

         weblogic.httpd.register.EmpTrans=examples.intro.EmpTrans

   - Make sure the Emp EJB is deployed:

         weblogic.ejb.deploy=\
               c:/weblogic/myserver/Emp.jar

   - Uncomment the following property so that JMS stores persistent messages in the Cloudscape demo database:

         weblogic.jms.connectionPool=demoPool

   - Define the EmpXactQueue Queue and add ACLs to allow everyone to send or receive messages:

         weblogic.jms.queue.EmpXactQueue=jms.queue.EmpXactQueue
         weblogic.allow.send.weblogic.jms.queue.EmpXactQueue=everyone
         weblogic.allow.receive.weblogic.jms.queue.EmpXactQueue=\
                    everyone

6. Start WebLogic Server.

7. Load the EmpTrans servlet in a browser with a URL like http://localhost:7001/EmpTrans.

8. After entering transactions in the EmpTrans servlet, start the EmpQueueReader client application at a command line with a command such as:

       java examples.intro.EmpQueueReader

The EmpTrans servlet displays a web page similar to this:

The output of the EmpQueueReader application is similar to this:

    C:\>java examples.intro.EmpQueueReader

    === Hire request ===
    Employee name ...... : WHITE
    Job title .......... : CLERK
    Manager ............ : 7839
    Hire date .......... : 1999-10-14
    Salary ............. : 2200.0
    Commission ......... : 0.0
    Department no ...... : 10
    ===========================
    Approve this request? (yes/no) yes
    Created new employee.

You can use the SqlServlet servlet to execute a query such as "select * from emp" to see that the Emp bean has updated the Emp database table with your transactions.

More about JMS

To find out more about WebLogic JMS, see Using WebLogic JMS.

Visit the JMS homepage for JMS FAQs, tutorials, and other news.

The javax.jms javadocs are available online. You can also download the JMS specification from Sun.

Copyright © 2000 BEA Systems, Inc. All rights reserved. Required browser: Netscape 4.0 or higher, or Microsoft Internet Explorer 4.0 or higher.