Chapter 12. Creating XML with Java

We now take a look at the one portion of manipulating XML that we have yet to address: creating and modifying XML. So far, all of our applications have used existing XML documents as constant data, never making changes to the original document. This is often the case in programming XML-based applications today; however, more and more leading edge technologies create XML documents in memory (such as XSP, which we looked at in Chapter 9). Other common applications that might need to modify XML data include XML editors and development environments as well as configuration managers, which we explore in this chapter.

In the last chapter, we created an XML configuration file for storing information related to configuring our XML-RPC classes. The assumption we made at that point was that changes to these configuration parameters would require a user (most likely a systems administrator) to edit the configuration file by hand and make modifications. Then (hopefully) the user would validate the modified XML document and restart the XML-RPC server and clients. However, this can be a very error-prone approach. First, it assumes that no mistakes are made when entering the new information. Second, it assumes that the user making the changes has the self-discipline to validate the modified XML document, ensuring correct and valid data has been entered. Even if both of these events occur every time modifications are made, which is unlikely in a real-world scenario, the configuration becomes more complicated if the client and server are on different machines. If the XML-RPC server is distributed as well, the configuration file then exists in another location; it is possible and even probable that four, five, or even more separate copies of the file exist, all on different servers. Any change to one of these files must result in the change being duplicated to all the other files. A Java application or servlet to modify the configuration file and then automatically update all the various locations is a good solution for this problem, and in this situation, the Java code must be able to modify XML.

We will explore modifying an XML document, and then saving the changed document, in this chapter. This final format could be another XML text file on a hard drive, a stream that is passed to another application component, or XML that is transformed and output as HTML. The Java APIs for programmatically working with XML make all of this possible.

Loading the Data

As in the previous chapters, the best way to learn to use these technologies and APIs is to actually code something useful with them. To demonstrate how to go about doing this, we take a look at further enhancing the functionality of our suite of XML-RPC classes and tools. In the last chapter, we migrated all of our configuration information to an XML configuration file. We have already created a class to read in this information and to use the loaded information for starting up our XML-RPC clients and server using the JDOM interfaces (as well as having looked at SAX and DOM alternatives). Now we will write a simple tool to update and modify this configuration information and then save the changes to the original configuration file.

In this section, we look at two components of this process: our utility class, com.oreilly.xml.XmlRpcConfiguration, that currently loads the configuration data, and a Java servlet to provide a user interface for editing the data. First we add mutator methods to our utility class that complement our accessor methods from Chapter 11. These will allow the servlet we build, as well as other applications, to modify the data within the utility class. Once we have created this entry point for applications to modify the configuration data, we will create a servlet to display the information, as well as to let users modify the data through an HTML form.

An Entry Point for Modification

Because our utility class, XmlRpcConfiguration, encapsulates the process of reading and writing to the underlying XML document on the filesystem, we can add mutator methods to the class now, and then later add behavior to write changes out to the actual file. This provides an abstraction layer that allows us to build applications in parallel: once the mutator methods are in place, one developer or group can work on the servlet interface, using the supplied accessor and mutator methods, while another developer or group can work on the method within the utility class that saves the updated configuration. Example 12.1 shows the XmlRpcConfiguration class with these mutator methods implemented, as well as a saveConfiguration method skeleton that will eventually use Java APIs to update the underlying configuration data, given either a filename or an OutputStream to write the updated configuration to.

Example 12-1. Utility Class with Mutator Methods

package com.oreilly.xml;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.IOException;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.List;

import org.jdom.Document;
import org.jdom.Element;
import org.jdom.JDOMException;
import org.jdom.Namespace;
import org.jdom.input.Builder;
import org.jdom.input.DOMBuilder;

/**
 * <b><code>XmlRpcConfiguration</code></b> is a utility class
 *   that will load configuration information for XML-RPC servers
 *   and clients to use.
 * 
 * @author 
 *   <a href="mailto:[email protected]">Brett McLaughlin</a>
 * @version 1.0
 */
public class XmlRpcConfiguration {
  
    /** The stream to read the XML configuration from */
    private InputStream in;
  
    /** Port number server runs on */
    private int portNumber;
    
