Sun Java Solaris Communities My SDN Account Join SDN
 
Technical Articles and Tips

Creating Parsers with JAXP and Referencing Enterprise Beans

 
View this issue as simple text February 11, 2003    

Welcome to the Enterprise Java Technologies Tech Tips, for February 11, 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 Parsers with JAXP
.Referencing Enterprise Beans

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 J2EE Tech Tips is written by Mark Johnson, president of elucify technical communications, and co-author of Designing Enterprise Applications with the Java 2, Enterprise Edition, 2nd Edition. Mark Johnson runs an open forum for discussion of the tips.

You can download the sample archive for these tips. The context root for the application is ttfeb2003, and 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 Parsers with JAXP

One of the major strengths of J2EE is its vendor-neutral, integrated support for XML. This month's tip focuses on how to use the Java API for XML (JAXP). JAXP provides a built-in set of interfaces for creating and configuring XML parsers in J2EE applications. This tip assumes you are familiar with the SAX and DOM interfaces to XML documents, and with basic XML terminology.

JAXP gives you complete control over creating both SAX and DOM parsers. JAXP resides in package javax.xml.parsers, which contains the following four classes:

  • DocumentBuilder - a DOM parser, which creates an object of type org.w3c.dom.Document
  • DocumentBuilderFactory - a class that creates DOM parsers
  • SAXParser - a SAX parser, which traverses an XML document, calling back to developer-defined code
  • SAXParserFactory - a class that creates SAX parsers

There are four steps to parsing an XML document through JAXP, for either SAX or DOM:

  1. Get a reference to a factory interface.
  2. Configure the factory to produce the kind of parser you need.
  3. Create the parser.
  4. Use the parser to parse the document.

If you download the sample archive, ttfeb2003.ear, you'll see that it includes a servlet, ParseXMLServlet. The servlet allows you to construct and configure either a DOM or SAX parser, and apply it to an XML document. The code prints a summary of what the parser encounters. So you can experiment with different parser settings and error conditions. The examples in this tip all come from the source code in the sample archive. Because SAX parsing is somewhat simpler than DOM parsing, let's first look at the steps for constructing and using SAX parsers. After that, let's investigate DOM parsing.

Getting and Using SAX Parser

The SAX parser interface contains a static method "newInstance" that returns a reference to the corresponding factory class. Getting a reference to the SAXParserFactory is as simple as calling this method:

  SAXParserFactory spf =
    SAXParserFactory.newInstance();

The next step is to configure the factory. This identifies the features for the parser. A SAXParserFactory has three methods for setting parser features:

  • void setNamespaceAware(boolean awareness) - If set to true, the factory will create a namespace-aware parser
  • void setValidating(boolean validating) - If set to true, the factory will create a parser that validates XML against a DTD
  • void setFeature(String name, boolean value) - Sets vendor-specific features in a SAX 2 parser

The ParseXMLServlet sample code creates a SAX parser, and allows you to turning namespaces or validation on or off. That way you can see how these features affect parser operation. Here's the code that turns namespaces and validation on or off:

  boolean isNamespaceAware =
    req.getParameter("JAXP_NamespaceAware") != null;
  boolean isValidating =
    req.getParameter("JAXP_Validating") != null;
  
  spf.setNamespaceAware(isNamespaceAware);
  spf.setValidating(isValidating);

The final two steps are to create the SAX parser, and use it to parse the file:

  SAXParseHandler sph = new SAXParseHandler(out);
  try {
    SAXParser sp = spf.newSAXParser();
    InputSource isrc = getInputSource(req);
    sp.parse(isrc, sph);

  } catch (ParserConfigurationException pce) {
    throw new ServletException(pce);
  } catch (SAXException sx) {
    ; // Continue and report error
  }

