The Real World

We finish this chapter up as we have finished our others: with a look at issues that affect using the tools in this chapter in a real-world situation. In this chapter, these include threading issues with writing XML data, alternatives to using JDOM for writing XML data, and handling lost references to the XmlRpcConfiguration utility class.

Threading, Writing, and Arithmetic

Although we sped through our look at the saveConfiguration( ) method and how it handled writing out XML, you may have noticed something key in the method declaration:

/**
 * <p>
 * This will save the current state out to the specified 
 *   <code>OutputStream</code>.
 * </p>
 *
 * @throws <code>IOException</code> - when errors occur in saving.
 */
public synchronized void saveConfiguration(OutputStream out)
    throws IOException {

    // Method implementation
}

We use the synchronized keyword here to ensure that the lock for our XmlRpcConfiguration object is obtained before the configuration data is written. This is particularly important when using XML, as APIs like JDOM can be built on implementations that periodically reload the underlying data; in other words, changes by other programs to the XML data could cause serious errors or corrupted data if this method writes to the data as well.

Additionally, you should be very careful when multiple applications write to the same XML data source. Entire database systems have been written to handle pessimistic and optimistic locking, sharing data, and the other complex issues that surround concurrent access to data. While XML addressed portable data, it certainly does not address issues such as this. A good rule of thumb is to provide one single point of entry for writing to any single XML data source. Multiple entry points for mutability can cause tricky bugs and serious headaches.

JDOM, SAX, or DOM, Revisited

In Chapter 11, we discussed the alternatives to using JDOM for reading XML data. We now examine alternatives for writing and modifying XML. This provides you a complete look at the Java APIs, and should allow you to use any API you choose for your various projects.

For comparison, here is the saveConfiguration( ) method from the last chapter, which uses JDOM:

/**
 * <p>
 * This will save the current state out to the specified 
 *   <code>OutputStream</code>.
 * </p>
 *
 * @throws <code>IOException</code> - when errors occur in saving.
 */
public synchronized void saveConfiguration(OutputStream out)
    throws IOException {

    // Get the JavaXML namespace
    Namespace ns = Namespace.getNamespace("JavaXML",
                   "http://www.oreilly.com/catalog/javaxml/");

    // Create the root element
    Element root = new Element("xmlrpc-config", ns);
    Document doc = new Document(root);
    doc.setDocType(new DocType("JavaXML:xmlrpc-config",
                               "DTD/XmlRpc.dtd"));
                                   
    root.addChild(new Element("hostname", ns)
            .setContent(hostname))
        .addChild(new Element("port", ns)
            .addAttribute("type", "unprotected")
            .setContent(portNumber + ""))
        .addChild(new Element("parserClass", ns)
            .setContent(driverClass));
                
    Element handlersElement = new Element("handlers", ns);
    Enumeration e = handlers.keys(  );
    while (e.hasMoreElements(  )) {
        String handlerID = (String)e.nextElement(  );
        String handlerClass = (String)handlers.get(handlerID);
            
        handlersElement.addChild(new Element("handler", ns)
            .addChild(new Element("identifier", ns)
                .setContent(handlerID))
            .addChild(new Element("class", ns)
                .setContent(handlerClass))
            );
    }
        
    root.addChild(new Element("xmlrpc-server", ns)
            .addChild(handlersElement));
                       
    // Output the document, use standard formatter
    XMLOutputter fmt = new XMLOutputter(  );
    fmt.output(doc, out);
}

Now we look at the SAX and DOM alternatives to this approach.

SAX

The discussion of SAX is short: simply put, you cannot modify XML with SAX. Because SAX is an event-based approach to XML data, it is only useful in parsing an XML document. The callbacks it defines are specifically designed for that purpose, and as SAX has no concept of an overall picture of an XML document, it also has no concept of changing that overall picture. It is this very fact that has caused much of the popularity of DOM; until recently, DOM has been the only means of creating XML from Java without having to write out XML directly using streams.

DOM

The Document Object Model does provide a means of creating and modifying XML from Java. However, it takes a more rigid view of the tree structure of an XML document. First, it considers everything in the DOM tree a Node (remember our discussions in Chapter 7)? Because these Nodes are all part of a tree, it is necessary to create a Node with an association to a particular DOM tree, represented by a DOM Document object. To ensure this model is adhered to, there is no facility for instantiating a DOM Node directly; instead, it is the Document interface within DOM that defines the createElement( ), createAttribute( ), and other Node creation operations. Another point to remember is that this rigid tree model considers everything in the tree a Node, including textual data. This means that there is no concept of an element’s content in DOM; instead, a DOM Element has child Nodes, some of which may be Text Nodes. This means that setting the value of an Element requires using the createTextNode( ) method to create a Text Node, and then adding that new Node to the desired parent Element. Of course, the createTextNode( ) method itself is invoked on the overall DOM Document object, to ensure the correct association with the DOM tree.