    /** Hostname server runs on */
    private String hostname;
    
    /** SAX Driver Class to load */
    private String driverClass;    
    
    /** Handlers to register in XML-RPC server */
    private Hashtable handlers;

    /** JDOM Document tied to underlying XML */
    private Document doc;
  
    /**
     * <p>
     * This will set a filename to read configuration
     *   information from.
     * </p>
     *
     * @param filename <code>String</code> name of
     *                 XML configuration file.
     */
    public XmlRpcConfiguration(String filename) 
        throws IOException {
        
        this(new FileInputStream(filename));
    }
    
    /**
     * <p>
     * This will set a filename to read configuration
     *   information from.
     * </p>
     *
     * @param in <code>InputStream</code> to read
     *           configuration information from.
     */
    public XmlRpcConfiguration(InputStream in) 
        throws IOException {    
            
        this.in = in;
        portNumber = 0;
        hostname = "";
        handlers = new Hashtable(  );
    
        // Parse the XML configuration information
        parseConfiguration(  );
    }
  
    /**
     * <p>
     * This returns the port number the server listens on.
     * </p>
     *
     * @return <code>int</code> - number of server port.
     */
    public int getPortNumber(  ) {
        return portNumber;
    }
    
    /**
     * <p>
     * This will set the port number to listen to.
     * </p>
     *
     * @param portNumber <code>int</code> port to listen to.
     */
    public void setPortNumber(int portNumber) {
        this.portNumber = portNumber;
    }

    /**
     * <p>
     * This returns the hostname the server listens on.
     * </p>
     *
     * @return <code>String</code> - hostname of server.
     */    
    public String getHostname(  ) {
        return hostname;
    }
    
    /**
     * <p>
     * This will set the hostname for the server to listen to.
     * </p>
     *
     * @param hostname <code>String</code> name of server's host.
     */
    public void setHostname(String hostname) {
        this.hostname = hostname;
    }
    
    /**
     * <p>
     * This returns the SAX driver class to load.
     * </p>
     *
     * @return <code>String</code> - name of SAX driver class.
     */
    public String getDriverClass(  ) {
        return driverClass;
    }
    
    /**
     * <p>
     * This will set the driver class for parsing.
     * </p>
     *
     * @param driverClass <code>String</code> name of parser class.
     */
    public void setDriverClass(String driverClass) {
        this.driverClass = driverClass;
    }    

    /**
     * <p>
     * This returns the handlers the server should register.
     * </p>
     *
     * @return <code>Hashtable</code> of handlers.
     */     
    public Hashtable getHandlers(  ) {
        return handlers;
    }    
    
    /**
     * <p>
     * This will set the handlers to register.
     * </p>
     *
     * @param handlers <code>Hashtable</code> of handler to register.
     */
    public void setHandlers(Hashtable handlers) {
        this.handlers = handlers;
    }
    
    /**
     * <p>
     * Parse the XML configuration information and
     *   make it available to clients.
     * </p>
     *
     * @throws <code>IOException</code> when errors occur.
     */
    private void parseConfiguration(  ) throws IOException {
        try {
            // Request DOM Implementation and Xerces Parser
            Builder builder = 
                new DOMBuilder("org.jdom.adapters.XercesDOMAdapter");
                
            // Get the Configuration Document, with validation
            doc = builder.build(in);
        
            // Get the root element
            Element root = doc.getRootElement(  );             
            
            // Get the JavaXML namespace
            Namespace ns = Namespace.getNamespace("JavaXML",
                           "http://www.oreilly.com/catalog/javaxml/");

            // Load the hostname, port, and handler class
            hostname = 
                root.getChild("hostname", ns).getContent(  );
            driverClass = 
                root.getChild("parserClass", ns).getContent(  );
            portNumber = 
                    root.getChild("port", ns).getIntContent(0  );            
            
            // Get the handlers
            List handlerElements = 
                root.getChild("xmlrpc-server", ns)
                    .getChild("handlers", ns)
                    .getChildren("handler", ns);   

            Iterator i = handlerElements.iterator(  );
            while (i.hasNext(  )) {
                Element current = (Element)i.next(  );
                handlers.put(current.getChild("identifier", ns)
                                    .getContent(  ),
                             current.getChild("class", ns)
                                    .getContent(  ));                  
            }
        } catch (JDOMException e) {
            // Log an error
            throw new IOException(e.getMessage(  ));
        }
    }

    
    /**
     * <p>
     * This will save the current state out to the XML-RPC configuration 
     *   file.
     * </p>
     *
     * @throws <code>IOException</code> - when errors occur in saving.     
     */
    public synchronized void saveConfiguration(String filename)
        throws IOException {    
            
        saveConfiguration(new FileOutputStream(filename));
    }
    
