Developer: J2EE
   DOWNLOAD
 Sample Code
 Oracle JDeveloper 10g
 
   TAGS
ajax, java, xml, All
 

 

Enabling Data Exchange in Ajax Applications


by Andrei Cioroianu

 

Learn how to transfer data between Ajax clients and Java servers with XML and JavaScript Object Notation.

Published June 2006

The Ajax core API known as XMLHttpRequest is all about sending HTTP requests for the sole purpose of exchanging data between Web browsers and servers. The JavaScript code running in a Web page can use XMLHttpRequest to submit the request parameters to a server-side script such as a Servlet or a JSP page. The invoked Servlet/JSP sends back a response containing data that is typically used to update the content viewed by the user without refreshing the whole page. This approach has both performance and usability advantages since the network traffic is reduced and the Web UI behaves almost like a desktop GUI.

Developing such a user interface is not an easy task, however, because you have to implement data exchange, validation, and processing, using JavaScript on the client side and Java (or something equivalent) on the server side. Nevertheless, there are many cases when the extra effort needed to build an Ajax-based interface is worthwhile, considering the benefits that it brings to your users.

In this article I’ll present the main methods for exchanging data between Ajax clients and servers, comparing the traditional Web application model with the Ajax model. I will also discuss techniques for handling and processing the data on both ends.

First, you’ll learn how to encode the parameters of a request object on the client side, using JavaScript. You can use the so called URL encoding, which is the default encoding used by Web browsers, or you can include the request parameters in an XML document. The server will process the request and will return a response whose data must be encoded too. The article discusses JavaScript Object Notation (JSON) and XML, which are the main options for the response’s data format.

Significant portions of the article’s content are dedicated to the XML-related APIs that you typically use in an Ajax application. On the client side, the XML API offer is very limited, but it is sufficient. In many cases XMLHttpRequest does everything you need, but you can also parse XML documents and serialize DOM trees in the Web browser, using JavaScript. On the server side, you have lots of APIs and frameworks that allow you to process XML documents. The article shows how to implement basic tasks, using some of the standard Java APIs for XML, which offer support for XML Schema, XPath, DOM, and many other useful standards.

The goal of this article is to provide everything you need for implementing data exchange in Ajax applications, using the best techniques and the most recent APIs. The included sample code is organized in three packages: util, model, and feed. The util package contains classes that provide methods for XML parsing, Schema-based validation, XPath-based querying, DOM serialization and JSON encoding. The model package contains an example data model that can be initialized from an XML document and then converted to the JSON format. You’ll also find a Schema example (used for XML validation) in the model directory. The feed package has classes that simulate a data feed, whose information is retrieved every 5 seconds with Ajax in order to refresh a Web page. The article explains how to avoid a memory leak in the Web browser, by aborting unfinished Ajax requests and deleting the XMLHttpRequest objects after using them.

The web directory contains the JSP and JavaScript samples. If you look into ajaxUtil.js, you’ll find utility functions for sending an Ajax request, aborting a request, and handling HTTP errors. The ajaxUtil.js file also provides JavaScript utilities for XML and URL encodings, XML parsing, and DOM serialization. The ajaxCtrl.jsp file acts as an Ajax controller, receiving every Ajax request, forwarding the parameters to the data model or feed for processing, and then returning the Ajax response. The rest of the Web files are samples that show how to use the utility methods.

Building Requests on the Client-Side

The easiest way to send data to a Web server is to encode the request parameters in a query string, which can be either appended to a URL or included in the request’s body, depending on the used HTTP method. If you need to send complex data structures, a better solution is to encode the information within an XML document. I’ll describe both methods in this section.

Encoding the Request Parameters. When developing a traditional Web application you don’t have to worry about encoding the form data because the Web browser performs this operation automatically when the user submits the data. In the case of an Ajax application, however, it’s your job to encode the request parameters. JavaScript provides a very useful function named escape() that replaces any character that cannot be part of a URL with %HH where HH is the hexadecimal code. For example, any space character is replaced with %20.