Note

This may feel a bit confusing to you: the creation of an XML element with text involves a DOM Element Node, a DOM Text Node, and a DOM Document object all working in concert. This has caused some newer XML developers to become frustrated, as they attempt to change an Element’s textual content with setNodeValue( ) , and get very different results than expected. Be careful when using DOM to keep thinking in a very strict tree model, and you will be able to avoid these types of problems.

With this in mind, let’s look at building up an XML document from the data supplied by a user in the XmlRpcConfigurationServlet application.

You might be expecting us to repeat the process of finding each XML element with data and then changing the value of the textual nodes, as in our first saveConfiguration( ) method for JDOM. Although this is certainly possible (as DOM provides a setValue( ) method for textual Nodes), this is neither the easiest nor the quickest way to handle this task. Instead, we use the various createXXX( ) methods that are defined in the DOM Document interface. A method for each type of DOM Node is provided, such as createElement( ) , createTextNode( ) , and createAttribute( ) . As each of these is created, it is assigned to the Document object used to create it. This maintains an ownership between each created Node and the DOM tree they belong to.

In this way, all the needed elements and data can be created within our Java code. However, this is only half of the task; each element and its data then need to be inserted into the DOM tree and assembled into the correct hierarchy. The simplest way to insert a Node into the tree is to use the appendChild( ) method on the parent node.

Warning

Make sure you understand the difference between createXXX( ) and appendNode( ). While createXXX( ) results in a Node that is associated with a DOM Document object, it does not insert that Node into the Document; the appendNode( ) method must still be invoked upon the desired parent of the new Node. Failure to distinguish between these two methods can result in a huge number of Nodes associated with a DOM Document object, but an empty DOM tree.

In this way, we can build up the complete document starting with the root element. Finally, we can replace the old root element with the new one, completing the update of our tree.

In addition to our not needing to perform complicated searching and retrieval of elements and data, building a tree this way is often much quicker. Searches through a DOM tree, particularly when the DOM tree becomes large, can take quite a lot of processing time; creating XML from the root element up is a much faster alternative to this extensive searching. The code to perform this task is included here:

/**
 * <p>
 * This will save the current state out to the specified 
                  
 *   <code>OutputStream</code>, using DOM
 * </p>
 *
 * @throws <code>IOException</code> - when errors occur in saving.
 */
public synchronized void saveConfiguration(OutputStream out)
   throws IOException {

    String NAMESPACE_URI = "http://www.oreilly.com/catalog/javaxml/";

    // We assume the DOM Document object was loaded in
    //   parseConfiguation(  ) and is saved in a member variable called
    //   <code>doc</code>.
    Element oldRoot = doc.getDocumentElement(  );
    Element newRoot = 
        doc.createElementNS(NAMESPACE_URI, "JavaXML:xmlrpc-config");
        
    // Handle hostname
    Element hostnameNode =
        doc.createElementNS(NAMESPACE_URI, "JavaXML:hostname");
    hostnameNode.appendChild(
        doc.createTextNode(hostname));
    newRoot.appendChild(hostnameNode);
        
    // Handle port number
    Element portNumberNode =
        doc.createElementNS(NAMESPACE_URI, "JavaXML:port");
    portNumberNode.appendChild(
        doc.createTextNode(portNumber + ""));
    portNumberNode.setValue("type", "unprotected");
    newRoot.appendChild(portNumberNode);

    // Handle SAX Driver class
    Element saxDriverNode =
        doc.createElementNS(NAMESPACE_URI, "JavaXML:parserClass");
    saxDriverNode.appendChild(
        doc.createTextNode(driverClass));
    newRoot.appendChild(saxDriverNode); 
        
    Element serverNode =
        doc.createElementNS(NAMESPACE_URI, "JavaXML:xmlrpc-server");
    Element handlersNode =
        doc.createElementNS(NAMESPACE_URI, "JavaXML:handlers");

    // Handle handlers
    Enumeration handlerIDs = handlers.keys(  );
    while (handlerIDs.hasMoreElements(  )) {
      String handlerID = (String)handlerIDs.nextElement(  );
      String handlerClass = (String)handlers.get(handlerID);
          
      Element handlerIDNode =
          doc.createElementNS(NAMESPACE_URI, "JavaXML:identifier");
      handlerIDNode.appendChild(
          doc.createTextNode(handlerID));
              
      Element handlerClassNode =
          doc.createElementNS(NAMESPACE_URI, "JavaXML:class");
      handlerClassNode.appendChild(
          doc.createTextNode(handlerClass));
              
      Element handlerNode =
          doc.createElementNS(NAMESPACE_URI, "JavaXML:handler");
      handlerNode.appendChild(handlerIDNode);
      handlerNode.appendChild(handlerClassNode);
                
      handlersNode.appendChild(handlerNode);
    }
            
    serverNode.appendChild(handlersNode);               
    newRoot.appendChild(serverNode);
      
    doc.replaceChild(newRoot, oldRoot); 

    // Serialize the DOM tree       
}