    /**
     * <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 {
            
        // To be implemented
    }
  
}

In addition to the new methods, you should notice that we create a Document member variable, doc, and use it in both the reading and the writing of the configuration document. It makes sense to store the reference to the JDOM Document object, rather than reloading it in the saveConfiguration( ) method. Our parseConfiguration( ) now loads the XML data into the doc member variable, which can then be reused in the saveConfiguration( ) method.

Displaying the Configuration

With the XmlRpcConfiguration class definition complete, we now need an interface for the user to view this configuration data and make changes. Using a Java servlet for this interface is a good idea, as it provides a simple request and response model without extensive network programming. This also makes remote administration possible through any Internet browser. We first need to code in the portion of the servlet that will respond to a simple browser request (which comes through the GET method) and display the current configuration information. All this requires is instantiating the XmlRpcConfiguration class and then outputting an HTML form with the information from the utility class filling the values within that form. Because this is basic Java and Java servlet code, Example 12.2 is provided without detailed explanation. If you are unfamiliar with the Java Servlet API, you should check out Java Servlet Programming, by Jason Hunter (O’Reilly & Associates).

Example 12-2. A Java Servlet to Display XML-RPC Configuration Information

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.Hashtable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.oreilly.xml.XmlRpcConfiguration;

/**
 * <b><code>XmlRpcConfigurationServlet</code></b> is an
 *   administration tool that allows configuration changes
 *   to be saved to the XML configuration file.
 * 
 * @version 1.0
 */
public class XmlRpcConfigurationServlet extends HttpServlet {
  
    /** Store the XML-RPC configuration file as a constant */
    private static final String CONFIG_FILENAME =
        "d:\prod\Java and XML\WEB-INF\conf\xmlrpc.xml";

    /**
     * Point action back at this servlet (and the 
     *   <code>{@link #doPost(  )}</code> method).
     *   In Servlet API 2.1 or 2.2, this can be done programmatically,
     *   but this example allows this to work in Servlet 2.0 as well
     */
    private static final String FORM_ACTION =
        "/javaxml/servlet/XmlRpcConfigurationServlet";
        
    /** Configuration object to work with */
    XmlRpcConfiguration config;
  
    /**
     * <p>
     * GET requests are received when the client wants to see the current
     *   configuration information. This provides a view-only look at 
     *   the data. The generated HTML form then submits back to this 
     *   servlet through POST, which causes the 
     *   <code>{@link #doPost}</code> method to be invoked.
     * </p>
     */
    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);
        