The sample code calls newSAXParser() to create the requested SAXParser. If the parser can't be created for some reason, the factory class throws a ParserConfigurationException. The SAXParser's "parse" method takes an org.xml.sax.InputSource object, which can be constructed from any java.io.Reader (in this case, it's a StringReader for a POST parameter). The parser's second argument is an object that extends class org.xml.sax.helpers.DefaultHandler. It is in this object that you need to implement application-specific functionality. In the case of the sample code, the parser prints parsing events and errors, formatted as HTML, to the HTTP response.

Creating a DOM Parser

Creating a DOM parser follows same four steps described in "Getting and Using a SAX parser". However, the class and method names are different for DOM parsers. Another difference is when the user code (that is, the code you define) is executed. With a SAX parser, user code is "called back" during the parse. There is no call back for DOM parsing. A DOM parser parses an XML document, and returns an interface to an org.xml.dom.Document. The Document interface provides a tree interface to the XML document. Instead of passively being called back by the parser, the user code must explicitly traverse the tree.

To create a DOM parser, you need to first acquire a reference to a DocumentBuilderFactory:

  // Get document builder factory instance
  DocumentBuilderFactory dbf =
    DocumentBuilderFactory.newInstance();

Just as with a SAX parser, the next step is to configure the parser factory. A DocumentBuilder has many more options than a SAXParser. In addition to setValidating and setNamespaceAware, DocumentBuilderFactory also allows you to configure the following features:

  • void setCoalescing(boolean value) - If set, the parser combines all adjacent Text nodes and CDATA section nodes into a single Text node in the Document tree. If not set, CDATA sections may appear as separate nodes in the tree.
  • void setExpandEntityReferences(boolean value) - If set, document entity references are expanded with their content. If not set, they are left as entity reference nodes.
  • void setIgnoringComments(boolean value) - If set, comments in the XML file do not appear in the Document tree; otherwise, they do.
  • void setIgnoringElementContentWhitespace(boolean value) - If set, "ignorable" white space (for example, the white space between elements and processing instructions) does not appear in the Document tree; otherwise, it does.

DOM documents do not have "features" like SAX 2, so DocumentBuilderFactory has no setFeature method.

As with the SAX parser, the final two steps are to create the parser, and use the parser to parse the document:

  SAXErrorHandler errorHandler = new SAXErrorHandler();
  // Get parser
  DocumentBuilder db = dbf.newDocumentBuilder();
  db.setErrorHandler(errorHandler);
  out.println("Successfully created document builder<br>");
  
  // Get input source
  InputSource isrc = getInputSource(req);
  
  // Parse input source
  document = db.parse(isrc);

The document builder factory method, newDocumentBuilder, creates the DocumentBuilder. The DocumentBuilder is the DOM parser. Parser method setErrorHandler takes a developer-defined object that implements interface org.xml.sax.ErrorHandler (in this case, it's an instance of class SAXErrorHandler). The parser calls the error handler when errors occur. Developer-defined code can then report the error and try to recover, or throw an exception. The parser method "parse" takes only an InputSource as input, and returns a DOM Document.

You'll notice that these DOM parser methods take SAX interfaces (ErrorHandler and InputSource). This is because virtually all DOM parsers that implement JAXP use SAX "under the hood" to break up the input XML, and use the resulting event stream to build the DOM tree representation.

Running the Sample Code

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

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

	
	$J2EE_HOME/deploytool -deploy ttfeb2003.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/ttfeb2003.

For a J2EE-compliant implementation other than the Reference Implementation, use your J2EE product's deployment tools to deploy the application on your platform.

When you run the application you should see a user interface that includes the following:

  • A text field in which to enter XML you want to parse
  • Radio buttons for selecting SAX or DOM parsing
  • Checkboxes for turning namespace awareness and validation on or off
  • Checkboxes to set various DOM-only features

Experiment by running the application multiple times, each time using different settings.

For example, suppose you entered the following XML for parsing:

  <movies>
    <movie>
       <title>
          Gone With the Wind
       </title>
       <actors>
           <actorName> 
              Vivien Leigh
           </actorName>
           <actorName> 
              Clark Gable
           </actorName>
       </actors>
       <description>
           A movie classic set during the Civil War.
       </description>
    </movie>
  </movies>

Here's part of what the output would look like if you select SAX, but don't turn on the namespace-aware or validate XML features:

SAX, namespace-aware and validate turned off

If you turn on the namespace-aware or validate XML features, you should see the following added to the output:

SAX, namespace-aware and validate turned on

Here's part of what the output would look like if you select DOM, and check all features (including DOM-only features):

DOM, all features checked
.
.

Referencing Enterprise Beans

Business entities often need to be able to reference one another to perform useful work. For example, an Invoice object usually needs to be able to access its associated LineItem objects.

This tip covers three methods for referencing between enterprise beans. The best way to maintain references between enterprise beans is by using container-managed relationships (CMR). The first method explain how to maintain references between beans using CMR. The second and third methods explain how to maintain references between beans when CMR is not available. The first two methods apply only to entity beans, while the last one applies both to entity beans and session beans. In this discussion, a "source" bean is where a relationship starts, and a "target" bean is where the relationship ends.

Method I. Use Container-Managed Relationships

The easiest way for one entity bean to reference another entity bean is to use container-managed relationships, or CMR. With CMR, you first model the relationship between a source bean and a target bean using accessors in the source bean's interface. Accessors are like the "setter" and "getter" methods used by traditional JavaBeans components and CMP entity beans. Unlike a conventional accessor, which returns a primitive type or a serializable object, a CMR accessor returns a reference to another entity bean. You need to describe the relationship in the bean's deployment descriptor. Based on the description, the container manages the implementation of the relationship between the beans.

For example, a Person object might have two relationships to other Person objects, one for each parent.

Person object-to-Person object relationships

These relationships between the source bean and the two target beans could be represented in the bean's local interface as two pairs of accessors, like this:

  public interface Person extends EJBLocalObject {
         // ... other methods

         public Person getMother();
         public void setMother(Person mom);
         public Person getFather();
         public void setFather(Person dad);
  }

These accessor methods would also appear in the bean implementation class:

  public abstract class PersonBean implements EntityBean
  {
         // ... other method implementations
        
         // CMR property accesssors
         public abstract Person getMother();
         public abstract void setMother(Person mom);
         public abstract Person getFather();
         public abstract void setFather(Person dad);
  }

Notice that the bean implementation class and the accessor methods are declared to be abstract. This is what makes using CMR so easy -- you simply declare the methods. There's no need to write implementations for these methods. The container writes the implementation based on the description of the relationship that appears in the deployment descriptor.

Container-managed relationships can even be used for relationships between a bean and many other beans. For example, an Invoice bean might have a relationship to multiple LineItem beans.

The CMR approach has some limitations:

  • Because CMR is available only for local entity beans, this method won't work for remote interfaces or non-entity beans.
  • Clients of local bean interfaces are always co-located inside the same JVM as the beans themselves, so enterprise beans and Web components are the only objects that can access local entity beans.
  • If your server is older than version 2.0, you won't have access to local interfaces or CMR. Most users of EJB technology have upgraded to version 2.0 or above, so this is not a limitation for most people.

Method II. Store the Bean's Primary Key, and Use the Bean's Home Interface to Find the Corresponding Instance

This is the method most enterprise bean developers already understand and use. The source bean maintains the primary key of the target bean as a property. When the source bean needs to access the target bean, it first acquires the target bean's Home interface, using JNDI. It then uses a finder method in the Home interface to get the component interface to the target bean.

For example, imagine that a Person bean needs to access another Person bean that represents that Person's mother. Assume that a Person's primary key is some arbitrary string (a passport number, for example).

Accessing a target bean through the primary key

The implementation code for a Person bean to get a reference to the corresponding mother bean looks like this:

  public class PersonBean extends EJBObject {
     // ... other methods

     // A CMP accessor to get the primary
     // key for the mother bean
     public abstract String getMotherPrimaryKey();

     // Return a reference to the Mother bean
     public Person getMother() {
        String motherPK = getMotherPrimaryKey();
        Person motherPerson = null;

        // If mother is known, look it up.
        if (motherPK != null) {
           try {
              // Get JNDI initial context
              InitialContext context = 
                new InitialContext();
   
              // Get home interface
              Object motherObject =
                 context.lookup(
                   "java:comp/env/ejb/Person");
   
              // Remote-interface-safe type cast 
              // to home interface
                PersonHome personHome =
                 (PersonHome)PortableRemoteObject.narrow(
                   object, PersonHome.class);
   
            // Find mother
              motherPerson = 
                personHome.findByPrimaryKey(motherPK);
           } catch (NamingException nex) {
              ...
           } catch (RemoteException rex) {
              ...
           } catch (FinderException fex) {
              motherPerson = null;
           }

           return motherPerson;
     }
  }

The PersonBean example shows how to access a remote entity bean. Local interfaces provide superior performances to remote interfaces, and so are usually preferable. Remote interfaces should be used in the minority of cases where location transparency is important. If the getMother method shown above were accessing a local interface instead of a remote interface, the call to PortableRemoteObject.narrow could be replaced with a simple typecast of the motherObject to interface PersonHome.

Maintaining references between beans by keeping their primary keys is not the most efficient technique, but it is the most flexible. It works for both local and remote interfaces, and also works for any enterprise Java client that can use enterprise beans. Compare the code samples for methods I and II: CMR improves performance, and the code is much easier to write.

Method III. Use a Handle to the Instance

This method works only for remote interfaces, but will work for both entity and session beans. A Handle is a persistable reference to an enterprise bean. Note, though, that persisted bean Handles are not portable. The serialized representation of a reference to an enterprise bean is specific to the application server.

The J2EE standard interface javax.ejb.EJBObject, which all entity and session bean remote interfaces must extend, contains a method called getHandle(). This method returns a Handle object. The Handle object is a serializable (and therefore persistable) reference to an entity or session bean. Because a Handle is Serializable, it can be written to a file (by a Web component, for example), transmitted across a network for use by another program, or saved in a database field. A Handle is a good way to pass a reference to an EJB remote interface from one program (or one tier) to another. The following example code shows how an application client could send a Handle to a UserProfile entity bean to another application component. (The method "sendBytes()" uses a messaging system such as JMS to send the bytes to the other component.)

Using a Handle
   UserProfile profile = getUserProfile(); 

   Handle handle = profile.getHandle();
   ByteArrayOutputStream baos
      = new ByteArrayOutputStream();
   ObjectOutputStream oos =
      new ObjectOutputStream(baos);
   oos.writeObject(handle);
   oos.close();
   byte[] bytes = baos.toByteArray();
   sendBytes(bytes);   

A Handle has a single method, getEJBObject(), that returns the remote interface of the bean instance that originally produced the handle. The following example code retrieves an object handle from the component above (using method receiveBytes), and uses it to get the entity bean corresponding to the handle.

   byte[] bytes = receiveBytes();
   ByteArrayInputStream bais =
      new ByteArrayInputStream(bytes);
   ObjectInputStream ois =
      new ObjectInputStream(bais);
   Handle handle =
      (Handle)ois.readObject();
   Object o =
      handle.getEJBObject();
   UserProfile profile = 
      PortableRemoteObject.narrow
         (o, UserProfile.class);

Note that the data sent across the network in the example code above is a reference to the bean, not the state of the bean. The data sent through the network is not the bean data itself, it is data that the server uses to locate the enterprise bean instance that the Handle references. If the bean no longer exists when getEJBObject() is called, an exception is thrown.

.
.

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: jdc-webmaster@sun.com

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/index.html


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

This document is protected by copyright.

Sun, Sun Microsystems, Java, Java Developer Connection, JavaServer Pages, JSP, J2SE, J2EE, and J2ME are trademarks or registered trademarks of Sun Microsystems, Inc. in the United States and other countries.