The Document Interface as a Node Type

Besides the factory methods and the methods common to all nodes, the Document interface has several unique methods that perform operations relevant only to document nodes. These include:

Getter methods

The Document interface has three methods that simply return particular parts of the document:

public Element getDocumentElement();
public DocumentType getDoctype();
public DOMImplementation getImplementation();

These are fairly self-explanatory. You’ve already seen the getDocumentElement() method used several times. It just returns the Element object representing the root element of the document. Similarly, the getDoctype() method returns the document’s DocumentType object or null if the document does not have a document type declaration. The getImplementation() method returns the DOMImplementation object that created this document.

Several pieces are missing. In particular, no part of the XML declaration is available: not version, not encoding, not standalone status. Other useful information that’s missing from DOM2 includes the actual encoding of the document (which is usually but not always the same as the encoding declared in the XML declaration) and the base URI of the document against which relative URIs in the document should be resolved. DOM Level 3 will add several more getter and setter methods to the Document interface to make these available.

public String getActualEncoding();
public void setActualEncoding(String actualEncoding);
public String getEncoding();
public void setEncoding(String encoding);
public boolean getStandalone();
public void setStandalone(boolean standalone);
public String getVersion();
public void setVersion(String version);
public void setBaseURI(String baseURI)
    throws DOMException;

The obvious getBaseURI() is not really missing. It’s just included in the Node super-interface rather than directly in the Document interface. Thus you can find out the base URI for any kind of node. This is important because XML documents can be built from multiple entities and different nodes may come from different files.

public String getBaseURI();

Finally DOM Level 3 adds one more setter/getter pair that, strictly speaking, doesn’t describe the document so much as the implementation. These two methods determine how draconian DOM is about checking for errors as the document is built in memory:

public boolean getStrictErrorChecking();
public void setStrictErrorChecking(boolean strictErrorChecking);

If the strict error checking property is false, then the implementation may not make every test it could possibly make. For instance, it might allow namespace prefixes that are not mapped to namespace URIs or make a text node a child of the document element. This can be faster, but it is also dangerous since other code may fail when presented with a Document object that does not satisfy all the usual constraints. Strict error checking is enabled by default. Even if strict error checking is false, however, some error checking may still be done. The purpose of these methods is to allow implementations to skip some of the tedious checking they normally do and thus improve performance. The purpose is not to allow malformed documents, though that may be the effect in some cases.

These properties are experimentally supported in Xerces 2.0.2. No other parsers support them at the time of this writing. The detailed signatures are still subject to change though, and you should not rely on them.

Example 10.10 is a simple program that parses a document from a URL passed through the command line and prints the values of these various properties. Since Xerces is currently the only parser to support the DOM3 properties, I used its implementation classes explicitly rather than the more generic JAXP.

Example 10.10. The properties of a Document object

import org.apache.xerces.parsers.DOMParser;
import org.apache.xerces.dom.DocumentImpl;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import java.io.IOException;


public class DocumentProperties {

  public static void main(String[] args) {
     
    if (args.length <= 0) {
      System.out.println("Usage: java DocumentProperties URL"); 
      return;
    }
    String url = args[0];
    
    // Since this only works in Xerces 2.0.2, we might as well use the 
    // Xerces-specific implementation classes instead of JAXP.
    DOMParser parser = new DOMParser();
    try {
      parser.parse(url); 
      DocumentImpl document = (DocumentImpl) parser.getDocument();
      
      // DOM2 properties
      System.out.println("Implementation: " + document.getImplementation());
      System.out.println("Root element: " + document.getDocumentElement());
      System.out.println("DOCTYPE: " + document.getDoctype());

      // DOM3 Properties
      System.out.println("Version: " + document.getVersion());
      System.out.println("Standalone: " + document.getStandalone());
      System.out.println("Declared encoding: " + document.getEncoding());
      System.out.println("Strict error checking: " + document.getStrictErrorChecking());
      System.out.println("Actual encoding: " + document.getActualEncoding());
      System.out.println("Base URI: " + document.getBaseURI());

    }
    catch (SAXException e) {
      System.out.println(url + " is not well-formed.");
    }
    catch (IOException e) { 
      System.out.println(
       "Due to an IOException, the parser could not read " + url
      ); 
    }
   
  }

}

