Wireless applications that must contact a remote server for their data present special problems for the developer. Protocol support in J2ME (Java 2 Platform, Micro Edition) is much more limited than in J2SE (Java 2 Platform, Standard Edition), and some devices might require multiple access methods. Also, radio communications are much less reliable than the landline connections most Internet standards assume. Standalone J2ME applications that don't require server connectivity lack these problems. Because of these difficulties, you need to modify standard architectures to accommodate wireless development's special needs.

This article introduces some architectural solutions to the problems wireless applications face and presents a general framework for hosting multiple J2ME applications. To illustrate how to use the framework, I describe and implement two applications: Live Chess and Event Alert. Figure 1 shows an emulator running Live Chess. This sample application lets the user watch a live chess game with a mobile phone or other Java-enabled device. By studying the framework and how these applications are implemented, you can appreciate the key factors in designing an architecture for live data delivery—one of wireless technology's most exciting capabilities.

Figure 1. J2ME enables dynamic graphical applications on mobile phones

To understand the material presented here, you should already be familiar with J2ME technologies, such as MIDP (Mobile Information Device Profile) and related protocols, including WAP/WML (Wireless Application Protocol/Wireless Markup Language). See Resources for more information on these subjects.

Development tools

Although this article is primarily about J2ME architectures, I will briefly describe the tools I used to develop the sample applications. Working with J2ME and specialized devices means dealing with additional development complexities, such as the need to use emulators. Also, you need to preverify your classfiles when writing for a CLDC (Connected Limited Device Configuration) device. Preverification allows for a smaller and simpler device security verifier. For full information on J2ME compilation and tools, see Resources.

I used Sun Microsystems' J2ME Wireless Toolkit to develop the client packages for Live Chess and Event Alert. This toolkit integrates with Sun's Sun ONE Studio (formerly, Forte for Java) environment and with JBuilder's MobileSet extension. Using the integrated toolkit, you can compile, preverify, and debug your program while simultaneously using an emulator. This interactive debugging can be useful for complex interfaces.

Instead of using an integrated environment, I opted for a special tool in Sun's toolkit called the KToolbar. The KToolbar is a mini-development environment that performs the key tasks in creating a MIDlet suite. I developed the source files for my projects separately in JBuilder and then used the KToolbar to generate the MIDlet suites. Using the KToolbar is somewhat awkward because it expects a fixed directory structure, so unless you are willing to change your source tree conventions to match KToolbar's, you must create a separate source tree and copy files back and forth. You can do limited debugging with KToolbar because it dumps System.out to its console window as your application runs in the emulator, and it has some built-in tracing capabilities.

Experienced J2ME developers will want to use an integrated environment for greater convenience, but alternatives—such as using KToolbar, or even compiling, preverifying, and debugging from the command line—are completely workable. For wireless applications, how you organize your architecture end-to-end is much more important than your development tools in determining a given application's overall maintainability. This is why basing your projects on a sound design is so important.

Overview of live data requirements

To architect J2ME applications that require live data updates from a server, you must first analyze the functionality range you desire. Figure 2 shows the typical case.

Figure 2. The framework's overall plan creates a two-tier structure on the server that isolates the application logic from the server functionality. This is especially important for J2ME support because the server might support various protocols.

On the client side, MIDlets support any Java-enabled device. The device's MIDlets divide conceptually into three separate code regions along the lines of the Model-View-Controller (MVC) pattern:

  • Model: Preferences, constraints, and application data
  • View: Display logic
  • Controller: Communication and application logic

The controller should support both data polling and listening for notifications. Currently, the MIDP specification does not support listening, but the upcoming MIDP 2.0 will. In addition, some devices, such as RIM's BlackBerry, currently have extensions to support listening for server push. You must prepare the display logic to render appropriate interfaces over a range of resolutions. The data model includes user preferences, application constraints, and the presentation data.

The idea behind application constraints is that different data model versions can be appropriate for different device types. For example, if the device does not support sound, then the server should not waste bandwidth by including application sound files in its data transmissions. Likewise, the server might send different logo and image sets depending on whether the device has a medium-sized display (160 by 160 pixels) or a small display (120 by 120 pixels).

