Skip to main content

XMLHttpRequest and Swing

Posted by rbair on August 31, 2006 at 5:01 PM PDT

For a while now (since well before we started the SwingX-WS project) I've been interested in making Swing accessible to the hordes of web authors out there. This demographic is being actively persued by Adobe(Flex) and Microsoft. Web folks are doing more and more to enhance their web applications via AJAX in order to get closer to a rich client experience. At some point it makes sense to use a rich client toolkit -- and each of us (Flex/Flash, Avalon/XAML/WPF, and Swing) want to be the toolkit used. This space (called Rich Internet Application or Smart Clients) is, I believe, a real growth area.

Our job, on the Swing team, is to ensure that when a development team hits that inflection point and wants to write a rich client app, they can do so in Java with Swing with the minimum amount of fuss. It is in this vein that XMLHttpRequest -- the Java implementation -- came to be.

In SwingLabs and on the Swing team we've long been aware of the difficulties people face when trying to write responsive GUI apps due to the inherent difficulties in writing threaded apps. In the past I've blogged about SwingWorker and BackgroundWorker -- two different approaches to this problem. Both of these are reasonable ways to simplify threading issues. The XMLHttpRequest code I'm introducing today also deals with threading issues -- but isn't as general purpose as SwingWorker or BackgroundWorker.

In this installment (I'm getting to the point, I promise!) I introduce the XMLHttpRequest and JSONHttpRequest beans, both part of the SwingX-WS project. I based the design and implementation on the W3C Working Draft Specification for XMLHttpRequest. So the good news is, for those of you familiar with XHR, you have essentially the same API available now for your Swing apps.

NOTE: The classes mentioned in this blog have been refactored into the org.jdesktop.http.async package of SwingX-WS. Also, the class HttpRequest has been renamed AsyncHttpRequest.

Before I get into the mechanics of making the call (if you care, just scroll down to the examples below), I want to take a few bytes of bandwidth and describe the kind of applications that would care about XMLHttpRequest. If you have an application that makes calls to REST web services (either structured or ad hoc), you'd probably care. If you are writing a fairly lightweight application -- heavy on graphics and light on data objects -- that uses, say, online photos, you'd care. If you were an AJAX saavy developer who wanted to try your hand at a little Swing -- you'd care.

At the heart of the API is a class called HttpRequest. This little fella is really where all the fun is. It implements almost all of the API (all, in fact, except for getResponseXML) defined in the aforementioned W3C specification. HttpRequest is a concrete implementation, which can be used directly.

The general concepts are pretty simple to grasp. Here's a basic code sample:

        final HttpRequest req = new HttpRequest();
        req.addOnReadyStateChangedListener(new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getNewValue() == ReadyState.LOADED) {
                    String response = req.getResponseText();
                    //do what you like with the response here
                    //for example, if the XML was XMLDecoder
                    //compatible:
                    java.beans.XMLDecoder decoder = new java.beans.XMLDecoder(
                        new ByteArrayInputStream(response.getBytes()));
                    Customer c = (Customer)decoder.readObject();
                    decoder.close();
                }
            }
        });
        try {
            req.open(HttpMethod.GET, new URL("http://MyCompany.com/someService?customer="A2342DAD"/"));
            req.send();
        } catch (Exception e) {
            e.printStackTrace();
        }

Those familiar with XMLHttpRequest will find this code to be really straitforward. In fact, the course grained API calls are all exactly the same:

  1. Create the HttpRequest object
  2. Attach an onreadystatechanged event listener
  3. Call "open", passing in the http method and URL
  4. Call "send"
  5. In the onReadyStateChanged event listener, listen for the ready state of LOADED.
  6. Do the stuff

This basic mechanism could be used rather cleanly for all kinds of scenarios. In this example I use it as a mechanism for deserializing some Customer java bean. I could also have passed the responseText to a SAX/STAX/DOM parser. Or maybe the text is HTML and I wanted to display it in an XHTML renderer. Maybe it was JAXX XML document, and represented some Swing widgets that should be created. Maybe it is some JavaScript code which, when used with JDK 6 and the scripting APIs could modify the Swing UI in some way. Perhaps it is JSON. Perhaps it is Java2D drawing code. Perhaps a serialized set of Painters. You get the drift.

In addition to getting data, HttpRequest can also be used to send data. Here is some simple code to do so:

        req.open(HttpMethod.POST, new URL("http://MyCompany.com/submit?Customer="A2342DAD""));
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        java.beans.XMLEncoder e = new java.beans.XMLEncoder(out);
        e.writeObject(customer);
        e.flush();
        req.send(out.toString());
        out.close();

This example reverses the last -- it constructs XML representing this customer bean and, using HTTP POST, sends this data to the web server.

For convenience, I extended HttpRequest with XMLHttpRequest and JSONHttpRequest. XMLHttpRequest simply adds the getResponseXML method to finish the XMLHttpRequest spec. This method returns a DOM object. Not always the most useful API to work with from within Java, but sometimes quite powerful when combined with XPath. A silly example:

     Document dom = req.getResponseXML();
     XPath xpath = XPathFactory.newInstance().newXPath();
     try {
         XPathExpression exp = xpath.compile("//p");
         NodeList nodes = (NodeList)exp.evaluate(dom, XPathConstants.NODESET);
         for (int i=0; i<nodes.getLength(); i++) {
             System.out.println(nodes.item(i).getTextContent());
         }
     } catch (Exception e) {
         e.printStackTrace();
     }

JSON is another alternative. Not as expressive or structured as XML but much faster to parse and easy to work with loosey-goosey. JSONHttpRequest supplies two new methods: getResponseJSON which returns a JSONObject, and getResponseMap returning a Map -- essentially what JSON boils down too. Ok, one more example and I'm calling it quites for today:

    public static void main(String[] args) {
        final JsonHttpRequest req = new JsonHttpRequest();
        req.addOnReadyStateChangedListener(new PropertyChangeListener() {
            public void propertyChange(PropertyChangeEvent evt) {
                if (evt.getNewValue() == ReadyState.LOADED) {
                    //extracting the data using JSON Java library
                    JSONObject json = req.getResponseJSON();
                    json = json.optJSONObject("ResultSet");
                    System.out.println("Total Results Avail.: " + json.optString("totalResultsAvailable"));
                    System.out.println("Results: ");
                    JSONArray results = json.optJSONArray("Result");
                    for (int i=0; i<results.length(); i++) {
                        System.out.println("\tTitle: " + results.optJSONObject(i).optString("Title"));
                    }
                   
                    //extracting the data just using maps & arrays
                    Map map = (Map)req.getResponseMap().get("ResultSet");
                    System.out.println("Total Results Avail.: " + map.get("totalResultsAvailable"));
                    System.out.println("Results: ");
                    Object[] results2 = (Object[])map.get("Result");
                    for (int i=0; i<results2.length; i++) {
                        Map m = (Map)results2[i];
                        System.out.println("\tTitle: " + m.get("Title"));
                    }
                }
            }
        });
        try {
            req.open(HttpMethod.GET, new URL("http://api.search.yahoo.com/ImageSearchService/V1/imageSearch?appid=YahooDemo&query=JavaOne&output=json"));
            req.send();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

I hope you found this interesting. Hopefully in a future installment I'll get to showing a real live Swing demo doing something useful, besides printing junk out to System.out. Romain? Come on buddy, give a brother a hand!

Related Topics >>