Here’s the output from when I ran this program against the DocBook source code for this chapter:

$ java DocumentProperties ch10.xml
Implementation: org.apache.xerces.dom.DOMImplementationImpl@ef9f1d
Root element: [chapter: null]
DOCTYPE: [chapter: null]
Version: 1.0
Standalone: false
Declared encoding: UTF-8
Strict error checking: true
Actual encoding: UTF-8
Base URI: file:///home/elharo/books/xmljava/ch10.xml

In this case, the detailed output depends on what the toString() method for each of the implementation classes does. Even once other parsers support DOM3, this will be something different for different parsers. A more serious application would use the methods of each interface (Document, Doctype, and Element) to provide more complete output.

Tip

If a non-Xerces DOM implementation precedes Xerces in your class path, then this program won’t compile. You need to make sure Xerces is the first DOM the compiler and runtime find. This is especially problematic in Java 1.4, which includes a DOM implementation that does not support the DOM3 properties used here. However, in Java 1.4 you can use the java interpreter’s -Xbootclasspath/p: option to prepend JAR archives to the boot classpath so that they will be preferred to the ones bundled with Java.

Finding elements

Some of the most useful methods in the Document interface are those that retrieve all the elements with certain names or IDs in the document, irrespective of where in the document they may actually be. When you’re only really interested in certain elements, this can avoid a lot of tedious and complex tree-walking. These three methods are:

public NodeList getElementsByTagName(String tagName);
public NodeList getElementsByTagNameNS(String namespaceURI, String localName);
public Element getElementByID();

The first two methods return a NodeList of the elements with the specified name or local name/namespace URI pair. This list is in document order. You can use the asterisk (*) to match all names or all namespace URIs.

The third method returns the single element with the specified ID value, or null if no such element is present in the document. The ID is given by an ID-type attribute on that element.

Caution

It is possible though invalid for multiple elements in one document to share the same ID. In this case, this method’s behavior is undefined. For maximum safety, you may want to limit this method to provably valid documents.

As a demonstration, let’s develop a an XML-RPC servlet that generates Fibonacci numbers. (This is actually what was on the other side of the clients you saw in Chapter 5 and Chapter 3). Recall that the request document looks like Example 10.11. The server needs to find the integer value of the single param. Since we know there’s exactly one int element in the request, its easy to use getElementsByTagName() to find it.

Example 10.11. An XML-RPC request document

<?xml version="1.0"?>
<methodCall>
  <methodName>calculateFibonacci</methodName>
  <params>
    <param>
      <value><int>23</int></value>
    </param>
  </params>
</methodCall>

The server needs to calculate the result based on the input transmitted by the client, wrap that up in a response document like the one shown in Example 10.12, and transmit that document back to the client.

Example 10.12. An XML-RPC response document

<?xml version="1.0"?>
<methodResponse>
  <params>
    <param>
      <value><double>28657</double></value>
    </param>
  </params>
</methodResponse>

Example 10.13 shows the complete servlet. It extends HttpServlet and implements SingleThreadModel. This interface lets the servlet container know that this servlet is not thread safe, and it should use a different instance of this class for each concurrent thread. However, one instance may be used for successive threads. This was necessary here because the JAXP DocumentBuilder and Transformer classes and possibly the class that implements DOMImplementation are not thread-safe. You could make the servlet thread safe by loading new instances of these interfaces inside doPost() rather than sharing instances created in init(). However, in a potentially high-volume server environment the resource cost for that feels disturbingly large. A better alternative would be to manually synchronize access to these objects inside doPost(). However, since proper synchronization is notoriously difficult, I prefer to leave the work to the server.

Example 10.13. A DOM based XML-RPC servlet

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.math.BigInteger;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;


