.
.
Core Java Technologies Technical Tips
.
   View this issue as simple text July 28, 2003    

In this Issue

Welcome to the Enterprise Java Technologies Tech Tips, for July 28, 2003. Here you'll get tips on using enterprise Java technologies and APIs, such as those in Java 2 Platform, Enterprise Edition J2EE.

This issue covers:

.Creating a Message-Driven Bean
.Iterating Nodes in DOM Trees

These tips were developed using Java 2 SDK, Standard Edition, v 1.4 and Java 2 SDK, Enterprise Edition, v 1.3.1 (Reference Implementation).

This issue of the Tech Tips is written by Mark Johnson, president of elucify technical communications, and co-author of Designing Enterprise Applications with the J2EE Platform, 2nd Edition. Mark Johnson runs an open forum for discussion of the tips at http://groups.yahoo.com/group/techtipsarchive/.

You can download the sample code. The context root for the application is ttjul2003. The index.html welcome file indicates how to use the sample code. Any use of this code and/or information below is subject to the license terms.

.

CREATING A MESSAGE-DRIVEN BEAN

The J2EE platform offers several types of enterprise components. Entity beans represent business data and functionality, and session beans represent business processes. A Message-Driven Bean, or MDB, implements business process logic in response to receipt of an asynchronous message through the Java Messaging Service (JMS). This month's first tip explains how to create a simple message-driven bean.

An MDB provides server-side business functionality in response to an asynchronous message. The communication path from a client to an MDB is as follows:

  • The client sends a message to a JMS destination, which is associated with an EJB container.

  • When a message arrives in a queue associated with an MDB, the container passes the message to the onMessage method of an idle MDB instance.

  • The MDB instance then performs the requested service using the message as its input.

figure 1

Note that as of EJB 2.1, MDBs can also receive messages from Connectors.

The client can be any JMS message sender, not just an application client or servlet. The message destination can be either a JMS Queue or JMS Topic. JMS ensures delivery of the message to the destination, and automatically handles acknowledgment, retries, and transactions.

MDBs have several traits in common with stateless session beans:

  • Both are enterprise beans components that implement business processes within an EJB container.

  • Both are "stateless", meaning that they contain no conversational, client-specific state across invocations.

  • Both can contain state that is not specific to a particular client, such as an EJB object or home interface reference, or a database connection.

  • Multiple instances of both MDBs and stateless session beans can be managed by the container.

  • The EJB container serializes service requests to the component, so the component code does not have to be reentrant.

  • Both kinds of beans can use container services such as life cycle management, security, concurrency, and transaction handling.

However there are some important differences between MDBs and stateless session beans:

  • Service requests to a stateless session bean are synchronous (by calling methods on a component interface). Service requests to an MDB are asynchronous (by sending a JMS message to a message destination).

  • A stateless session bean can return data directly to a caller as the return value of a component interface method invocation. While the JMS destination of an MDB can acknowledge receipt of a message, the component can't return data to the sender of the message. If an MDB wants to send information back to the sender, it must send another JMS message as a reply.

  • Only valid EJB clients (such as J2EE application clients, and Web components such as servlets and JSP pages) can access stateless session beans. By contrast, any JMS sender can invoke an MDB by sending a message to that bean's JMS destination.

  • A session bean has a client security context that controls which clients can create and use the bean. MDBs have no client security context. Access to an MDB is managed by controlling access to the MDB's JMS destination.

MDB REQUIREMENTS