        // Output HTML user interface
        out.println("<html><head>");
        out.println("<title>XML-RPC Configurations</title>");
        out.println("</head><body>");
        out.println("<h2 align="center">XML-RPC Configuration</h2>");
        out.println("<form action="" + FORM_ACTION + "" " +
                    "method="POST">");
        out.println("<b>Hostname:</b> ");
        out.println("<input type="text" " +
                    "name="hostname" " +
                    "value="" + config.getHostname(  ) + 
                    "" />");
        //out.println("<br />");
        out.println("&nbsp;&nbsp;&nbsp;&nbsp;");
        out.println("<b>Port Number:</b> ");
        out.println("<input type="text" " +
                    "name="port" " +
                    "value="" + config.getPortNumber(  ) + 
                    "" />");
        out.println("<br />");        
        out.println("<b>SAX Driver Class:</b> ");
        out.println("<input type="text" " +
                    "name="driverClass" size="50"" +
                    "value="" + config.getDriverClass(  ) + 
                    "" />");
        out.println("<br />");        
        out.println("<br />"); 
        out.println("<h3 align="center">XML-RPC handlers</h3>");
        
        // Display current handlers
        Hashtable handlers = config.getHandlers(  );
        Enumeration keys = handlers.keys(  );
        int index = 0;
        while (keys.hasMoreElements(  )) {
            String handlerID = 
                (String)keys.nextElement(  );
            String handlerClass = 
                (String)handlers.get(handlerID);
            out.println("<b>Identifier:</b> ");
            out.println("<input type="text" " +
                        "value="" + handlerID + "" " +
                        "name="handlerID" />  ");
            out.println("<b>Class:</b> ");
            out.println("<input type="text" " +
                        "value="" + handlerClass + "" " + 
                        "size="30" " +
                        "name="handlerClass" />  ");   
            out.println("<br />");
            index++;                
        }
        
        // Display empty boxes for additional handlers
        for (int i=0; i<3; i++) {
            out.println("<b>Identifier:</b> ");
            out.println("<input type="text" " +
                        "name="handlerID" />  ");
            out.println("<b>Class:</b> ");
            out.println("<input type="text" " +
                        "size="30" " +            
                        "name="handlerClass" />  ");   
            out.println("<br />");
            index++;
        }
        
        out.println("<br /><center>");
        out.println("<input type="submit" value="Save Changes" />");
        out.println("</center>");
        out.println("</form></body></html>");
        
        out.close(  );          
    }
    
    /**
     * <p>
     * This method receives requests for modification of the
     *   XML-RPC configuration information, all from the
     *   <code>{@link #doGet}</code> method.  This will again
     *   use the utility class to update the configuration
     *   file, letting the <code>{@link XmlRpcConfiguration}</code>
     *   object handle the actual writing to a file.
     * </p>
     */
    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);
        }
        
        // Save the port number
        int portNumber;
        try {
          portNumber =
            Integer.parseInt(
              req.getParameterValues("port")[0]);
        } catch (Exception e) {
          portNumber = 0;
        }        
        if (portNumber > 0) {
          config.setPortNumber(portNumber);
        }
        
        // Save the SAX driver class
        String driverClass =
          req.getParameterValues("driverClass")[0];
        if ((driverClass != null) && (!driverClass.equals(""))) {
          config.setDriverClass(driverClass);
        }               
        
        // Save the handlers
        String[] handlerIDs = 
          req.getParameterValues("handlerID");
        String[] handlerClasses =
          req.getParameterValues("handlerClass");
        Hashtable handlers = new Hashtable(  );
        for (int i=0; i<handlerIDs.length; i++) {
          handlers.put(handlerIDs[i], handlerClasses[i]);
        }
        config.setHandlers(handlers);
        
        // Request the changes be written to the configuration store
        config.saveConfiguration(CONFIG_FILENAME);
        
        // Output a confirmation message
        res.setContentType("text/html");
        PrintWriter out = res.getWriter(  );
        
        out.println("Changes saved <br />");
        out.println("<a href="" + FORM_ACTION + 
                    "">Return to Configuration Administration" +
                    "</a>");
        out.close(  );
          
    }
  
}

We take advantage of knowing that initial requests come to the servlet through the GET method, while submitting our form can be done with the POST method. This allows us to display configuration information on the GET requests (with the doGet( ) method) and to update changes when POST requests are received (with the doPost( ) method). When requests come through GET requests, an HTML screen is rendered showing the current configuration information, as in Figure 12.1.

HTML user interface for viewing and modifying configuration information

Figure 12-1. HTML user interface for viewing and modifying configuration information

When the button is clicked, the HTML form is submitted, and the same servlet receives the request, this time as a POST request. The doPost( ) method then reads each of the parameters from the submitted form and uses the mutator methods of the XmlRpcConfiguration class to update the configuration data. Finally, the saveConfiguration( ) method is called with the same filename as originally used. In the next section, we implement saving the updated data to the JDOM Document object and then the XML configuration file. Finally, our Java servlet displays an HTML hyperlink that (through another GET request) takes the user back to the configuration form. The updated data will then be displayed, and the process can be repeated.

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

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