[ Team LiB ] Previous Section Next Section

Why Use XML with JSP and Servlets?

If you are new to the XML world, you are probably wondering why you would care about using XML with servlets and JSPs. After all, you are sending output to a browser and the browser understands HTML, not XML. One thing you often find when you create applications that need to send data to other applications is that there are often firewalls sitting between the applications. This is especially true when one business sends data to another business. You would think that RMI or CORBA would be the technology of choice for exchanging data between businesses, but when it comes to firewalls, many developers take the path of least resistance: HTTP. There are products that allow you to send CORBA over a firewall, and RMI even supports HTTP tunneling, but because most firewalls already support HTTP and HTTPS (HTTP over SSL), you can use the URL and URLConnection classes to communicate with servlets and JSPs with little or no extra work.

When you pass XML data via HTTP, it makes sense to use servlets to handle incoming XML and JSPs to generate outgoing XML. Java has several good XML parsers for handling incoming XML. You might need to perform additional work to copy the XML values into Java classes, however.

Automatically Generated XML

The simple, uniform structure of XML makes it very computer friendly. The nested structure of XML makes it an ideal format for storing complex data structures. In fact, XML makes a nice format for serializing Java objects. There are several different ways to serialize Java objects into XML. Some approaches define an XML DTD that describes a Java object. For example, you might have XML that looks like this:


<class>
    <class-name>tyjsp.TestClass</class-name>
    <attribute>
        <attribute-name>myAttribute</attribute-name>
        <attribute-value>Foo</attribute-value>
    </attribute
</class>

Although this method is good, it is very Java-centric. XML allows you to describe a huge variety of data structures, so wouldn't it be nice if you could easily map those data structures into Java as well? Conversely, it would be nice to generate XML from these Java classes.

You can use a package called JOX (Java Objects in XML), available at http://www.wutka.com/jox, to serialize Java objects into XML. To use JOX, you also need the DTD Parser available from http://www.wutka.com/dtdparser.html. Listing 19.7 shows an example JavaBean suitable for serialization.

Listing 19.7 Source Code for TestBean.java
package com.wutka.jox.test;

import com.wutka.jox.*;
import java.util.*;

public class TestBean implements java.io.Serializable
{
    protected int foo;
    protected String bar;
    protected java.util.Date baz;
    protected Vector thingies;
    protected TestSubbean subbean;

    public TestBean()
    {
        bar = "";
        baz = new Date();
        thingies = new Vector();
    }

    public int getFoo() { return foo; }
    public void setFoo(int aFoo) { foo = aFoo; }

    public String getBar() { return bar; }
    public void setBar(String aBar) { bar = aBar; }

    public java.util.Date getBaz() { return baz; }
    public void setBaz(java.util.Date aBaz) { baz = aBaz; }

    public TestSubbean getSub() { return subbean; }
    public void setSub(TestSubbean aSub) { subbean = aSub; }

    public String[] getThingies()
    {
        String[] retThingies = new String[thingies.size()];
        if (thingies.size() > 0) thingies.copyInto(retThingies);

        return retThingies;
    }

    public void setThingies(String[] newThingies)
    {
        thingies = new Vector(newThingies.length);
        for (int i=0; i < newThingies.length; i++)
        {
            thingies.addElement(newThingies[i]);
        }
    }

    public String getThingies(int i)
    {
        return (String) thingies.elementAt(i);
    }

    public void setThingies(int i, String thingy)
    {
        thingies.setElementAt(thingy, i);
    }

    public String toString()
    {
        StringBuffer ret = new StringBuffer(
            "foo="+foo+";bar="+bar+";baz="+baz.toString()+
            ";thingies=");
        for (int i=0; i < thingies.size(); i++)
        {
            if (i > 0) ret.append(",");
            ret.append((String) thingies.elementAt(i));
        }

        ret.append(";sub=");
        ret.append(subbean.toString());

        return ret.toString();
    }
}

Listing 19.8 shows a servlet that uses the JOX library to display the contents of a bean.

Listing 19.8 Source Code for BeanXMLServlet.java
package tyjsp.xml;

import com.wutka.jox.*;
import com.wutka.jox.test.*;
import javax.servlet.*;
import javax.servlet.http.*;
import java.io.*;