We use the DOM Level 2 methods to create our XML document with namespace awareness. The createElementNS( ) method takes in the namespace URI and then the full name of the new element. The full name does include the namespace prefix; this allows the method to ensure that a namespace prefix and namespace URI are either both included, or both excluded, helping to ensure properly formed XML.

Serialization of a DOM tree is another task, like obtaining the DOM Document object, that is not outlined in the DOM specification. To achieve serialization, you need to see if your vendor provides a helper class that will handle that functionality for you. Once you have located your parser’s serializer, you typically need only to import that class and pass it an OutputStream or PrintWriter instance. Using Apache Xerces, the class needed is the org.apache.xml.serialize.XMLSerializer. We can use that class to write to the OutputStream that our saveConfiguration( ) method has supplied to it:

/**
 * <p>
 * This will save the current state out to the specified 
 *   <code>OutputStream</code>, using DOM
 * </p>
 *
 * @throws <code>IOException</code> - when errors occur in saving.
 */
public synchronized void saveConfiguration(OutputStream out)
    throws IOException {

    // Modify Document object
      
    doc.replaceChild(newRoot, oldRoot);        
        
    // Serialize the DOM tree
                  
    org.apache.xml.serialize.XMLSerializer out = 
                  
        new org.apache.xml.serialize.XMLSerializer(out, null);
                  
    out.serialize(doc);
}

The Apache XMLSerializer class has several different constructors; the one used here takes in an OutputStream and the format to use for output. We specify null to allow the default format to be used. The serialize( ) method takes as input the Document, Element, or DocumentFragment to serialize. We pass in the modified DOM tree Document, and the serialization occurs. At this point, we have emulated the functionality we created with JDOM to modify and output an XML document with user-supplied input.

Where Did That XmlRpcConfiguration Go?

When using a servlet or other web-based construct for providing a user interface, several issues must be handled that are not problems with thick or static clients. One of these is garbage collection and user lag. User lag refers to a user loading a servlet or piece of Java code on the Internet, and then taking a coffee break, attending three meetings, and eating a candy bar. What can happen is that object references in the servlet that was accessed may be garbage collected between the time of the original request and the user’s (much later) action. When one servlet submits to another, this is not a problem; however, in our example, the XmlRpcConfigurationServlet submitted data to itself. The possible bug is that the config member variable, an instance of com.oreilly.xml.XmlRpcConfiguration, is only created in the doGet( ) method, but is then reused in the doPost( ) method:

public void doGet(HttpServletRequest req, 
                  HttpServletResponse res) 
    throws ServletException, IOException {
      
    res.setContentType("text/html");
    PrintWriter out = res.getWriter(  );
        
    // Load the configuration information with our utility class
               
    config = new XmlRpcConfiguration(CONFIG_FILENAME);
    
    // Rest of method
}

public void doPost(HttpServletRequest req, 
                   HttpServletResponse res) 
    throws ServletException, IOException {
 
    // Save the hostname
    String hostname =
      req.getParameterValues("hostname")[0];
    if ((hostname != null) && (!hostname.equals(""))) {
      config.setHostname(hostname);
    }
    
    // Rest of method
 }

If sufficient time has passed between the initial GET request and the subsequent POST request, a NullPointerException can result when config is accessed in the doPost( ) method. This can also occur if the servlet code is changed and reloaded in the middle of a request, something the Jakarta Tomcat and other popular servlet engines support.

To avoid this problem, it makes sense to ensure that the config member variable is valid in both the doGet( ) and doPost( ) methods; however, there is a lot of overhead with instantiating the XmlRpcConfiguration class, as it must parse the supplied filename again. Instead of recreating the variable each time, we can take advantage of knowing the variables that are garbage-collected have their values set back to null.[17] Thus, we make a comparison to null, and only reinstantiate config when needed:

public void doPost(HttpServletRequest req, 
                  HttpServletResponse res) 
    throws ServletException, IOException {

    // Update the configuration information
               
    if (config == null) {
               
        config = new XmlRpcConfiguration(CONFIG_FILENAME);    
               
    }
 
    // Save the hostname
    String hostname =
      req.getParameterValues("hostname")[0];
    if ((hostname != null) && (!hostname.equals(""))) {
      config.setHostname(hostname);
    }
    
    // Rest of method
}

This will ensure that if user lag occurs, it does not affect your program’s operation.



[17] This is a bit of an overstatement. However, in the case of a non-local variable, it will hold true, particularly when dealing with servlets. For more information on garbage collection and values of non-initialized or garbage-collected variables, you should consult Java in a Nutshell, by David Flanagan (O’Reilly & Associates).

..................Content has been hidden....................

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