The Source for Java Technology Collaboration
User: Password:



Richard Bair's Blog

August 2006 Archives


XMLHttpRequest and Swing

Posted by rbair on August 31, 2006 at 05:01 PM | Permalink | Comments (11)

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!



Varargs Puzzler

Posted by rbair on August 10, 2006 at 02:28 PM | Permalink | Comments (10)

This probably isn't up to Click 'n' Hack's standards, but here's a fun little Java 5 puzzler for a Thursday afternoon.

NOTE: For those in the dark, Click and Hack are really Neal Gafter and Joshua Bloch. They have a little skit they do at conferences where they present java code puzzlers.

Alright, we've had Java 5 for a while now and have had a good chance to get up to date with our new language features. In this puzzler I've written a method that takes a vararg of Objects, counts them up, and returns how many elements there were. Here's the code:


   private static int elementCount(Object... elements) {
     return elements == null ? 0 : elements.length;
   }

   public static void main(String... args) {
     System.out.println("null length array: " + elementCount(null));
     System.out.println("[a,b,c] length array: " + elementCount("a", "b", "c"));
   }

Pretty straight to the point. I have a method called elementCount, and it simply returns the length of the elements array, or 0 if the param is null. So what's wrong with it?

  a) Nothing at all! It works as expected!
  b) There is a compiler warning
  c) It fails to compile
  d) Both (a) and (b)

Ok, I'll give you a few minutes to look it over, read the Java Language Specification, try it out in your favorite IDE, etc. Done?

And the answer is..... d! In fact, there is a compiler warning on this line:


    //line of code
    System.out.println("null length array: " + elementCount(null));

    //compiler warning
    warning: non-varargs call of varargs method with inexact argument type for last parameter;

Besides this warning, though, the code executes properly. But since I don't really like compiler warnings, I go ahead and make the change. Here's the new code.


   private static int elementCount(Object... elements) {
     return elements == null ? 0 : elements.length;
   }

   public static void main(String... args) {
     System.out.println("null length array: " + elementCount((Object)null));
     System.out.println("[a,b,c] length array: " + elementCount("a", "b", "c"));
   }

Alright, that took care of the warning! So what's wrong with it? Anything? Come on, no cheating, think about it!

  a) It runs, but prints out "[a,b,c] length array: 0"
  b) There is a compiler warning
  c) Perfecto!
  d) It runs, but prints out "null length array: 1"

Alright, you've had your chance. What is it?




That's right, "d" again. It turns out the problem is this statement: elementCount((Object)null). The problem is that (Object)null tells the compiler to include a one element Object array where the first element is null. Instead, what we wanted to do was this (Object[])null! This is why we get the compiler warning, the compiler doesn't know if we want null to be an element in the vararg array or whether we want it to be the vararg array itself.

Tripped me up for a few minutes today, hope you enjoyed it.

Note: Updated to refer to "Click and Hack" instead of "Click and Clack" -- also fixed a typo in the description. Thanks for pointing these things out!





Powered by
Movable Type 3.01D
 Feed java.net RSS Feeds