public class BeanXMLServlet extends HttpServlet
{
    public void service(HttpServletRequest request,
        HttpServletResponse response)
        throws IOException, ServletException
    {

// Create the bean and populate it.
        TestBean bean = new TestBean();

        bean.setThingies(
            new String[] { "Moe", "Larry", "Curly", "Shemp",
                "Curly Joe" });

        bean.setFoo(5);
        bean.setBar("This is the bar value");
        bean.setBaz(new java.util.Date());

        TestSubbean sub = new TestSubbean();
        sub.setName("Mark");
        sub.setAge(35);

        bean.setSub(sub);

// Set the content type for the response.
        response.setContentType("text/xml");

// Get the Writer for sending the response.
        Writer out = response.getWriter();

// Wrap a JOXBeanWriter around the output writer.
        JOXBeanWriter beanOut = new JOXBeanWriter(out);

// Write out the object as XML with a root tag of <MarkTest>.
        beanOut.writeObject("MarkTest", bean);
    }
}

Listing 19.9 shows the XML generated by JOX.

Listing 19.9 Source Code for TestBean.xml
<?xml version="1.0"?>
<MarkTest>
<thingies>Moe</thingies>
<thingies>Larry</thingies>
<thingies>Curly</thingies>
<thingies>Shemp</thingies>
<thingies>Curly Joe</thingies>
<foo>5</foo>
<baz>5/15/00 5:59 PM</baz>
<bar>This is the bar value</bar>
<sub>
<age>35</age>
<name>Mark</name>
</sub>
</MarkTest>

As usual, you need to package TestBean and BeanXMLServlet into a WAR file along with a WEB-INF/web.xml file. In addition, you need the copy the jox.jar and dtdparser.jar files into the WEB-INF/lib directory. The web.xml file should look something like Listing 19.10.

Listing 19.10 Source Code for web.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<web-app>
    <servlet>
        <servlet-name>BeanXMLServlet</servlet-name>
        <servlet-class>tyjsp.BeanXMLServlet</servlet-class>
    </servlet>

    <servlet-mapping>
        <servlet-name>BeanXMLServlet</servlet-name>
        <url-pattern>/beanxml</url-pattern>
    </servlet-mapping>
</web-app>

Parsing XML with SAX and DOM

When you use servlets and JSPs to generate XML for application-to-application communication, the client on the other end must be able to interpret the XML and turn it into useful data structures. More importantly, if a client sends XML to your JSP or servlet, you must be able to parse the XML document. You can use any of several different parsers to turn XML documents into Java data structures.

There are two different approaches for parsing XML: SAX and DOM. SAX stands for Simple API for XML, and it enables you to handle XML tags in the data as the parser encounters them. In other words, when the parser locates an XML tag, it calls a Java method to handle the tag. It's up to you to decide what to do with it. DOM, or Document Object Model, isn't strictly an API: It's an object model describing how an XML document is organized. When you use a DOM parser to parse an XML document, the parser reads the entire document and passes you back a Document object containing everything that was defined in the XML document.

Each of these approaches has its advantages and disadvantages, and you certainly don't need to choose one over the other. You can use whichever one makes sense for your situation. For example, if you are parsing very large files (in the 1015MB range and higher), you probably want to use SAX, because a DOM parser first reads the entire file into memory before you can begin processing it. The Java XML API from Sun supports both SAX and DOM.

Using SAX to Parse XML

SAX uses an event-driven model for parsing. The SAX parser reads the XML, and when it finds something interesting, it calls a method in a handler class. The handler class is something that you must write, although there is a skeleton base class that you can start with. SAX will tell you when it finds the beginning of a document, the end of a document, an opening tag, a closing tag, or character data within an element. It will also tell you when it finds an error.

SAX is most useful when you need to read through a very large XML file but you might not need much of the data in the file. If you need to search through a file for a particular tag or data value, SAX is generally much quicker.

Listing 19.11 shows a servlet that reads an XML file sent to it and searches for a particular tag. After it finds the tag, it looks for character data.

Listing 19.11 Source Code for SaxParseServlet.java
package examples.xml;

import java.io.*;

import javax.servlet.*;
import javax.servlet.http.*;

import javax.xml.parsers.*;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;