On the server side, the architecture should be prepared to deliver its data via a variety of interfaces, including WML and servlets. Under normal circumstances, servlets are the preferred method of communicating with a MIDlet. With WML presentation capability, you have a backup in case the user cannot install the client for some reason or has trouble operating it. Being able to implement the framework with J2EE interfaces is also an important server-side consideration because most commercial clustering systems only work with Enterprise JavaBeans (EJBs).

Servers should also have a notification engine that lets them send and receive out-of-band messages, such as SMS (short message service) messages and email. This capability can be an integral part of your application and can also act as a backup system. If you have users who do not have Java-enabled devices, your notification engine might be the only way to reach them.

The client framework

Now that you have an idea of the overall architecture's elements, let's consider the client design in more detail. Figure 3 shows how to structure a client that primarily displays server-generated information. Most importantly, you should isolate and abstract the data package or packages that are traded with the server. You should not process this raw data in different parts of the MIDlet. When the package arrives, it must first be validated and then used to populate the data model. Other MIDlet parts use the data model exclusively. Validation is necessary because wireless networks are lossy and the client often receives fragmentary transmissions, in which case, it needs to repeat its request.

Figure 3. In the client architecture, the server's data packets hydrate the data model used by the MIDlet for all its operations. The controller has two possible inbound interface mechanisms—one for polling, the other for listening. MIDP 2.0 will support the listener.

The controller manages communication with the server and activation of the MIDlet's displayables. A standard way to implement multiple communication interfaces (e.g., both polling and notification) on the controller is to create a base class with common functionality and then subclass it with the appropriate interface. Listing 1 shows how we do this in the case of the Live Chess MIDlet:

Listing 1. Live Chess

class ServerCommunicator {
      //Override these methods to provide functionality
      boolean zPacketArrived( DataPacket dp ){ return false; }
      boolean zPacketSend( DataPacket dp ){ return false; }
}
class ServerCommunicator_Polling extends ServerCommunicator {
      private final static int POLLING_INTERVAL_MS = 10000;
      private final static String SERVER_URL = 
            "http://example.com/data-servlet";
      void vStart(){
            PollingContainer poller = new PollingContainer(this);
            java.util.Timer timerPoller = new java.util.Timer();
            timerPoller.schedule(poller, 0, POLLING_INTERVAL_MS);
      }
      String getURL(){ return SERVER_URL; }
      boolean zPacketArrived(DataPacket dp){
            if( !LiveChessClient.zValidatePacket(dp) ) return false;
            LiveChessClient.vProcessPacket(dp);
      }
      boolean zPacketSend( DataPacket dp ){
            try {
                  int iDataLength = dp.length();
                  HttpConnection con =
                        HttpConnection)Connector.open(this.getURL());
                  con.setRequestMethod(HttpConnection.POST);
                  con.setRequestProperty("User-Agent", 
                        "Profile/MIDP-1.0 Configuration/CLDC-1.0" );
                  con.setRequestProperty("Content-Language", "en-US" );
                  con.setRequestProperty("Accept", "application/octet-stream" );
                  con.setRequestProperty("Connection", "close" );
                  con.setRequestProperty("Content-Length", 
                        Integer.toString( iDataLength ) );
                  OutputStream os = con.openOutputStream();
                  os.write( dp.getBytes() );
                  os.close();
                  return true;
            } catch( Exception ex ) {
                  return false;
            }
      }
}
class PollingContainer extends java.util.TimerTask {
      ServerCommunicator_Polling mParent;
      public PollingContainer( ServerCommunicator_Polling parent ){
          mParent = parent;
      }
      public final void run(){
          String sURL = mParent.getURL();
            try {
                  vPollServer( sURL );
            } catch(Exception ex) {}
      }
      public void vPollServer ( String sURL ) throws IOException {
            HttpConnection con = (HttpConnection)Connector.open(sURL);
            con.setRequestMethod(HttpConnection.GET);
            con.setRequestProperty("User-Agent", 
                  "Profile/MIDP-1.0 Configuration/CLDC-1.0" );
            con.setRequestProperty("Content-Language", "en-US" );
            con.setRequestProperty("Accept", "application/octet-stream" );
            con.setRequestProperty("Connection", "close" );
            byte[] abData;
            if( con.getResponseCode() == HttpConnection.HTTP_OK ){
                  InputStream isData = con.openInputStream();
                  int iDataLength = (int)con.getLength();
                  if( iDataLength == -1 ){ //Read one char at a time
                        ByteArrayOutputStream baos = 
                              new ByteArrayOutputStream();
                        int iData;
                        while( (iData = isData.read()) != -1 )
                              baos.write(iData);
                        abData = baos.toByteArray();
                        baos.close();
                  } else { //Length is known, read all at once
                        abData = new byte[iDataLength];
                        isData.read( abData );
                  }
            }
            DataPacket dpArrived = new DataPacket_LiveChess(abData);
            mParent.zPacketArrived( dpArrived );
      }
}