The sample code download contains a utility function named buildQueryString() that concatenates the parameters retrieved from an array, separating the name and value of each parameter with = and placing & characters between the name-value pairs:

function buildQueryString(params) {
    var query = "";
    for (var i = 0; i < params.length; i++) {
        query += (i > 0 ? "&" : "")
            + escape(params[i].name) + "="
            + escape(params[i].value);
    }
    return query;
}

Let’s say you want to encode the following parameters:

var someParams = [
    { name: "name",  value: "John Smith" },
    { name: "email", value: "john@company.com" },
    { name: "phone", value: "(123) 456 7890" }
];

The buildQueryString(someParams) call will produce the result included below:

name=John%20Smith&email=john@company.com&phone=%28123%29%20456%207890

If you want to use the GET method, you must append the query string to the URL after the ? character. When using POST, the Content-Type header should be set to application/x-www-form-urlencoded with setRequestHeader() and the query string must be passed to the send() method of XMLHttpRequest, which sends the HTTP request to the server.

Creating an XML Document. Using strings to build the elements with their attributes and data is the simplest way to create an XML document with JavaScript. If you adopt this solution you need a utility method for escaping the & , < , > , " and ' characters:

function escapeXML(content) {
    if (content == undefined)
        return "";
    if (!content.length || !content.charAt)
        content = new String(content);
    var result = "";
    var length = content.length;
    for (var i = 0; i < length; i++) {
        var ch = content.charAt(i);
        switch (ch) {
            case '&':
                result += "&";
                break;
            case '<':
                result += "<";
                break;
            case '>':
                result += ">";
                break;
            case '"':
                result += """;
                break;
            case '\'':
                result += "&apos;";
                break;
            default:
                result += ch;
        }
    }
    return result;
}

To make the task easier, you would also need other utility methods such as this:

function attribute(name, value) {
    return " " + name + "=\"" + escapeXML(value) + "\"";
}

The following example builds an XML document from an array of objects that have three properties: symbol, shares and paidPrice:

function buildPortfolioDoc(stocks) {
    var xml = "<portfolio>";
    for (var i = 0; i < stocks.length; i++) {
        var stock = stocks[i];
        xml += "<stock ";
        xml += attribute("symbol", stock.symbol);
        xml += attribute("shares", stock.shares);
        xml += attribute("paidPrice", stock.paidPrice);
        xml += "/>";
    }
    xml += "</portfolio>";
    return xml;
}

If you prefer using DOM, you can use the Web browser’s APIs for parsing XML and serializing DOM trees. IE lets you create an empty document with new ActiveXObject("Microsoft.XMLDOM"). Then you may use the loadXML() or load() methods to parse the XML from a string or from a URL respectively. In the IE case, each node has a property named xml that lets you get an XML representation of the node and all its descendants. Therefore, you can parse an XML string, modify the DOM tree and then serialize the DOM back to XML.

The Firefox and Netscape browsers allow you to create an empty document with document.implementation.createDocument(...). Then you can create DOM nodes with createElement(), createTextNode(), createCDATASection() and so on. The Mozilla browsers also provide two APIs named DOMParser and XMLSerializer. The DOMParser API contains the parseFromStream() and parseFromString() methods. The XMLSerializer class has the corresponding methods for serializing a DOM tree: serializeToStream() and serializeToString().

The following function parses an XML string and returns the DOM Document:

function parse(xml) {
    var dom;
    try {
        dom = new ActiveXObject("Microsoft.XMLDOM");
        dom.async = false;
        dom.loadXML(xml);
    } catch (error) {
        try {
            var parser = new DOMParser();
            dom = parser.parseFromString(xml, "text/xml");
            delete parser;
        } catch (error2) {
            if (debug)
                alert("XML parsing is not supported.");
        }
    }
    return dom;
}

The next function serializes a DOM Node and all its descendents, returning the XML as a string:

function serialize(dom) {
    var xml = dom.xml;
    if (xml == undefined) {
        try {
            var serializer = new XMLSerializer();
            xml = serializer.serializeToString(dom);
            delete serializer;
        } catch (error) {
            if (debug)
                alert("DOM serialization is not supported.");
        }
    }
    return xml;
}

You can also use XMLHttpRequest as a parser or serializer. The response to an Ajax request is parsed automatically when it is received from the server. You can access both the text version and the DOM tree via the responseText and responseXML properties of XMLHttpRequest. In addition, a DOM tree is serialized automatically when it’s passed to the send() method.

Sending Requests. In a previous article I presented the XMLHttpRequest API and a utility function named sendHttpRequest(), which you can find in the ajaxUtil.js file from the downloadable samples. This function takes four parameters (the HTTP method, the URL, an array of parameters and a callback), creates a XMLHttpRequest object, sets its properties and calls the send() method. If the callback parameter is provided, the request is send asynchronously and the callback function is called when the response is received. Otherwise, the request is send synchronously and you may process the response as soon as sendHttpRequest() returns.

As you can see, you must make some important choices when using XMLHttpRequest

  • the HTTP method you’ll use (GET or POST)
  • the format used to encode the request parameters (XML and URL encoding have been discussed earlier in this article)
  • whether the call will be made synchronously (waiting for the response) or asynchronously (using a callback)
  • the format of the response, such as XML, XHTML, HTML or JavaScript Object Notation (JSON), which will be discussed later in this article.

Let’s suppose you want to get some share prices from a data feed. This information will be refreshed periodically without any user intervention. In this case, the HTTP request should be sent asynchronously because the user interface doesn’t have to be blocked while retrieving the information. The request parameters are an array of symbols, which can be encoded in the URL. You don’t want to send XML documents when making frequent requests because the server could be overloaded. Since you are interested only in the latest share prices, any previous request that hasn’t been completed yet should be aborted:

var ctrlURL = "ajaxCtrl.jsp";
var feedRequest = null;

function sendInfoRequest(symbols, callback) {
    if (feedRequest)
        abortRequest(feedRequest);
    var params = new Array();
    for (var i = 0; i < symbols.length; i++)
        params[i] = {
            name: "symbol",
            value: symbols[i]
        };
    feedRequest = sendHttpRequest(
        "GET", ctrlURL, params, callback);
}

Before calling the abort() method of the request object, the abortRequest() function, which you can find in the ajaxUtil.js file, sets the onreadystatechange property to a callback that does nothing. In addition, it is very important to delete the request object to avoid a memory leak:

function abortRequest(request) {
    function doNothing() {
    }
    request.onreadystatechange = doNothing;
    request.abort();
    delete feedRequest;
}

Let’s consider another case. When transmitting the whole user data to be saved in a database, you should send the request synchronously because you probably don’t want to let the user modify the data while it is being saved. In this case, the XML format is preferred because it is usually easier to encode an object model in a document instead of using lots of string parameters. In addition, the requests for saving the data aren’t very frequent and the server can handle the load without any problems. The XML document could be encoded as a parameter so that you can access it in a JSP page, using the EL syntax ( ${param.xml}). Here is the function that sends the model’s data encoded in an XML document:

function sendSaveRequest(xml) {
    var params = [ { name: "xml", value: xml } ];
    var saveRequest = sendHttpRequest("POST", ctrlURL, params);
    if (saveRequest)
        delete saveRequest;
}

You can also send a request synchronously if you need to restore the object model, retrieving the data from the server. In this case, the server should return a JSON response so that you can easily convert it to a JavaScript object tree with eval(loadRequest.responseText):

function sendLoadRequest() {
    var model = null;
    var loadRequest = sendHttpRequest("GET", ctrlURL);
    if (loadRequest) {
        model = eval(loadRequest.responseText);
        delete loadRequest;
    }
    return model;
}

The following two sections describe what you typically do with the XML documents on the server and how you can respond to the Ajax requests.

Processing Requests on the Server-Side

The Servlet/JSP container parses each HTTP request and creates a ServletRequest instance, which lets you obtain the request parameters with getParameter() / getParameterValues() or the request’s body with getInputStream(). In a JSP page, you can also get the parameters, using the EL syntax (${param...} and ${paramValues...}). Note that you can get a request parameter with getParameter() or ${param...} only if the Ajax client uses an utility function like buildQueryString() to encode the data, using the application/x-www-form-urlencoded format, which was described in the previous section. If you pass an XML document or a DOM tree to the send() method of XMLHttpRequest on the client side, you would have to use the getInputStream() method of ServletRequest on the server side.

Data Validation. A typical Web application does a lot of data validation. Most of the possible errors are fairly simple such as missing request parameters, number format errors, and so on. The user usually causes these errors, forgetting to enter the value of a form element or providing an invalid value. Web frameworks, such as JSF and Oracle ADF Faces are very good at handling the user errors. In the case of an Ajax application, these errors should be caught and handled on the client side, using JavaScript. For example, you can verify if a numeric value is invalid with isNaN(new Number(value)).

For security and reliability reasons, data should be revalidated on the server side and you shouldn’t assume that XML requests have been formatted correctly. XML Schema is a good tool for validating complex requests on the server side. The sample code download contains a class named XMLUtil that provides methods for loading and using schema documents. The following code fragment shows how to initialize a SchemaFactory:

import javax.xml.*;
import javax.xml.validation.*;
...
protected static SchemaFactory schemaFactory;
static {
    schemaFactory = SchemaFactory.newInstance(
        XMLConstants.W3C_XML_SCHEMA_NS_URI);
    schemaFactory.setErrorHandler(newErrorHandler());
}

The newErrorHandler() method returns a SAX error handler:

import org.xml.sax.*;
...
public static ErrorHandler newErrorHandler() {
    return new ErrorHandler() {
        public void warning(SAXParseException e)
                throws SAXException {
            Logger.global.warning(e.getMessage());
        }

        public void error(SAXParseException e)
                throws SAXException {
            throw e;
        }

        public void fatalError(SAXParseException e)
                throws SAXException {
            throw e;
        }
    };
}

You can use getResourceAsStream() to locate and load a XSD file from a directory or JAR specified in the CLASSPATH:

public static InputStream getResourceAsStream(String name)
        throws IOException {
    InputStream in = XMLUtil.class.getResourceAsStream(name);
    if (in == null)
        throw new FileNotFoundException(name);
    return in;
}

Then, the SchemaFactory instance is used to obtain a Schema object with the newSchema() method:

import javax.xml.validation.*;
...
public static Schema newSchema(String name)
        throws IOException, SAXException {
    Schema schema;
    InputStream in = getResourceAsStream(name);
    try {
        schema = schemaFactory.newSchema(new StreamSource(in));
    } finally {
        in.close();
    }
    return schema;
}

You can also create an Oracle XMLSchema object with the following method:

import oracle.xml.parser.schema.XMLSchema;
import oracle.xml.parser.schema.XSDBuilder;
...
public static XMLSchema newOracleSchema(String name)
        throws IOException, SAXException {
    XMLSchema schema;
    InputStream in = getResourceAsStream(name);
    try {
        XSDBuilder builder = new XSDBuilder();
        schema = builder.build(new InputSource(in));
    } catch (Exception e) {
        throw new SAXException(e);
    } finally {
        in.close();
    }
    return schema;
}

The next thing you need to create is a DocumentBuilderFactory. The setSchema() method, which was defined by JAXP 1.2, may throw an UnsupportedOperationException if a JAXP 1.1 implementation is found in CLASSPATH, replacing the JAXP 1.2 implementation of Java SE 5.0. In this case, you can still create a schema object using newOracleSchema() and you can set it with the setAttribute() method:

import javax.xml.parsers.*;
import oracle.xml.jaxp.JXDocumentBuilderFactory;
...
public static DocumentBuilderFactory newParserFactory(
        String schemaName) throws IOException, SAXException {
    DocumentBuilderFactory parserFactory
        = DocumentBuilderFactory.newInstance();
    try {
        parserFactory.setSchema(newSchema(schemaName));
    } catch (UnsupportedOperationException e) {
        if (parserFactory instanceof JXDocumentBuilderFactory) {
            parserFactory.setAttribute(
                JXDocumentBuilderFactory.SCHEMA_OBJECT,
                newOracleSchema(schemaName));
        }
    }
    return parserFactory;
}

Then, you create a DocumentBuilder object that can be used to validate and parse an XML document:

import javax.xml.parsers.*;
...
public static DocumentBuilder newParser(
        DocumentBuilderFactory parserFactory)
        throws ParserConfigurationException {
    DocumentBuilder parser = parserFactory.newDocumentBuilder();
    parser.setErrorHandler(newErrorHandler());
    return parser;
};

Let’s say you want to validate an XML document against the portfolio.xsd schema example:

<xsd:schema xmlns:xsd="http://www.w3.org/2001/XMLSchema">

    <xsd:element name="portfolio" type="portfolioType"/>

    <xsd:complexType name="portfolioType">
        <xsd:sequence>
            <xsd:element name="stock"
                    minOccurs="0" maxOccurs="unbounded">
                <xsd:complexType>
                    <xsd:attribute name="symbol"
                        type="xsd:string" use="required"/>
                    <xsd:attribute name="shares"
                        type="xsd:positiveInteger" use="required"/>
                    <xsd:attribute name="paidPrice"
                        type="xsd:decimal" use="required"/>
                </xsd:complexType>
            </xsd:element>
        </xsd:sequence>
    </xsd:complexType>

</xsd:schema>

The parsePortfolioDoc() method of the DataModel class uses XMLUtil to validate and parse the xml parameter, returning a DOM Document:

private static final String SCHEMA_NAME
    = "/ajaxapp/model/portfolio.xsd";
private static DocumentBuilderFactory parserFactory;
    
private static Document parsePortfolioDoc(String xml)
        throws IOException, SAXException,
        ParserConfigurationException {
    synchronized (DataModel.class) {
        if (parserFactory == null)
            parserFactory = XMLUtil.newParserFactory(SCHEMA_NAME);
    }
    DocumentBuilder parser = XMLUtil.newParser(parserFactory);
    InputSource in = new InputSource(new StringReader(xml));
    return parser.parse(in);
}

Now that you have a DOM tree, the next thing to do is getting the data you need form the DOM nodes.

Extracting the Needed Information. You can navigate a DOM tree, using the DOM API, or you can use a query language such as XQuery or XPath. Java provides a standard API for XPath, which is used next. The XMLUtil class creates an XPathFactory that has a newXPath() method:

import javax.xml.xpath.*;
...
protected static XPathFactory xpathFactory;
static {
    xpathFactory = XPathFactory.newInstance();
}
    
public static XPath newXPath() {
    return xpathFactory.newXPath();
}

The following methods evaluate XPath expressions in a given context, returning the resulted values:

import javax.xml.xpath.*;
import org.w3c.dom.*;
...
public static String evalToString(String expression,
        Object context) throws XPathExpressionException {
    return (String) newXPath().evaluate(expression, context,
        XPathConstants.STRING);
}
    
public static boolean evalToBoolean(String expression,
        Object context) throws XPathExpressionException {
    return ((Boolean) newXPath().evaluate(expression, context,
        XPathConstants.BOOLEAN)).booleanValue();
}
    
public static double evalToNumber(String expression,
        Object context) throws XPathExpressionException {
    return ((Double) newXPath().evaluate(expression, context,
        XPathConstants.NUMBER)).doubleValue();
}
    
public static Node evalToNode(String expression,
        Object context) throws XPathExpressionException {
    return (Node) newXPath().evaluate(expression, context,
        XPathConstants.NODE);
}
    
public static NodeList evalToNodeList(String expression,
        Object context) throws XPathExpressionException {
    return (NodeList) newXPath().evaluate(expression, context,
        XPathConstants.NODESET);
}

The setData() method of DataModel uses the XPath evaluation methods to extract the information from a portfolio XML document:

public synchronized void setData(String xml)
        throws IOException, SAXException,
        ParserConfigurationException,
        XPathExpressionException {
    try {
        ArrayList<stockbean> stockList
            = new ArrayList<stockbean>();
        Document doc = parsePortfolioDoc(xml);
        NodeList nodeList = XMLUtil.evalToNodeList(
            "/portfolio/stock", doc);
        for (int i = 0; i < nodeList.getLength(); i++) {
            Node node = nodeList.item(i);
            StockBean stock = new StockBean();
            stock.setSymbol(
                XMLUtil.evalToString("@symbol", node));
            stock.setShares(
                (int) XMLUtil.evalToNumber("@shares", node));
            stock.setPaidPrice(
                XMLUtil.evalToNumber("@paidPrice", node));
            stockList.add(stock);
        }
        this.stockList = stockList;
    } catch (Exception e) {
        Logger.global.logp(Level.SEVERE, "DataModel", "setData",
            e.getMessage(), e);
    }
}

Once you have the data in an object model on the server side, you can process it according to your application’s requirement. Then, you must respond to the Ajax request.

Generating Responses on the Server-Side

Returning HTML as the response to an Ajax request is the easiest solution because you can use the JSP syntax to build the markup and the Ajax client just has to insert the HTML somewhere in the page, using the innerHTML property of a <div> or <span> element. It is much more efficient, however, to return only the data without any presentation markup to the Ajax client. You can use an XML format or JSON.

Producing XML Responses. Java EE provides many options for creating XML documents, which can be generated with JSP, created from object trees with JAXB, or produced with javax.xml.transform.Transformer as in the following example that serializes a DOM tree:

import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;
...
public static TransformerFactory serializerFctory;
static {
    serializerFctory = TransformerFactory.newInstance();
}
    
public static void serialize(Node node, OutputStream out)
        throws TransformerException {
    Transformer serializer = serializerFctory.newTransformer();
    Properties serializerProps = new Properties();
    serializerProps.put(OutputKeys.METHOD, "xml");
    serializer.setOutputProperties(serializerProps);
    Source source = new DOMSource(node);
    Result result = new StreamResult(out);
    serializer.transform(source, result);
}

With so many standard options and open source frameworks for producing XML on the server side, the only problem you have is choosing the one that is right for you. On the client side, however, the situation is very different since DOM is the only option for parsing the XML. Some browsers also provide support for XPath and XSLT.

In a previous Ajax article, you learned how to produce the XML with JSP and then process it on the client side with JavaScript and DOM. Another solution is to use JSON instead of XML as a data format for the response to the Ajax request. As mentioned earlier, a JSON string can be converted to a JavaScript object tree with the eval() function. This is much easier than extracting the information from a DOM tree with JavaScript. All you need is a good utility class for producing JSON on the server side.

JSON Encoding. The JSONEncoder class provides methods for encoding literals, objects and arrays. The result is stored in a java.lang.StringBuilder:

package ajaxapp.util;

public class JSONEncoder {
    private StringBuilder buf;
    
    public JSONEncoder() {
        buf = new StringBuilder();
    }

    ...
}

The character() method encodes a single character:

public void character(char ch) {
    switch (ch) {
        case '\'':
        case '\"':
        case '\\':
            buf.append('\\');
            buf.append(ch);
            break;
        case '\t':
            buf.append('\\');
            buf.append('t');
            break;
        case '\r':
            buf.append('\\');
            buf.append('r');
            break;
        case '\n':
            buf.append('\\');
            buf.append('n');
            break;
        default:
            if (ch >= 32 && ch < 128)
                buf.append(ch);
            else {
                buf.append('\\');
                buf.append('u');
                for (int j = 12; j >= 0; j-=4) {
                    int k = (((int) ch) >> j) & 0x0f;
                    int c = k < 10 ? '0' + k : 'a' + k - 10;
                    buf.append((char) c);
                }
            }
    }
}

The string() method encodes a whole string:

public void string(String str) {
    int length = str.length();
    for (int i = 0; i < length; i++)
        character(str.charAt(i));
}

The literal() method encodes a JavaScript literal:

public void literal(Object value) {
    if (value instanceof String) {
        buf.append('"');
        string((String) value);
        buf.append('"');
    } else if (value instanceof Character) {
        buf.append('\'');
        character(((Character) value).charValue());
        buf.append('\'');
    } else
        buf.append(value.toString());
}

The comma() method appends a comma character:

private void comma() {
    buf.append(',');
}

The deleteLastComma() method removes the last comma character if it is found at the end of the buffer:

private void deleteLastComma() {
    if (buf.length() > 0)
        if (buf.charAt(buf.length()-1) == ',')
            buf.deleteCharAt(buf.length()-1);
}

The startObject() method appends a { character, staring a JavaScript object:

public void startObject() {
    buf.append('{');
}

The property() method encodes a JavaScript property:

public void property(String name, Object value) {
    buf.append(name);
    buf.append(':');
    literal(value);
    comma();
}

The endObject() method appends a } character, ending a JavaScript object:

public void endObject() {
    deleteLastComma();
    buf.append('}');
    comma();
}

The startArray() method appends a [ character, starting a JavaScript array:

public void startArray() {
    buf.append('[');
}

The element() method encodes an element of a JavaScript array:

public void element(Object value) {
    literal(value);
    comma();
}

The endArray() method appends a ] character, ending a JavaScript array:

public void endArray() {
    deleteLastComma();
    buf.append(']');
    comma();
}

The toString() method returns the JSON string:

public String toString() {
    deleteLastComma();
    return buf.toString();
}

The clear() method clears the buffer:

public void clear() {
    buf.setLength(0);
}

The JSONEncoder class is used by DataModel to encode the data that it maintains:

public synchronized String getData() {
    JSONEncoder json = new JSONEncoder();
    json.startArray();
    for (int i = 0; i < stockList.size(); i++) {
        StockBean stock = stockList.get(i);
        json.startObject();
        json.property("symbol", stock.getSymbol());
        json.property("shares", stock.getShares());
        json.property("paidPrice", stock.getPaidPrice());
        json.endObject();
    }
    json.endArray();
    return json.toString();
}

The ajaxCtrl.jsp page sets the model’s data if the xml request parameter is present. Otherwise, it outputs the JSON string returned by getData(), using the ${dataModel.data} EL expression:

<%@ taglib prefix="c" uri="#" %>
...
<jsp:useBean id="dataModel" scope="session"
    class="ajaxapp.model.DataModel" />

<c:choose>
    ...
    <c:when test="${!empty param.xml}">
        <c:set target="${dataModel}" 
            property="data" 
            value="${param.xml}" />
    </c:when>
    <c:otherwise>
        ${dataModel.data}
    </c:otherwise>
</c:choose>

The job is not finished since the Ajax client must process the JSON data.

Handling Responses on the Client-Side

In a typical Web application, you generate the content on the server-side, using JSP, Web frameworks and tag libraries. Ajax applications fit very well into this profile since Web frameworks such as JavaServer Faces and Oracle ADF Faces can help you a lot when building Ajax applications. There is however, a significant difference between Ajax and non-Ajax applications. When using Ajax, you must handle data on the client side and generate pieces of content dynamically with JavaScript in order to present the data to the user.

If you use the JSON format for the data transfer, it is very easy to convert the text to an object tree, using the eval() function provided by JavaScript. If you prefer using XML, you’ll have more work to do, but this format has its own advantages. For example, many types of clients can consume XML while JSON is easy to parse only in a JavaScript environment. In addition, errors can be spotted and fixed much faster in the XML’s case, which reduces the debugging time.

Accessing DOM Trees with JavaScript. The DOM API for JavaScript is very similar to Java’s org.w3c.dom package. The main difference is the direct access to properties in the JavaScript’s case while Java keeps the properties private, letting you access them with get and set methods. For example, you can get a document’s root element with dom.documentElement.

DOM is somehow a low-level API, giving you access to the structure of the parsed documents. For example, you want to ignore the comments in most cases and you probably wouldn’t expect to have adjacent text nodes. Let’s take a simple example:

var xml = "<element>da<!--comment-->ta&amp;"
    + "<![CDATA[cdata</element>";

You could parse the above XML string with a utility function that was presented earlier:

var dom = parse(xml);

You can find the code of the parse() function in ajaxUtil.js. In this example, it returns a DOM tree whose root element contains a text node, followed by a comment, another text node and a character data node. If you want the contained text without the comment, you have to iterate over the element’s children, concatenating the values of the text and character data nodes, whose types are 3 and 4 respectively:

var element = dom.documentElement;
var childNodes = element.childNodes;
var text = "";
for (var i = 0; i < childNodes.length; i++)
    if (childNodes[i].nodeValue) {
        var type = childNodes[i].nodeType;
        if (type == 3 || type == 4)
            text += childNodes[i].nodeValue;
    }

When using DOM, you should build a small set of utility functions so that you don’t have to deal with low-level details like those described above.

Generating Dynamic Content with JavaScript. Web browsers give you access to the DOM structure of the Web page via the document object. For example, you can locate very easily an element with document.getElementById(...). You can also create new elements and text nodes, which can be inserted into the existing document. It is much simpler, however, to build the HTML by concatenating strings like in the following example:

function updateInfo(request) {
    var shares = eval(request.responseText);
    var table = "<table border=1 cellpadding=5>";
    table += "<tr>";
    table += "<th>Symbol</th>";
    table += "<th>Trend</th>";
    table += "<th>Last Price</th>";
    table += "</tr>";
    for (var i = 0; i < shares.length; i++) {
        var share = shares[i];
        var symbol = escapeXML(share.symbol)
        var trend = share.trend > 0 ? "+" : "-";
        var lastPrice = new Number(share.lastPrice).toFixed(2);
        table += "<tr>";
        table += "<td>" + symbol + "</td>";
        table += "<td>" + trend + "</td>";
        table += "<td>" + lastPrice + "</td>";
        table += "</tr>";
    }
    table += "</table>";
    document.getElementById("table").innerHTML = table;
}

By setting the innerHTML property of the object returned by getElementById(), the generated HTML can be inserted into an empty <div> element like this:

<div id="table">
</div>

This article’s samples use the updateInfo() function as a callback for handling the responses to the Ajax requests that are sent to the server with sendInfoRequest from the ajaxLogic.js file. If you want to update the information every 5 seconds, you can use the setInterval() function of JavaScript:

var symbols = [ ... ];
setInterval("sendInfoRequest(symbols, updateInfo)", 5000);

A class named DataFeed simulates a feed on the server side. The ajaxCtrl.jsp page invokes the feed’s getData() method, returning the response as a the JSON string. On the client side, the updateInfo() function parses the JSON string with eval(request.responseText) as you’ve seen in the code sample presented above.

Conclusion

In this article, you’ve learned how to transfer data from an Ajax client to a server, using the URL encoding and XML formats. Then, you’ve seen how to validate the data with XML Schema, extract the needed information with XPath and produce XML or JSON responses on the server side. Finally, you’ve found out how to handle the XML and JSON responses with JavaScript and how to generate dynamic content that is used to update the Web page in the browser.


Andrei Cioroianu ( devtools@devsphere.com) is the founder of Devsphere, a provider of development, integration, and consulting services. Cioroianu has written many Java articles published by Oracle Technology Network, ONJava, JavaWorld, and Java Developer’s Journal. He also coauthored the books Java XML Programmer’s Reference and Professional Java XML (both from Wrox Press).