Developing an MDB is as easy as developing any other JMS message listener. You need to create only one class, the message-driven bean class (MDB class). The MDB class must meet the following requirements:

  • It must be public, and must not be abstract or final. Also, it must not implement a finalize method. It can implement other methods as desired.

  • It must have a public, zero-argument constructor. Keep in mind that J2EE services (such as access to JNDI) are not available in the constructor. MDB instances should be initialized in the ejbCreate method.

  • It must have a single public void method, ejbCreate, which takes no arguments. ejbCreate is called after an MDB instance is created, and also after setMessageDrivenContext is called (see below). The purpose of ejbCreate is to allow the bean to initialize its internal state. For example, a database connection used by an MDB might be created in ejbCreate.

  • It must implement interface javax.ejb.MessageDrivenBean. The interface has two methods:

    • setMessageDrivenContext: The EJB container calls setMessageDrivenContext when it creates a new MDB instance, passing an argument of type javax.ejb.MessageDrivenContext. The MessageDrivenContext interface is analogous to the EntityContext and SessionContext interfaces of entity and session beans. The MDB usually retains a copy of the object, and uses it later to request services from the container. Because MDBs do not have home interfaces, the MessageDrivenContext methods getEJBHome and getEJBLocalHome are meaningless and should not be called.

    • ejbRemove: The EJB container calls ejbRemove before removing an MDB instance. This method allows the bean to clean up any resources it might hold, such as database connections. The container may automatically remove MDB instances as a part of its resource management mechanism.

  • It must implement javax.jms.MessageListener. This interface has a single method:

    • onMessage: An MDB's onMessage method implements application-specific business functionality. This is the method that receives Message objects from a JMS destination.

The application-specific functionality you want to implement in the MDB goes in the onMessage method. The other methods provide support for the bean's life cycle events. After you write the MDB class, the next step is to deploy the MDB.

MDB DEPLOYMENT

Because an MDB is an enterprise bean, it is deployed in a JAR file. The deployment descriptor in the JAR file (META_INF/ejb-jar.xml) contains a <message-driven> tag for each MDB used in the application. The contents of this tag configure the MDB to its container, controlling such aspects as transaction behavior, acknowledgment mode, and external component references.

A deployer uses application deployment tools to associate an MDB class with a JMS Queue or Topic.

The following figure shows the J2EE SDK's deploytool being used to configure LoaderMDB bean (the MDB in the sample code) with a queue called jms/queue/LoaderQueue.

figure 2

SAMPLE CODE

This month's sample code demonstrates a simple EJB loader that loads data into entity beans. In this program, a standalone program called TestQueue sends the contents of an XML file to a named JMS message destination. The EJB container routes the message to an MDB named LoaderMDB. The MDB's onMessage method parses the XML and creates corresponding PersonBean objects, if it can.

figure 3

Keep in mind that this is a sample program. A "real world" entity bean loader would bulkload entity bean data into a database using backend tools, instead of creating beans through a home interface.