public class FibonacciXMLRPCDOMServlet extends HttpServlet 
 implements SingleThreadModel {

  // Fault codes   
  public final static int MALFORMED_REQUEST_DOCUMENT = 1;
  public final static int INVALID_REQUEST_DOCUMENT   = 2;
  public final static int INDEX_MISSING              = 3;
  public final static int NON_POSITIVE_INDEX         = 4;
  public final static int BAD_INTEGER_FORMAT         = 5;
  public final static int UNEXPECTED_PROBLEM         = 255;
   
  private DocumentBuilder   parser;
  private DOMImplementation impl;
  private Transformer       idTransform;
  
  // Load a parser, transformer, and implementation
  public void init() throws ServletException {  
  
    try {
      DocumentBuilderFactory factory 
       = DocumentBuilderFactory.newInstance();
      this.parser = factory.newDocumentBuilder();
      this.impl   = parser.getDOMImplementation();
    }
    catch (Throwable t) { 
      // It's unusual to catch a generic Throwable instead of an
      // exception. Here I'm specifically worried about 
      // FactoryConfigurationErrors and
      // ParserConfigurationExceptions, both of which are real
      // possibilities in a servlet environment because of the
      // weird ways servlet containers arrange classpaths.
      throw new ServletException(
       "Could not locate a JAXP parser", t); 
    }
    
    try {
      TransformerFactory xformFactory 
       = TransformerFactory.newInstance();  
      this.idTransform = xformFactory.newTransformer();
    }
    catch (Throwable t) { 
      throw new ServletException(
       "Could not locate a JAXP transformer", t); 
    }
    
  }   
  
  // Respond to an XML-RPC request
  public void doPost(HttpServletRequest servletRequest,
   HttpServletResponse servletResponse)
   throws ServletException, IOException {
    
    servletResponse.setContentType("text/xml; charset=UTF-8");               
    PrintWriter out = servletResponse.getWriter();
    InputStream in  = servletRequest.getInputStream();

    Document request;
    Document response;
    try {
      request = parser.parse(in);

      NodeList ints = request.getElementsByTagName("int");
      if (ints.getLength() == 0) {
        // XML-RPC allows i4 as an alias for int.
        ints = request.getElementsByTagName("i4"); 
      }
      Node input = ints.item(0); // throws NullPointerException
      String generations = getFullText(input);
      int numberOfGenerations = Integer.parseInt(generations);
      BigInteger result = calculateFibonacci(numberOfGenerations);
      response = makeResponseDocument(result);
    }
    catch (SAXException e) {  
      response = makeFaultDocument(MALFORMED_REQUEST_DOCUMENT, e.getMessage());
    }
    catch (NullPointerException e) {  
      response = makeFaultDocument(INDEX_MISSING, e.getMessage());
    }
    catch (NumberFormatException e) {  
      response = makeFaultDocument(BAD_INTEGER_FORMAT, e.getMessage());
    }
    catch (IndexOutOfBoundsException e) {  
      response = makeFaultDocument(NON_POSITIVE_INDEX, e.getMessage());
    }
    catch (Exception e) {  
      response = makeFaultDocument(UNEXPECTED_PROBLEM, e.getMessage());
    }
    
    // Transform onto the OutputStream
    try {
      Source input = new DOMSource(response);
      Result output = new StreamResult(out);
      idTransform.transform(input, output);
      servletResponse.flushBuffer();
      out.flush(); 
      out.println();
    }
    catch (TransformerException e) {
      // If we get an exception at this point, it's too late to
      // switch over to an XML-RPC fault.
      throw new ServletException(e); 
    }
    
  }

  
  // Given a node which does not contain any Element children,
  // accumulate all its text content from both text nodes and
  // CDATA sections (but not comments or processing instructions 
  // and return it as a single string.
  private static String getFullText(Node node) {
    
    StringBuffer result = new StringBuffer();
    
    NodeList children = node.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      Node child = children.item(i);
      int type = child.getNodeType();
      if (type == Node.TEXT_NODE 
       || type == Node.CDATA_SECTION_NODE) {
        result.append(child.getNodeValue()); 
      }
      else if (type == Node.ENTITY_REFERENCE_NODE) {
        // The JAXP spec is unclear about whether or not it's
        // possible for entity reference nodes to appear in the
        // tree. Just in case, let's expand them recursively:
        result.append(getFullText(child));
        // Validity does require that if they do appear their 
        // replacement text is pure text, no elements.
      }
    }
    
    return result.toString();
    
  }
  
  // If performance is an issue, this could be pre-built in the
  // init() method and then cached. You'd just change one text 
  // node each time.  This would only work in a SingleThreadModel 
  // servlet.
  public Document makeResponseDocument(BigInteger result) {
    
    Document response 
     = impl.createDocument(null, "methodResponse", null);
     
    Element methodResponse = response.getDocumentElement();
    Element params         = response.createElement("params");
    Element param          = response.createElement("param");
    Element value          = response.createElement("value");
    Element doubleElement  = response.createElement("double");
    Text    text = response.createTextNode(result.toString()); 
 
    methodResponse.appendChild(params);
    params.appendChild(param);
    param.appendChild(value);
    value.appendChild(doubleElement);
    doubleElement.appendChild(text);

    return response;
   
  }
  
  public Document makeFaultDocument(int faultCode, String faultString) {
        
    Document faultDoc
     = impl.createDocument(null, "methodResponse", null);
     
    Element methodResponse = faultDoc.getDocumentElement();
    
    Element fault         = faultDoc.createElement("fault");
    Element value         = faultDoc.createElement("value");
    Element struct        = faultDoc.createElement("struct");
    Element memberCode    = faultDoc.createElement("member");
    Element nameCode      = faultDoc.createElement("name");
    Text    nameCodeText  = faultDoc.createTextNode("faultCode");
    Element valueCode     = faultDoc.createElement("value");
    Element intCode       = faultDoc.createElement("int");
    String  codeString    = String.valueOf(faultCode);
    Text    textCode      = faultDoc.createTextNode(codeString);
    Element doubleElement = faultDoc.createElement("double");
    Element memberString  = faultDoc.createElement("member");
    Element nameString    = faultDoc.createElement("name");
    Text    nameText    = faultDoc.createTextNode("faultString");
    Element valueString   = faultDoc.createElement("value");
    Element stringString  = faultDoc.createElement("string");
    Text    textString    = faultDoc.createTextNode(faultString);

    methodResponse.appendChild(fault);
    fault.appendChild(value);
    value.appendChild(struct);
    struct.appendChild(memberCode);
    struct.appendChild(memberString);
    memberCode.appendChild(nameCode);
    memberCode.appendChild(valueCode);
    memberString.appendChild(nameString);
    memberString.appendChild(valueString);
    nameCode.appendChild(nameCodeText);
    nameString.appendChild(nameText);
    valueCode.appendChild(intCode);
    valueString.appendChild(stringString);
    intCode.appendChild(textCode);
    stringString.appendChild(textString);
    
    return faultDoc;
       
  } 
  
  public static BigInteger calculateFibonacci(int generations) 
   throws IndexOutOfBoundsException {
    
    if (generations < 1) {
      throw new IndexOutOfBoundsException(
       "Fibonacci numbers are not defined for " + generations 
       + "or any other number less than one.");
    }
    BigInteger low  = BigInteger.ONE;
    BigInteger high = BigInteger.ONE;      
    for (int i = 2; i <= generations; i++) {
      BigInteger temp = high;
      high = high.add(low);
      low = temp;
    }
    return low;   
        
  }

}

