Ajax-like Asynchronous SOA Calls with Java ME

by Eric Giguere

The Java ME platform lends itself quite readily to accessing external resources using a service-oriented architecture (SOA). Support for HTTP-based connectivity as well as core Java features like threads and synchronization make it relatively simple to build SOA client applications.

One of the emerging uses of SOA is in creating more interactive applications, mostly due to the rise of the Ajax programming paradigm. Ajax, which stands for asynchronous JavaScript and XML, is a model for building more responsive browser-based applications. Ajax uses JavaScript for local processing, XML for data exchange, and asynchronous HTTP calls for server interaction. (With synchronous calls, an application must stop its processing and wait for a response; with asynchronous calls, the application sends off the request and continues with its work until it is interrupted by the server's response.) Web applications built using this model benefit because many simple tasks like data validation and page navigation can be done more quickly on the client without the delays implicit in server-side processing, even on fast networks.

Despite what many think, however, Ajax is not a new paradigm for mobile application development. The central tenets of Ajax programming - local data processing whenever possible, the use of portable data formats, and aynchronous server access via HTTP - have long been used by mobile application developers to create applications that work well with slow or unavailable networks. (At least one company has long sold an Ajax-like mobile web application development system.) In fact, many Java ME applications are built using an Ajax-like model, except of course with Java used in place of JavaScript. These are not browser-based applications, so there are more complexities involved, particularly with application deployment, but on the flip side there is also more flexibility in terms of what non-browser applications can do.

That is not to say that Java ME developers cannot learn anything from Ajax. Java ME's built-in HTTP support is synchronous by nature, unlike Ajax's equivalent, the XMLHttpRequest object. (Actually, the XMLHttpRequest object can be used either way, but it is almost always used to make asynchronous calls.) But one of the first things Java ME developers learn is to make all HTTP requests on separate threads, because blocking a system thread is a very bad thing to do.

Unfortunately, there is no built-in support for creating and managing these background connections. Some platforms, for example, limit you to just two or three concurrent HTTP connections at any given time. What we need is a set of utility classes for asynchronous HTTP communication in Java ME similar to what you can do with Ajax's XMLHttpRequest object.

The Callback Interface

Asynchronous communication is interrupt-driven in the sense that we don't know when the response to the request we made will come back. JavaScript deals with interrupts using event handlers, but in Java we use callbacks. So our first task is to define an interface for that callback. Let's call the interface AsyncHttpCallback:

import java.io.IOException;
import javax.microedition.io.HttpConnection;

// Callbacks for asynchronous HTTP processing. Note
// that any streams opened by a callback must later be
// closed by the callback.

public interface AsyncHttpCallback {

    // Stage 1: Fetch URL to open. If null is returned,
    // cancels operation. Otherwise, the URL is used to
    // create an HttpConnection.

    String startingCall( Object cookie );

    // Stage 2: Prepare HttpConnection object. Client
    // sets headers, method type, and writes out any
    // POST data, if any. If false is returned, the
    // HTTP request is cancelled.

    boolean prepareRequest( HttpConnection conn,
                            Object cookie )
                            throws IOException;

    // Stage 3: Request has been made and a response
    // code is available. Check the response code for
    // validity, and return true if further
    // processing is to occur. If processing can
    // continue and the response code is a redirect,
    // create a new connection and then cycle back
    // to stage 2.

    boolean checkResponse( HttpConnection conn,
                           Object cookie )
                           throws IOException;

    // Stage 4: Process the response body, but only
    // if checkResponse returned true.

    void processResponse( HttpConnection conn,
                          Object cookie )
                          throws IOException;

    // Stage 5: Request completed with no exceptions or
    // cancellation. The connection is immediately closed
    // after this call.

    void endingCall( HttpConnection conn,
                     Object cookie )
                     throws IOException;

    // Operation was cancelled or an exception occurred.
    // The connection is immediately closed after this call.
    // If no exception is passed, operation was cancelled.

    void cancelingCall( HttpConnection conn,
                        Object cookie,
                        Throwable exception )
                        throws IOException;

}

It is important to note here that we're not creating a clone of the XMLHttpRequest object, only borrowing some of its ideas to build our utility classes. The methods in the AsyncHttpCallback interface are called at various points throughout the life of an asynchronous HTTP connection, much like the onreadystatechange event of an XMLHttpRequest is triggered.

The underlying HttpConnection object is always passed to the callbacks, because all we're really interested in is managing the request-response cycle, not the reading or writing of data.

For convenience, we also define an abstract class, AsyncHttpCallbackImpl, with default implementations for most of the callbacks:

import java.io.IOException;
import javax.microedition.io.HttpConnection;

// Default implementation of AsyncHttpCallback

public abstract class AsyncHttpCallbackImpl
                implements AsyncHttpCallback {

    // The URL to open must be provided by subclass.
    
    public abstract String startingCall( Object cookie );

    // By default there's nothing to do.

    public boolean prepareRequest( HttpConnection conn,
                                   Object cookie )
                                   throws IOException {
        return true;
    }

    // Only continue if HTTP_OK or one of the redirection
    // codes is returned.

    public boolean checkResponse( HttpConnection conn,
                                  Object cookie )
                                  throws IOException {

        int rc = conn.getResponseCode();

        return( rc == HttpConnection.HTTP_OK ||
                AsyncHttpManager.isRedirect( rc ) );
    }

    // Process response.

    public abstract void processResponse( HttpConnection conn,
                                          Object cookie )
                                          throws IOException;

    // Operation completed with no exceptions. The connection
    // is immediately closed after this call.

    public void endingCall( HttpConnection conn,
                            Object cookie )
                            throws IOException {
    }

    // Operation was cancelled or aborted.

    public void cancelingCall( HttpConnection conn,
                               Object cookie,
                               Throwable exception )
                                   throws IOException {
    }
}
Managing the Connections

With the callbacks defined, we can now create a class to handle the connections, AsyncHttpManager. The singleton instance of this class is responsible for managing the threads that actually process the connections. There's a lot of code here, so be sure to read the comments carefully:

import java.io.IOException;
import java.util.Vector;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;

// Manages a series of asynchronous HTTP requests.
// To make a request, call the queue method and pass an
// object that implements AsyncHttpCallback and an optional
// cookie. The request will be queued and executed on a
// separate thread. At any point you can cancel all pending
// and executing requests by calling the cancelAll method.

public class AsyncHttpManager {

    private AsyncHttpManager(){
    }

    // Cancels a single connection, invoking the
    // appropriate callback.

    private void cancel( AsyncConnection conn, Throwable e ){
        AsyncHttpCallback cb = conn.getCallback();

        try {
            cb.cancelingCall( conn.getHttpConnection(),
                              conn.getCookie(),
                              e );
        }
        catch( IOException ignore ){
        }
        finally {
            close( conn );
        }
    }

    // Cancels all pending requests. Those currently
    // being executed are cancelled as soon as possible,
    // the pending ones are cancelled immediately.

    public void cancelAll(){
        synchronized( _waiting ){

            // Signal each worker thread to end at the
            // earliest opportunity. We don't kill them,
            // we just forget about them and leave them
            // to die.

            for( int i = 0; i < _workers.length; ++i ){
                _workers[i].die();
            }

            // Now run through the queue of waiting
            // requests and cancel each HTTP operation.
            // Cancelling is done by closing each
            // HttpConnection, though whether the call
            // is cancelled immediately or not is up to
            // the system.

            while( _waiting.size() != 0 ){
                AsyncConnection conn = (AsyncConnection)
                    _waiting.firstElement();
                _waiting.removeElementAt( 0 );

                cancel( conn, null );
            }

            // Clean up

            _workers = null; // clear out workers
            _waiting.notifyAll(); // wakes all the workers
        }
    }

    // Close an asynchronous connection. A simple
    // convenience method useful for cleanup.

    private void close( AsyncConnection conn ){
        if( conn != null ){
            close( conn.getHttpConnection() );
            conn.setHttpConnection( null );
        }
    }

    // Close an HttpConnection. Convenience method. 
    // We don't care about any I/O exceptions.

    private void close( HttpConnection hc ){
        if( hc != null ){
            try { hc.close(); } catch( IOException e ){}
        }
    }

    // Return the singleton instance of the manager,
    // creating it if necessary. All interaction will
    // be through this instance.

    public static synchronized AsyncHttpManager getInstance(){
        if( _singleton == null ){
            _singleton = new AsyncHttpManager();
        }

        return _singleton;
    }

    // Returns number of worker threads to create. The
    // number obviously depends on what the underlying
    // platform supports. If the platform only supports
    // two simultaneous HTTP connections, don't set this
    // value any higher than 2.

    public static int getWorkerCount(){
        return _maxWorkers;
    }

    // Initialize the worker threads. Creates and starts
    // a new thread for each worker.

    private void init( int maxCalls ){
        if( maxCalls < 1 ) maxCalls = 1;

        _workers = new Worker[maxCalls];

        for( int i = 0; i < maxCalls; ++i ){
            Worker w = new Worker();
            _workers[i] = w;
            new Thread( w ).start();
        }
    }

    // Is HTTP Return code a redirection? Convenience
    // method.

    public static boolean isRedirect( int rc ){
        return( rc == HttpConnection.HTTP_MOVED_PERM ||
                rc == HttpConnection.HTTP_MOVED_TEMP ||
                rc == HttpConnection.HTTP_SEE_OTHER ||
                rc == HttpConnection.HTTP_TEMP_REDIRECT );
    }

    // Queues a request for processing. A unique cookie
    // will be generated for each request.

    public void queue( AsyncHttpCallback request ){
        queue( request, null, null );
    }

    // Queues a request for processing. The given
    // cookie will be passed back to the callback
    // methods unchanged. If the cookie is null,
    // a unique cookie will be generated.

    public void queue( AsyncHttpCallback request,
                       Object cookie ){
        queue( request, cookie, null );
    }

    // Queues a request for processing with an initial
    // HttpConnection already defined. The given cookie
    // will be passed back to the callback methods. If
    // the cookie is null, a unique cookie will be
    // generated.

    public void queue( AsyncHttpCallback cb,
                       Object cookie,
                       HttpConnection hc ){

        // Synchronize on the queue of waiting
        // requests. If there are no worker threads
        // available, create them. Queue a new call
        // request and then wake the worker threads
        // to let them process the request.

        synchronized( _waiting ){
            if( _workers == null ){
                init( _maxWorkers );
            }

            AsyncConnection conn =
               new AsyncConnection( cb, cookie, hc );

            _waiting.addElement( conn );
            _waiting.notifyAll(); // wake the workers
        }
    }

    // Sets the maximum worker count. Adjust this
    // as appropriate for the device.

    public static void setWorkerCount( int count ){
        _maxWorkers = count;
    }

    private static int              _maxWorkers = 2;
    private static AsyncHttpManager _singleton;

    private Vector _waiting = new Vector();
    private Worker _workers[];

    // AsyncConnection Class --------------------------------
    //
    // Holds the information about a request needed to invoke
    // the callbacks.

    private static class AsyncConnection {

        // Initialize a connection with the given
        // callback and cookie. If cookie is null,
        // creates a new one.

        AsyncConnection( AsyncHttpCallback cb,
                         Object cookie ){
            this( cb, cookie, null );
        }

        // Initialize a connection with the given
        // callback, connection and cookie. If cookie
        // is null, creates a new one.

        AsyncConnection( AsyncHttpCallback cb,
                         Object cookie,
                         HttpConnection hc ){
            _callback = cb;
            _cookie = cookie;
            _httpconn = hc;

            if( _cookie == null ){
                _cookie = new Object();
            }
        }

        AsyncHttpCallback getCallback(){ return _callback; }

        Object getCookie(){ return _cookie; }

        HttpConnection getHttpConnection(){
            return _httpconn;
        }

        void setHttpConnection( HttpConnection hc ){
            _httpconn = hc;
        }

        private AsyncHttpCallback _callback;
        private Object            _cookie;
        private HttpConnection    _httpconn;
    }

    // Worker Class ----------------------------------------
    //
    // Worker waits for a new connection request to be
    // queued and then processes it.

    private class Worker implements Runnable {

        // Tells worker to stop what it's doing and exit
        // as soon as possible.

        public void die(){
            _die = true;
        }

        // Processes a connection request. Implicitly
        // handles HTTP redirections unless checkResponse
        // indicates otherwise.

        private void process( AsyncConnection conn ){
            AsyncHttpCallback cb = conn.getCallback();

            try {
                HttpConnection hc = conn.getHttpConnection();
                Object         cookie = conn.getCookie();
                String         url = null;
                boolean        process = true;

                // Get the starting URL

                if( hc == null ){
                    url = cb.startingCall( cookie );

                    if( url == null ){
                        System.out.println( "Null URL, cancelling" );
                        cancel( conn, null );
                        return;
                    }
                }

                // Prepare the connection and then check
                // the response, handling redirects as
                // necessary.

                int follow = 5;

                while( follow-- > 0 ){
                    hc = conn.getHttpConnection();

                    if( hc == null ){
                        System.out.println( "Connecting to " + url );
                        hc = (HttpConnection) Connector.open( url );
                        conn.setHttpConnection( hc );
                    }

                    if( !cb.prepareRequest( hc, cookie ) ){
                        cancel( conn, null );
                        return;
                    }

                    int rc = hc.getResponseCode();

                    if( !cb.checkResponse( hc, cookie ) ){
                        process = false;
                        break;
                    } else if( !isRedirect( rc ) ){
                        break;
                    }

                    // Handle redirects here

                    url = hc.getHeaderField( "Location" );
                    if( url == null ){
                        throw new IOException( "No Location header" );
                    }

                    if( url.startsWith( "/" ) ){
                        StringBuffer b = new StringBuffer();
                        b.append( "http://" );
                        b.append( hc.getHost() );
                        b.append( ':' );
                        b.append( hc.getPort() );
                        b.append( url );
                        url = b.toString();
                    } else if( url.startsWith( "ttp:" ) ){
                        url = "h" + url;
                    }

                    conn.setHttpConnection( null );
                    close( hc );
                }

                // Ooops, can't actually get it...

                if( follow == 0 ){
                    throw new IOException( "Too many redirects" );
                }

                // Now we can process the data

                if( process ){
                    cb.processResponse( hc, cookie );
                }

                cb.endingCall( hc, cookie );

                close( conn );
            }
            catch( Throwable e ){
                cancel( conn, e );
            }
        }

        // Worker thread main logic. Pulls a single
        // request off the queue and processes it.

        public void run(){
            do {
                AsyncConnection conn = null;

                // Wait for a request to arrive on the
                // queue. When a request is queued, the
                // manager will wake all the threads.

                synchronized( _waiting ){

                    while( !_die ){

                        if( _waiting.size() != 0 ){
                            conn = (AsyncConnection)
                                   _waiting.firstElement();

                            _waiting.removeElementAt( 0 );
                            break;
                        }

                        try {
                            _waiting.wait();
                        }
                        catch( InterruptedException e ){
                        }
                    }
                }

                // If we have a connection request,
                // we must either cancel it or
                // process it....

                if( conn != null ){
                    if( _die ){
                        cancel( conn, null );
                    } else {
                        process( conn );
                    }
                }
            } while( !_die );
        }

        private volatile boolean _die;
    }
}

Two inner classes are used in this class: AsyncConnection to hold the information for a given request, and Worker to actually process the requests. Each Worker instance runs on a separate thread.

Making the Call

To make an asynchronous call, create a class that extends AsyncHttpCallbackImpl and minimally implement startingCall() and processResponse(). Here's an example of a class that sends a GET request to a given URL and dumps the first 20 lines of the response:

import java.io.InputStream;
import java.io.IOException;
import javax.microedition.io.HttpConnection;

// Simple asynchronous HTTP GET request that does nothing
// except dump the stream it gets back from the server.

public class AsyncGetAndDump extends AsyncHttpCallbackImpl {
    AsyncGetAndDump( String url ){
        _url = url;
    }

    public String startingCall( Object cookie ){
        System.out.println( _url + " call starting" );
        return _url;
    }

    public void processResponse( HttpConnection hc,
                                 Object cookie )
                                 throws IOException {
        InputStream in = null;
        int lines = 0;
        String prefix = _url + ": ";

        try {
            StringBuffer b = new StringBuffer();
            int          ch;

            b.append( prefix );

            in = hc.openInputStream();

            while( ( ch = in.read() ) != -1 ){
                b.append( (char) ch );

                if( ch == '\\n' ){
                    System.out.print( b.toString() );
                    b.setLength( 0 );
                    b.append( prefix );
                    ++lines;

                    if( lines > 20 ) break;
                }
            }

            if( b.length() > 0 ){
                System.out.println( b.toString() );
            }
        }
        finally {
            if( in != null ){
                try { in.close(); } catch( IOException e ){}
            }
        }
    }

    public void cancelingCall( HttpConnection hc,
                               Object cookie,
                               Throwable e )
                                   throws IOException {
        if( e != null ){
            System.err.println( _url + " exception: " + e );
        } else {
            System.err.println( _url + " cancelled request" );
        }
    }

    public void endingCall( HttpConnection hc,
                            Object cookie )
                            throws IOException {
        int rc = hc.getResponseCode();

        System.err.println( _url +
                  " request response code was " + rc );
    }


    private String _url;
}

Once such a class is defined, queuing and processing a request is very simple, just a matter of doing this:

String url = "http://www.sun.com";
AsyncGetAndDump req = new AsyncGetAndDump( url );
AsyncHttpManager.getInstance().queue( req );

The request will be processed as soon as possible. To control how many worker threads are used (and hence how many simultaneous connections are possible), call AsyncHttpManager.setWorkerCount() before queuing any requests.

Conclusion

As you can see, the classes to manage asynchronous communication are fairly complex. But these classes aren't enough by themselves, you have to build your application with asynchronous communication in mind.

Comments:

hai

Posted by guest on October 11, 2007 at 08:28 PM PDT #

hai

Posted by guest on October 11, 2007 at 08:29 PM PDT #

Are you agreeing with this tech tip in Japanese, or is there another comment you'd like to make? Thanks!

Posted by cdorffi on November 02, 2007 at 02:48 PM PDT #

Post a Comment:
  • HTML Syntax: NOT allowed
About

Tips for developers who use Java technologies (Java SE, Java ME, JavaFX) for mobile and embedded devices.

Search

Categories
Archives
« April 2014
SunMonTueWedThuFriSat
  
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
   
       
Today