The sample code for LoaderMDB contains all of the required methods for an MDB:

  • The class implements MessageDrivenBean and MessageListener:
    public class LoaderMDB
       implements MessageDrivenBean, MessageListener {
  • A public, zero-argument constructor is called when a new instance is created:
    public LoaderMDB() {
    }
  • The methods ejbCreate and ejbRemove initialize and remove the internal state of the LoaderMDB:
     private Logger _logger;
     private PersonLocalHome _plh;

     public void ejbCreate() {
        _logger = Util.createLogger(
                            this.getClass().getName());
        _plh = null;
     }

     public void ejbRemove()
        throws EJBException {
     }
  • The method setMessageDrivenContext saves the MessageDrivenContext for later use:
     private MessageDrivenContext _mdc;
   
     public void setMessageDrivenContext(
                              MessageDrivenContext mdc)
        throws EJBException {
        _mdc = mdc;
     }
  • The onMessage method creates entity beans corresponding to the data in the XML document. Method onMessage receives a TextMessage containing XML input to the MDB. The code uses instanceof to ensure that only TextMessages are processed. The code downcasts the message to a TextMessage and gets its text. The text is then parsed into a DOM Document. If the document's top node is "LOAD", it passes the entire document tree to the loadDocument method. The details of how the loadDocument method extracts data from the DOM tree are the topic of the next tip (see "Iterating Nodes in DOM Trees").
     public void onMessage(Message message) {

        // Process only TextMessages
        if (!(message instanceof TextMessage))
           return;

        try {
           String msg = ((TextMessage)message).getText();
           _logger.info(
                 "MDB received message: " + msg + "---");

           Document doc = parseMessage(msg);

           Element root = doc.getDocumentElement();
           if (
            root.getTagName().equalsIgnoreCase("LOAD")) {
              loadDocument(root);
           }
        } catch (JMSException jex) {
           _logger.warning(jex.toString());
           throw new EJBException(jex);
        }
     }

The MDB class is deployed in an ejb-jar file with a suitable deployment descriptor called META-INF/ejb-jar.xml. Here is an excerpt from the descriptor, containing the declaration of the MDB LoaderMDB:

  <ejb-jar>
    <display-name<LoaderJAR</display-name>
    <enterprise-beans>
      <!-- Process receiving a PO from -->
      <!-- the Java Pet Store Start-->
      <message-driven>
        <description>
          Message-driven bean that loads data 
          into enterprise beans
        </description>
        <display-name>LoaderMDB</display-name>
        <ejb-name>LoaderMDB</ejb-name>
        <ejb-class>
          com.elucify.tips.jul2003.LoaderMDB
        </ejb-class>
        <transaction-type>Container</transaction-type>

        <message-driven-destination>
          <destination-type>
            javax.jms.Queue
          </destination-type>
        </message-driven-destination>

        <!-- EJB local refs -->
        <ejb-local-ref>
          <ejb-ref-name>ejb/local/Person</ejb-ref-name>
          <ejb-ref-type>Entity</ejb-ref-type>
          <local-home>
            com.elucify.tips.jul2003.PersonLocalHome
          </local-home>
          <local>
            com.elucify.tips.jul2003.PersonLocal
          </local>
          <ejb-link>
            jul2003-ejb.jar#PersonBean
          </ejb-link>
        </ejb-local-ref>
      </message-driven>

The <message-driven> tag declares an MDB. It has a description, display-name, ejb-name, and other elements, just like any other enterprise bean. Unlike other enterprise bean deployment descriptors, it doesn't define home or component interfaces. A component developer can use the <message-driven-destination> tag to tell the application deployer what kind of JMS destination to use. In this case, the LoaderMDB uses a javax.jms.Queue.

See the section "Running the Sample Code" for instructions on how to run the sample program.

For more information about MDBs, see the section "What Is a Message-Driven Bean?" and "A Message-Driven Bean Example" in the J2EE Tutorial.

.
.

ITERATING NodeS IN DOM TREES

The February 11, 2003 Tech Tip titled "Creating Parsers with JAXP" showed how to build a XML DOM parser using the Java API for XML Processing (JAXP). The following tip explains the structure of a DOM tree, and provides some hints for effectively using DOM interface methods.

A DOM tree is a tree of document nodes. It represents the lexical structure of an XML document as a tree of objects. A parser that reads an XML file and produces a tree of these DOM objects is called a DOM parser. In Enterprise Java, DOM parsers implement interface javax.xml.parsers.DocumentBuilder. A program acquires a reference to a DocumentBuilder using class javax.xml.parsers.DocumentBuilderFactory.

The following figure shows an XML document that is parsed by an XML parser (of type DocumentBuilder) into a tree of nodes. The root node is of type org.w3c.dom.Document.

figure 4

Each element in a DOM tree implements one of the interfaces in package org.w3c.dom. This package was defined by the World Wide Web Consortium, or W3C.

The following figure shows an inheritance diagram of the interfaces that can be found in a DOM document tree.

figure 5

Every object in a DOM document is a subtype of interface Node. The top-level item in a document tree is always either a Document or a DocumentFragment (which can represent a part of a Document). Note that the Document and DocumentFragment interfaces are subinterfaces of Node, even though they always appear at the top of a Document. Below the top-level Node is an Element (which is also a Node) called the document element. (A Document can have other children, such as entities, entity references, and processing instructions.) The Document element contains the top-level tag that encloses the entire document.

In the DOM tree illustrated above, the document element is the one labeled "A".

The methods used to iterate a tree of Nodes always return references of type Node, but these objects have a "real" type, as well. To work effectively with DOM trees, you have to understand what structures may appear in a DOM document. You also need to know how to cast the Node reference down to the object's real type, so you can use its API.

The following examples from the sample code demonstrate how to do several common read-only operations on a DOM tree.

GETTING THE DOCUMENT ELEMENT

Often, the first thing you want to do with a Document is get its document element, so that you can traverse the document tree. In the sample code, the onMessage method in the EJBLoader receives an XML document from a JMS client and converts it to a String. The method then parses the String and gets the document element, like this:

   Document doc = parseMessage(msg);
         
   Element root = doc.getDocumentElement();
   if (root.getTagName().equalsIgnoreCase("LOAD")) {
      loadDocument(root);
   }

Method getDocumentElement returns an Element, so there's no need to typecast it. In the next line, method getTagName returns the name of the tag. If the tag name is "LOAD", the code passes the document element to loadDocument.

ITERATING CHILD NodeS

Every object in a DOM tree implements interface Node, and every object has zero or more child nodes. The child nodes of an Element correspond to the Elements it contains, plus other Node subtypes, such as EntityReferences and CharacterData subtypes. When you iterate over the children of an Element, be sure that you only subcast Node to Element when the Node really is an Element. If you subcast a Text Node to Element, for example, you get a ClassCastException. Method loadDocument below iterates over the child nodes of document element of the DOM tree. Each time it encounters an Element with tag name PERSON, it calls loadPerson on that Node. Other Nodes are ignored.

   protected void loadDocument(Element doc) {
      NodeList kids = doc.getChildNodes();
      Node kid;

      _logger.info("Loading document");

      for (int i = 0; i < kids.getLength(); i++) {
         kid = kids.item(i);
         if (kid instanceof Element &&
             ((Element)kid).getTagName().equalsIgnoreCase(
                                                 "PERSON")) {
            loadPerson((Element)kid);
         }
      }

      _logger.info("Done loading document");
   }

Node method getChildNodes returns a NodeList. NodeList is an interface with two methods. One method of NodeList is getItem(i), which returns the "ith" item in the list. List items are numbered from 0 to n-1, where n is the length of the list. The other NodeList method is getLength, which returns the list length.

The for loop in the code above iterates over all of the child nodes of the document element. The instanceof keyword checks if the Node can be safely typecast to Element. If the element tag name is "PERSON", a Person bean is loaded from the data in the Element. DOM API methods in the Java platform often require typecasting from Node to a subinterface of Node, as this example demonstrates. In such cases, it's best to use instanceof to check type compatibility before a typecast.

An equivalent way to iterate over the child Nodes of a Node appears in the entity bean method Person.load, shown below:

   public void load(Element person)
      throws CreateException {

      Node node = person.getFirstChild();

      while (node != null) {

         if (node instanceof Element) {
            Element e = (Element)node;
            String tagname = e.getTagName();

            if (tagname.equalsIgnoreCase("ADDRESS")) {
               loadAddress(e);
            } else {
               // Get text contents of node
               String nodevalue = null;
               nodevalue = Util.getElementTextContents(e);
               set(tagname, nodevalue);
            }
         }
         node = node.getNextSibling();
      }
   }

Method getFirstChild returns the first child Node of a given Node. Method getNextSibling returns the next "sibling" Node of a given Node. Another pair of methods, getLastChild and getPreviousSibling, can be used to traverse child Nodes in reverse order.

GETTING ELEMENTS BY TAG NAME

A somewhat easier way to code the loadDocument example above is to use method Element.getElementsByTagName, which returns a NodeList of all of the subelements of an Element that have a given tag name. Because each Node returned by this method is guaranteed to be an Element, there's no need to check the type with instanceof. Method loadDocument could be also be written as follows:

   protected void loadDocument(Element doc) {
      NodeList kids = doc.getElementsByTagName("PERSON");
      Element kid;

      _logger.info("Loading document");

      for (int i = 0; i < kids.getLength(); i++) {
         kid = (Element)kids.item(i);
         loadPerson((Element)kid);
      }

      _logger.info("Done loading document");
   }

The typecast is still necessary because NodeList.item returns Node, not Element. The problem with this approach is that getElementsByTagName returns all subelements with the given name, at any depth in the tree. If the input XML were constrained by a DTD to have PERSON elements only immediately within a LOAD element, this problem would be overcome.

For more information about the Document Object Model see the "Document Object Model" chapter in the Java Web Services Tutorial.

.
.

RUNNING THE SAMPLE CODE

Download the sample archive for these tips. The application's context root is ttjul2003. The downloaded EAR file also contains the complete source code for the sample.

You can deploy the application archive (ttjul003.ear) in the J2EE Reference Implementation using the deploytool program:

    $J2EE_HOME/deploytool -deploy ttjul2003.ear localhost

Replace localhost with the name of the host on which the server is installed. For a standard installation on a single machine, the hostname typically (and literally) is localhost. You can access the application at http://localhost:8000/ttjul2003.

It is highly recommended that you use the J2EE Reference Implementation to run this example. To run the sample program, follow these steps:

  1. Be sure that both the j2ee server and database server are running. For the J2EE Reference Implementation, execute both:
       
         j2ee -verbose
         
       and
       
         cloudscape -start
    
  2. Download the ear file as described above.
  3. Deploy the ear file to your server using deploytool.
  4. Extract the test data file from the archive:
          jar xvf ttjul2003.ear data.xml
    
  5. Create a JMS Queue called "jms/queue/LoaderQueue":
          j2eeadmin -addJmsDestination jms/queue/LoaderQueue queue
    
  6. Run the "TestQueue" sample program as follows (all on one line):
       
         java -classpath "ttjul2003.ear:$CLASSPATH" TestQueue 
         send jms/queue/LoaderQueue -f data.xml
    
    You might need to adjust the syntax for your command interpreter.
  7. Check the output log of your server to see the messages produced by the MDB.

The sample code also includes a Web application that allows you to send JMS messages to the MDB, print a list of all Persons in the database, and find an individual Person in the database. To use the form, deploy the application and then visit http://localhost:8000/ttjul2003.

For a J2EE-compliant implementation other than the Reference Implementation, use your J2EE product's deployment tools to deploy the application on your platform. You might need to perform additional tasks, such as generating SQL for CMP, and defining database names.

.
.
.

Reader Feedback

  Very worth reading    Worth reading    Not worth reading 

If you have other comments or ideas for future technical tips, please type them here:

 

Have a question about Java programming? Use Java Online Support.

.
.

IMPORTANT: Please read our Terms of Use, Privacy, and Licensing policies:
http://www.sun.com/share/text/termsofuse.html
http://www.sun.com/privacy/
http://developer.java.sun.com/berkeley_license.html


Comments? Send your feedback on the Enterprise Java Technologies Tech Tips to: http://developers.sun.com/contact/feedback.jsp?category=sdn

Subscribe to other Java developer Tech Tips:

- Core Java Technologies Tech Tips. Get tips on using core Java technologies and APIs, such as those in the Java 2 Platform, Standard Edition (J2SE).
- Wireless Developer Tech Tips. Get tips on using wireless Java technologies and APIs, such as those in the Java 2 Platform, Micro Edition (J2ME).

To subscribe to these and other JDC publications:
- Go to the JDC Newsletters and Publications page, choose the newsletters you want to subscribe to and click "Update".
- To unsubscribe, go to the subscriptions page, uncheck the appropriate checkbox, and click "Update".


ARCHIVES: You'll find the Enterprise Java Technologies Tech Tips archives at:
http://developer.java.sun.com/developer/EJTechTips/


Copyright 2003 Sun Microsystems, Inc. All rights reserved. 4150 Network Circle, Santa Clara, CA 95054 USA.

This document is protected by copyright.

Trademark Information: http://www.sun.com/suntrademarks/
Java, J2SE, J2EE, J2ME, and all Java-based marks are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.


Sun Microsystems, Inc.
.
.