To compile FibonacciXMLRPCDOMServlet, you'll need to install the Java Servlet API somewhere in your class path. This is not included in the default distribution of the JDK. To get this to run in most servlet containers, you’ll need to add the JAR files for the DOM and JAXP implementations to the servlet’s library. In Tomcat, that directory is $TOMCAT_HOME/lib. It is not enough to have them in the virtual machine’s default ext directory because most servlet engines do not load classes from there. Details are in Chapter 3.

The servlet is divided into six methods. The init() method, the servlet substitute for a constructor, is responsible for finding a DOMImplementation class and loading a parser and a transformer engine. There’s no reason to waste time creating new ones for each request.

doPost() is the standard servlet method for responding to HTTP POST. Each POST to this servlet represents a separate XML-RPC request. This method first uses getElementsByTagName() to find the single int element in this request. Then it extracts the text content of this element and converts it to a Java int. This is more involved than you might expect because it’s necessary to consider the possibility that this text might not be part of a single node. For example, any of these three legal elements would give the int element multiple children:

<int>12<!-- Why is this comment here? -->34</int>
<int><?target data?>1234</int
<int>12<![CDATA[34]]></int>

Admittedly these are edge cases, but they have to be handled because they are legal. Comments and CDATA sections could be eliminated at parse time with the DocumentBuilderFactory’s setCoalescing(true) and setIgnoringComments(true) methods. However, that still leaves the possibility of a processing instruction.