The framework's view module must fit the data model to the display. Depending on the device characteristics, the controller might request different data classes. The view module needs to inform the controller which data class it wants—small, medium, or large, for instance. However, the view should work with the model in exactly the same way no matter what data class populates the model.

The server framework

Perhaps the server framework's most interesting feature is that it uses the same data-handling model and application control logic as the client. This is why you should keep the application control logic strictly separate from the communication interfaces. You can reuse these client parts with a different view implementation to render your application's WML version. In this case, the data packet simply moves around inside the server instead of being sent to the client.

Figure 4 shows the framework's server half. When a request arrives, the application gateway routes the request to the correct application according to its URL. Each application container implements a set of handlers to process requests for both WML and MIDP responses. The gateway exposes an API that lets the applications make asynchronous notifications. The gateway isolates the applications from managing the details of the external communication protocols and handles all requests consistently. For example, if requests need to be logged, the gateway can do it for all applications. The gateway also acts as a load balancer if the applications are located on separate machines or clusters.

Figure 4. In the framework's server half, the gateway acts like a switchboard to route data packages to the correct application. Note that each application supports two output renderers: one for MIDP and one for WML.
Page 2 of 2

The framework uses only one servlet as the entry point for all requests. Whether the request is a GET or a POST, the master servlet acts essentially the same: it decodes request and passes it on to the gateway. When the application returns a response, the master servlet encodes the data and attaches any necessary HTTP headers.

A notification engine is standard equipment on most application server setups, but in a wireless situation, those engines take on added importance and complexity. In wireless communications, a notification like a pager message can be an integral part of the application logic instead of an after-the-fact throwaway.

Ideally, to implement server push, you want to use SMS to notify a phone and have the MIDlet on the phone receive the message. Though simpler and more reliable than using a socket, this approach is not possible under standard MIDP. Some advanced phones, such as the Siemens SL45i, have native support along with Java extensions to do this; but, in general, a MIDlet lacks access to SMS. This might not be the case forever, so keep in mind when building your framework that SMS-enabled MIDP might become standard in the future.

Sending paging messages is also somewhat problematic. Pager service providers typically offer either a Web form or an email interface (and sometimes both) to let users send pages on their networks, but the multitude of such providers requires that a user register her pager with the application provider. Currently, no third-party products present a unified API for all paging networks.

J2EE-enabling the framework

The sample applications are not J2EE-enabled, but you can easily envision how to convert them. You could replace the explicit session ID in the sample framework with a cookie-based system and stateful session beans. Then, by giving the other application objects EJB interfaces, the sample would fit into any J2EE application server context. If you have many applications with frequently changing functionality, you might consider adding a Web services layer. This requires adding a lightweight XML parser (about 30 KB) and other components to the client, but lets the client discover services dynamically and reap other Web services benefits. The Resources section links to a whitepaper in Sun's BluePrint series on some issues surrounding J2EE for wireless application provisioning.

Implement Live Chess

The Live Chess client's job is minimal: it lets the user select a game to view and dumbly displays that game's positions. For server-oriented applications, you want to structure the client to be as thin as possible and process heavy data on the server. This seems contradictory. Usually in client/server development, you aim to offload as much processing as possible on the client; but when delivering to micro platforms like mobile phones, you want to minimize resource usage. Also, because the server runs J2SE/J2EE, it has much more powerful language capabilities than the stripped down client. These factors reverse the design logic normal in J2ME development.

Currently, MIDP 1.0 is strictly a client-initiated specification—no vendor-independent way exists for the server to push data or signal the device to begin receiving. Therefore, the Live Chess client control only implements its polling interface. You should also have a notification interface because MIDP 2.0 (also known as MIDP_NG), which entered public review in mid-April 2002, will support push similarly to how BlackBerry devices currently do: a separate thread on the device runs a listener on a well-known port to receive incoming data connections. Also, Live Chess could theoretically implement its notification interface with specific support for devices like BlackBerry.