public class SaxParseServlet extends HttpServlet
{
    public void doPost(HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException
    {
        try
        {
// Create a parser factory.
            SAXParserFactory factory = SAXParserFactory.newInstance();

// Ask the parser factory to create a new parser.
            SAXParser parser = factory.newSAXParser();

// This servlet just sends a plain text response.
            response.setContentType("text/plain");

// Create an input source around the request reader; ask the parser
// to parse the input source and invoke methods in the XMLHandler class
// when it finds XML elements.
            parser.parse(new InputSource(request.getReader()),
                new XMLHandler(request, response));
        }
        catch (ParserConfigurationException exc)
        {
            throw new ServletException(exc.toString());
        }
        catch (SAXException exc)
        {
            throw new ServletException(exc.toString());
        }
    }

    class XMLHandler extends DefaultHandler
    {
        protected HttpServletRequest request;
        protected HttpServletResponse response;

        protected boolean handlingFirstName;
        protected boolean handlingLastName;
        protected boolean inName;

        protected String firstName;
        protected String lastName;

        public XMLHandler(HttpServletRequest aRequest,
            HttpServletResponse aResponse)
        {
            request = aRequest;
            response = aResponse;

            inName = false;
            handlingFirstName = false;
            handlingLastName = false;
        }

        public void startElement(String uri, String name, String qName,
            Attributes attributes)
        {
// Use qualified name if not namespaceAware.
            if ("".equals(name)) name = qName;
// Look for a <name> element.
            if (name.equals("name"))
            {
                inName = true;
                firstName = null;
                lastName = null;
            }
// If inside a <name> element, look for <first>.
            else if (name.equals("first"))
            {
                if (!inName) return;

                handlingFirstName = true;
            }
// If inside a <name> element, look for <last>.
            else if (name.equals("last"))
            {
                if (!inName) return;

                handlingLastName = true;
            }
        }

        public void characters(char[] chars, int start, int length)
        {
// If these characters are occurring inside a <first> element, save them.
            if (handlingFirstName)
            {
                firstName = new String(chars, start, length);
            }
// If these characters are occurring inside a <last> element, save them.
            else if (handlingLastName)
            {
                lastName = new String(chars, start, length);
            }
            else
            {
                return;
            }
        }

        public void endElement(String uri, String name, String qName)
            throws SAXException
        {
// Use qualified name if not namespaceAware.
            if ("".equals(name)) name = qName;

            if (name.equals("name"))
            {
// After the end of the name element, if there's a first and a last name,
// print them separated by a space.
                if ((firstName != null) && (lastName != null))
                {
                    try
                    {
                        PrintWriter out = response.getWriter();

                        out.println(firstName+" "+lastName);
                    }
                    catch (IOException ioExc)
                    {
                        throw new SAXException(ioExc.toString());
                    }
                }
                inName = false;
            }
            else if (name.equals("first"))
            {
                if (!inName) return;
                handlingFirstName = false;
            }
            else if (name.equals("last"))
            {
                if (!inName) return;
                handlingLastName = false;
            }
        }
    }
}

Listing 19.12 shows a test client program that sends the XML file to the servlet from Listing 19.11.

Listing 19.12 Source Code for XMLTestClient.java
import java.io.*;
import java.net.*;

public class XMLTestClient
{
    public static void main(String[] args)
    {
        try
        {
// args[1] is the name of the file to send.
File f = new File(args[1]);
            int contentLength = (int) f.length();

// args[0] is the URL to send the file to.
            URL url = new URL(args[0]);
            URLConnection conn = url.openConnection();

// Tell the URLConnection that this is an XML file.
            conn.setDoOutput(true);
            conn.setRequestProperty("content-type", "text/xml");
            conn.setRequestProperty("content-length", ""+contentLength);

            FileInputStream in = new FileInputStream(f);

            byte[] buffer = new byte[4096];
            int len;

            OutputStream out = conn.getOutputStream();

// Send the XML file to the servlet.
            while ((len = in.read(buffer)) > 0)
            {
                out.write(buffer, 0, len);
            }

            InputStream resp = conn.getInputStream();

// Read the response back from the servlet.
            while ((len = resp.read(buffer)) > 0)
            {
                System.out.write(buffer, 0, len);
            }
        }
        catch (Exception exc)
        {
            exc.printStackTrace();
        }
    }
}

To execute XML test client, make sure you run it in the same directory as test.xml. If you have installed the SAX parse servlet in a WAR file called xml.war and the servlet URL pattern is saxparse, you would execute XMLTestClient with the following command:


java XMLTestClient http://localhost/xml/saxparse test.xml
Using DOM to Parse XML

A DOM parser reads an XML file in its entirety before passing any information to you. It uses a set of Java classes to create a representation of the XML contents. An XML file is structured like a tree. The main document tag is the base of the tree, and each nested tag is a branch of the tree. The document model used by a DOM parser is also structured like a tree. You receive a Document object, which returns a list of Node objects.

The Node class is really an interface, not a class. DOM has a number of classes that implement the Node interface. The one you deal with most often is the Element class, which represents a tag or tag pair from an XML document. You might also find Comment nodes, Text nodes, CDATASection nodes, Character nodes, and several others. The Element class might contain a list of child nodes representing the tags and data contained between the element's opening and closing tags.

Listing 19.13 shows a servlet that uses a DOM parser to parse through the same file as the servlet in Listing 19.9. You can see how different a DOM parser is from a SAX parser. Although SAX is a bit faster, DOM tends to be a bit easier to use when you need to preserve the document's structure.

Listing 19.13 Source Code for DomParseServlet.java
package tyjsp.xml;

import java.io.*;

import javax.servlet.*;
import javax.servlet.http.*;

import javax.xml.parsers.*;
import org.xml.sax.*;
import org.w3c.dom.*;

public class DomParseServlet extends HttpServlet
{
    public void doPost(HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException
    {
        try
        {
// Create a parser factory.
            DocumentBuilderFactory factory = DocumentBuilderFactory.
                newInstance();

// Ask the parser factory to create a new parser.
            DocumentBuilder parser = factory.newDocumentBuilder();

// This servlet just sends a plain text response.
            response.setContentType("text/plain");

            PrintWriter out = response.getWriter();

// Create an input source around the request reader; ask the parser
// to parse the input source.
            Document doc = parser.parse(new InputSource(request.getReader()));

// Get all the Name elements.
            NodeList names = doc.getElementsByTagName("name");

            int numNames = names.getLength();

            for (int i=0; i < numNames; i++)
            {
                Element e = (Element) names.item(i);

                String firstName = null;

// See whether there is a first name.
                NodeList firstNameList = e.getElementsByTagName("first");
                if (firstNameList.getLength() > 0)
                {
                    Element firstNameNode = (Element) firstNameList.item(0);

// Make the really bold assumption that <first> has a child and that
// it is text. You really should check first, though.
                    CharacterData nameText = (CharacterData)
                        firstNameNode.getFirstChild();

                    firstName = nameText.getData();
                }

                String lastName = null;

// See whether there is a last name.
                NodeList lastNameList = e.getElementsByTagName("last");
                if (lastNameList.getLength() > 0)
                {
                    Element lastNameNode = (Element) lastNameList.item(0);

// Make the really bold assumption that <last> has a child and that
// it is text. You really should check first, though.
                    CharacterData nameText = (CharacterData)
                        lastNameNode.getFirstChild();

                    lastName = nameText.getData();
                }

                if ((firstName != null) && (lastName != null))
                {
                    out.println(firstName+" "+lastName);
                }
            }
        }
        catch (ParserConfigurationException exc)
        {
            throw new ServletException(exc.toString());
        }
        catch (SAXException exc)
        {
            throw new ServletException(exc.toString());
        }
    }
}
Using JOX to Parse XML

The JOX library uses a DOM parser to parse through XML. The main reason to use JOX instead of using DOM directly is that JOX automatically copies values from the XML document into a JavaBean. Also, JOX is geared toward reading and writing files and fits very well within the servlet framework.

Listing 19.14 shows a servlet that uses JOX to read an XML file and copy its values into a JavaBean.

Listing 19.14 Source Code for JOXParseServlet.java
package tyjsp.xml;

import java.io.*;

import javax.servlet.*;
import javax.servlet.http.*;

import com.wutka.jox.*;
import com.wutka.jox.test.*;

public class JOXParseServlet extends HttpServlet
{
    public void doPost(HttpServletRequest request,
        HttpServletResponse response)
        throws ServletException, IOException
    {
        TestBean newBean = new TestBean();

        JOXBeanReader reader = new JOXBeanReader(request.getReader());

        reader.readObject(newBean);

// This servlet just sends a plain text response.
        response.setContentType("text/plain");

        PrintWriter out = response.getWriter();

        out.println(newBean.toString());
    }
}
    [ Team LiB ] Previous Section Next Section