There are also a lot of illegal things we aren’t handling. For instance, nothing notices if the methodName element is missing or if the root element isn’t methodCall. The proper way to handle this is to write a schema and validate the document against it before processing. An XML-RPC schema was demonstrated in Example 2.14. However, actually validating against this requires resorting to parser-specific classes. If you’re using Xerces, the following code would do the trick:

DOMParser parser = new DOMParser();
parser.setErrorHandler(YourErrorHandler);
parser.setFeature(
 "http://apache.org/xml/features/validation/schema", true);
parser.setProperty(
 "http://apache.org/xml/properties/schema/"
 + "external-noNamespaceSchemaLocation",
 "http://example.com/schemas/xmlrpc.xsd");
parser.parse(in);
Document doc = parser.getDocument();
// Work with the parser as before…

YourErrorHandler is an instance of some org.xml.sax.ErrorHandler implementation that throws a SAXException on detecting a validity error. Other schema-validating parsers like Oracle have slightly different APIs for checking a document against a known schema. Neither JAXP nor DOM2 provides a standard way to do this. When finished and implemented, DOM3 should allow you to perform schema-validation in a parser independent fashion.

Assuming the request document is valid, the next step is to calculate the requested Fibonacci number. The calculateFibonacci() method does this. There’s nothing new here, just math. This method doesn’t have to know anything about XML, DOM, or XML-RPC. If the original XML-RPC request contained a non-positive integer, then this method detects it and throws an IndexOutOfBoundsException. This will be caught in the doPost() method and converted into an XML-RPC fault response.

Once the result has been calculated, the makeResponseDocument() method wraps it up in properly formatted XML-RPC. If at any point something goes wrong, e.g. the request document is missing the required int element, an exception is thrown. This is caught and instead of the normal response, makeFaultDocument() is called to produce a proper XML-RPC fault response document.

Finally, a JAXP identity transform copies the finished result document onto the servlet’s output Writer. There’s not a lot we can do about an exception at this point, so any TransformerExceptions caught during the transform are converted into a ServletException with the original exception being available as the root cause of the ServletException. We can’t just let the TransformerException exception bubble up because doPost() in the superclass is not declared to throw it.

The SOAP servlet is similar in structure. However, namespaces are significant in SOAP so getElementsByTagNameNS() and other namespace-aware methods should be used. Example 10.14 demonstrates.

Example 10.14. A DOM based SOAP servlet

import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;
import java.math.BigInteger;
import org.w3c.dom.*;
import org.xml.sax.SAXException;
import javax.xml.parsers.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;