Implement Event Alert

In the course of a clinical medical trial, anything negative and unexpected that happens to a patient is called an adverse event, and the investigating physician needs to be notified immediately if one occurs. The Event Alert application illustrates a typical adverse event system using our framework. In the example project, you implement Event Alert on top of the same framework that already delivers Live Chess. The framework's true test is whether it can implement the new application cleanly without interfering with existing applications. From Figure 4, you can see how the framework's various applications exist completely independently from the server's communications areas. The gateway unifies the external connections into one API for the applications and protects the applications from connection-related problems and error handling.

Event Alert defines the concrete class DataPacket_AdverseEvent to hold its information transfer. Because this class is derived from DataPacket, the gateway can handle it generically (see Listing 2). As far as the gateway is concerned, an Event Alert data packet does not differ from Live Chess's packet. The abstract class DataPacket defines base class methods that route data packets correctly:

Listing 2. Event Alert

import javax.servlet.*;
import java.io.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.johnchamberlain.j2meframework.DataPacket;
public class MasterServlet extends HttpServlet {
      private static final String DATA_PACKET_PARAM = "DP";
    public MasterServlet() {}
      public void init(ServletConfig config) throws ServletException {
            super.init(config);
      }
      public void destroy() {}
      public void doGet(HttpServletRequest request, 
                        HttpServletResponse response) 
                              throws ServletException, IOException {
            this.vHandleRequest(request, response);
      }
      public void doPost(HttpServletRequest request, 
                        HttpServletResponse response) 
                              throws ServletException, IOException {
            this.vHandleRequest(request, response);
      }
      public void doPut(HttpServletRequest request, 
                        HttpServletResponse response) 
                              throws ServletException, IOException {
            response.sendError (HttpServletResponse.SC_BAD_REQUEST, 
                                    "PUT not supported.");
      }
      public long getLastModified(HttpServletRequest request) { return -1; }
      void vHandleRequest(HttpServletRequest request, 
                              HttpServletResponse response){
            try {
                  String sRequestPath = request.getPathInfo();
                  String sDataPacket = request.getParameter(DATA_PACKET_PARAM);
                  Class classDataPacket = getDataPacketClass( sDataPacket );
                  Constructor con = 
                  classDataPacket.getConstructor(new Class[]{String.class});
                  DataPacket dpIn = 
                  (DataPacket)con.newInstance(new Object[]{sDataPacket});
                  ApplicationHandler app = getHandlerForURL( sRequestPath );
                  DataPacket dpOut = app.dpProcessPacket(dpIn);
                  response.setContentType("application/octet-stream");
                  response.setContentLength(dpOut.length());
                  response.getOutputStream().write(dpOut.getBytes());
                  response.getOutputStream().flush();
            } catch(Exception ex) {}
      }
      // ...
}

Unlike Live Chess, Event Alert uses the notification engine to page its users. A mobile phone might be turned off or have other availability problems. Users are also more likely to carry a pager than a phone. When a doctor needs to receive an alert, the paging system is the first line of defense. To make a paging notification, the application calls the appropriate API on the gateway, which in turn calls the appropriate module in the notification engine. Note that the notification engine will log the page—an important auditing function.

Even though Event Alert has different needs and functionality than Live Chess, it can easily live on the same servers because the framework keeps each application happy in its own sandbox.

Live! With J2ME

This article described the overall design considerations for a framework for delivering live data via a wireless device. You then saw how to implement two completely independent applications using the framework. The right design allows multiple applications to coexist peacefully and simultaneously support a range of different devices and communication protocols. Also, the framework must have a clear path for scalability.

Many developers unnecessarily restrict their delivery methods and range of supported devices to sidestep the complexity of multiplatform development. The whole idea of using Java, however, is to enable multiplatform development, which can be simple if you architect for it. For specialized applications, such as 3D games, for example, you might want to hardwire your delivery mechanism, but typical business applications that tend to use standard text controls and simple, scalable graphics can and should be supported across as many environments and transports as possible. By using a framework such as this one, you have the basis for maximum support for your J2ME systems.

John Chamberlain is a longtime consultant in the Boston area. He holds a master's degree in computer science, is a frequent contributor to technical journals, and has spoken at JavaOne. Check out his Website at http://johnchamberlain.com.

Learn more about this topic