public class FibonacciSOAPDOMServlet extends HttpServlet 
  implements SingleThreadModel {

  // Fault codes   
  public final static String MALFORMED_REQUEST_DOCUMENT 
   = "MalformedRequest";
  public final static String INVALID_REQUEST_DOCUMENT 
   = "InvalidRequest";
  public final static String INDEX_MISSING 
   = "IndexMissing";
  public final static String NON_POSITIVE_INDEX 
   = "NonPositiveIndex";
  public final static String BAD_INTEGER_FORMAT
  = "BadIntegerFormat";
  public final static String UNEXPECTED_PROBLEM
  = "UnexpectedProblem";    
    
  private DocumentBuilder   parser;
  private DOMImplementation impl;
  private Transformer       idTransform;
  
  // Load a parser, transformer, and implementation
  public void init() throws ServletException {  
  
    try {
      DocumentBuilderFactory factory 
       = DocumentBuilderFactory.newInstance();
      // Always turn on namespace awareness
      factory.setNamespaceAware(true);

      this.parser = factory.newDocumentBuilder();
      this.impl   = parser.getDOMImplementation();
      
    }
    catch (Throwable t) { 
      throw new ServletException(
       "Could not locate a JAXP parser", t); 
    }
    
    try {
      TransformerFactory xformFactory = TransformerFactory.newInstance();  
      this.idTransform = xformFactory.newTransformer();
    }
    catch (Throwable t) { 
      throw new ServletException(
       "Could not locate a JAXP transformer", t); 
    }
    
  } 
  
  public void doPost(HttpServletRequest servletRequest,
   HttpServletResponse servletResponse)
   throws ServletException, IOException {
    
    servletResponse.setContentType("text/xml; charset=UTF-8");               
    PrintWriter out = servletResponse.getWriter();
    InputStream in  = servletRequest.getInputStream();

    Document request;
    Document response;
    String generations ="here";
    try {
      request = parser.parse(in);
       
      NodeList ints = request.getElementsByTagNameNS(
       "http://namespaces.cafeconleche.org/xmljava/ch3/", 
       "calculateFibonacci");
      Node input = ints.item(0);
      generations = getFullText(input);
      int numberOfGenerations = Integer.parseInt(generations);
      BigInteger result = calculateFibonacci(numberOfGenerations);
      response = makeResponseDocument(result);
    }
    catch (SAXException e) {  
      response = makeFaultDocument(MALFORMED_REQUEST_DOCUMENT, 
       e.getMessage());
    }
    catch (NullPointerException e) {  
      response = makeFaultDocument(INDEX_MISSING, 
       e.getMessage());
    }
    catch (NumberFormatException e) {  
      response = makeFaultDocument(BAD_INTEGER_FORMAT, 
       generations + e.getMessage());
    }
    catch (IndexOutOfBoundsException e) {  
      response = makeFaultDocument(NON_POSITIVE_INDEX, 
       e.getMessage());
    }
    catch (Exception e) {  
      response = makeFaultDocument(UNEXPECTED_PROBLEM, 
       e.getMessage());
    }
    
    // Transform onto the OutputStream
    try {
      Source input = new DOMSource(response);
      Result output = new StreamResult(out);
      idTransform.transform(input, output);
      servletResponse.flushBuffer();
      out.flush(); 

      out.println();
    }
    catch (TransformerException e) {
      // If we get an exception at this point, it's too late to
      // switch over to a SOAP fault
      throw new ServletException(e); 
    }
    
  }

  private static String getFullText(Node node) {
    
    StringBuffer result = new StringBuffer();

    NodeList children = node.getChildNodes();
    for (int i = 0; i < children.getLength(); i++) {
      Node child = children.item(i);
      int type = child.getNodeType();
      if (type == Node.TEXT_NODE 
       || type == Node.CDATA_SECTION_NODE) {
        result.append(child.getNodeValue()); 
      }
    }

    return result.toString();
    
  }  

  // The details of the formats and namespace URIs are likely to
  // change when SOAP 1.2 is released.
  public Document makeResponseDocument(BigInteger result) {
    
    Document response 
     = impl.createDocument(
      "http://schemas.xmlsoap.org/soap/envelope/", 
      "SOAP-ENV:Envelope", null);
     
    Element envelope = response.getDocumentElement();
    Element body = response.createElementNS(
     "http://schemas.xmlsoap.org/soap/envelope/", 
     "SOAP-ENV:Body");
    envelope.appendChild(body);
 
    Element Fibonacci_Numbers = response.createElementNS( 
     "http://namespaces.cafeconleche.org/xmljava/ch3/",
     "Fibonacci_Numbers");
    body.appendChild(Fibonacci_Numbers);

    Element fibonacci = response.createElementNS(
     "http://namespaces.cafeconleche.org/xmljava/ch3/",
     "fibonacci");
    Fibonacci_Numbers.appendChild(fibonacci);

    Text text = response.createTextNode(result.toString()); 
    fibonacci.appendChild(text);

    return response;
   
  }
  
  public Document makeFaultDocument(String code, String message){
    
    Document faultDoc = impl.createDocument(
      "http://schemas.xmlsoap.org/soap/envelope/", 
      "SOAP-ENV:Envelope", null);

    Element envelope = faultDoc.getDocumentElement();

    Element body = faultDoc.createElementNS(
     "http://schemas.xmlsoap.org/soap/envelope/", 
     "SOAP-ENV:Body");
    envelope.appendChild(body);
 
    Element fault = faultDoc.createElementNS(
     "http://schemas.xmlsoap.org/soap/envelope/", "Fault");
    body.appendChild(fault);

    Element faultCode = faultDoc.createElement("faultcode");
    fault.appendChild(faultCode);

    Element faultString = faultDoc.createElement("faultstring");
    fault.appendChild(faultString);

    Text textCode = faultDoc.createTextNode(code);
    faultCode.appendChild(textCode);

    Text textString = faultDoc.createTextNode(message);
    faultString.appendChild(textString);
    
    return faultDoc;
       
  } 
  
  public static BigInteger calculateFibonacci(int generations) 
   throws IndexOutOfBoundsException {
    
    if (generations < 1) {
      throw new IndexOutOfBoundsException(
       "Fibonacci numbers are not defined for " + generations 
       + "or any other number less than one.");
    }
    BigInteger low  = BigInteger.ONE;
    BigInteger high = BigInteger.ONE;      
    for (int i = 2; i <= generations; i++) {
      BigInteger temp = high;
      high = high.add(low);
      low = temp;
    }
    return low;   
        
  }

}

This example has the same basic structure as the XML-RPC version. That is, the init() method loads the parser, DOM implementation, and identity transform. The doPost() method reads the request data and delegates building the request to the makeResponseDocument() method. If anything goes wrong, the makeFaultDocument() is called to produce a properly formatted SOAP fault response. Finally, a JAXP ID transform serializes the response onto the network stream. The formats of the request and response are a little different, but the program flow is the same.

I did structure the building of the two response documents (success and fault) a little differently. FibonacciXMLRPCServlet built all the nodes first and then connected them to each other. Here I add them to the tree as soon as they’re created. There’s not a lot of reason to do it one way or the other. Just use whichever seems more natural to you.

The details can be a little opaque. In a real-world program I’d definitely add some comments showing an example of the documents each method builds. If you’re building a lot of XML-RPC or SOAP documents with varying parameters, then it wouldn’t hurt to either develop a more generic library or buy or borrow a third-part library such as Apache SOAP. Behind the scenes, they’re doing something very much like what I’ve done in these two examples.

Transferring nodes between documents

In DOM, every node belongs to exactly one document at all times. It cannot exist independently of a Document, and it cannot be part of more than one Document at the same time. This is why the Document interface serves as the factory for creating node objects of various kinds. Furthermore, in DOM2, a node cannot be detached from its original document and placed in a new document. However, this restriction is loosened somewhat in DOM3.

Copying Nodes

The importNode() method makes a copy of a node found in another document. The copy can then be inserted in the importing document’s tree using the usual methods like insertBefore() and appendChild(). The document from which the node is imported is not changed in any way.

public Node importNode(Node toBeImported, boolean deep)
    throws DOMException;

The deep argument determines whether or not all the node’s descendants are copied with it. If true, they are. If false, they aren’t.

Document and document type nodes cannot be imported. Trying to import one will throw a DOMException. Entity and notation nodes can be imported but cannot be added as children of the importing document’s document type node. Thus there’s really not a lot of reason to import them.

Most of the other kinds of nodes can be imported with pretty much the results you’d expect. (Just remember to pass true for the second argument. This is almost always what you want.) The only really tricky ones are entity reference nodes. Even if deep is true, the children of the entity reference node are not copied. Instead the replacement text (node list) of the imported entity reference depends on what the importing document’s DTD says it is, not the original replacement text.

Moving Nodes

DOM Level 3 adds an adoptNode() method that moves a node from one document to another. That is, the node is deleted from the original document and inserted into the new document:

public Node adoptNode(Node adoptee)
    throws DOMException;

Adopted nodes are always moved with all their descendants intact. Otherwise, this behaves pretty much like importNode. That is, document nodes, document type nodes, notation nodes, and entity nodes cannot be adopted. All other kinds can be. Descendants of entity reference nodes are deleted from the original document but not copied into the new document.

As usual with DOM3 methods, this is past the bleeding edge. The latest versions of Xerces support it, but no other parsers do. There are also still a lot of unresolved issues concerning the behavior of this method, so I wouldn’t rely on it. For the time being, its better to use a two-step procedure in which a node is first copied into the new document with importNode() and then deleted from the original document with removeChild().


Copyright 2001, 2002 Elliotte Rusty Haroldelharo@metalab.unc.eduLast Modified July 27, 2002
Up To Cafe con Leche