Device Programming with MIDP, Part 3

Device Programming with MIDP, Part 3

Use MIDP's Communication APIs to interact with external systems

Summary
In the final part of this MIDP series, Michael explores the methods of communication between the MIDlet and the world at large. Using the APIs contained in Java 2, Micro Edition's Mobile Information Device Profile (MIDP), developers can interact with external systems. This article will explore these APIs in a detailed example, which demonstrates the interaction that can exist between a J2ME device and a servlet-based Web system. (3,000 words)
By Michael Cymerman In the first two parts of this three-part series, I examined the features and functionality of the Mobile Information Device Profile (MIDP) APIs as they relate to operations and information stored on the device itself. In this final part, I'll focus on the interaction of a networked device with the network at large.

Networked devices can use numerous protocols to communicate with each other. In this article, I'll focus on the HttpConnection interface that you can use to access information stored on a Web server. Preceding the article's example, I will discuss the interactions of the javax.microedition.io interfaces and classes. The example will concentrate on the interaction between a MIDP device and a JSP-based system.

Connection hierarchy
All interfaces in the javax.microedition.io package are based on the Connection interface. Other connection interfaces inherit the method contained in Connection and define methods used for accessing relevant variables and actions of that Connection type. I will discuss the more common interfaces in this article; the other interfaces are left to the reader for investigation.

HTTPConnection explained
The HttpConnection interface provides other methods on top of the Connection interface to enable HTTP interaction. Here is a list of some of those useful methods:

  • String getHeaderField(int index)
  • String getHeaderField(String name)
  • long getHeaderFieldDate(String name, long def)
  • int getHeaderFieldInt(String name, int def)
  • String getHeaderFieldKey(int n)
  • String getHost()
  • long getLastModified()
  • int getPort()
  • String getProtocol()
  • String getQuery()
  • String getRef()
  • String getRequestMethod()
  • String getRequestProperty(String key)
  • int getResponseCode()
  • String getResponseMessage()
  • String getURL()
  • void setRequestMethod(String method)
  • void setRequestProperty(String key, String value)

These methods let you access the HTTP fields just as you would in a servlet-based system.

The other interfaces are well documented in the API specification. Those interfaces will contain methods for sending datagram packets and streaming data to and from the device using a variety of protocols. I will not examine those in detail because while they differ in exact method signature, they are obtained in an identical fashion.

The Connector object
How does the MIDP API figure out which interface to create and return to the calling class? The answer is that the proper Connector is returned based upon the value passed in the connection string.

The following connection string alerts the Connector object that the system is looking for an HttpConnection:

HttpConnection httpConn = Connector.open("http://www.itpath.com");

The MIDP Connector object parses through the connection string and determines that it is a URL for accessing a Webpage. An implementation of the HttpConnection interface is returned to the calling class.

Other connection protocols need different connection strings. Consult Table 1 for a listing of the connection strings for various protocols:

Table 1. Connection Strings for Various Protocols
Protocol Connection String
Http http://www.yahoo.com
Stream-based Socket Socket://localhost:6160
Datagram-based Socket - listening datagram://:6160
Datagram-based Socket - sending datagram://121.232.121.232:6160
Serial Port comm.:0;baudrate=5000
File file://helloWorld.txt

Example: Tying it all together
The following example is intended to tie the concepts discussed in this article to those in the rest of the series. In this example, the MIDlet accesses information stored on a remote system. That information is returned in the form of an XML message to the MIDlet. By parsing that XML, the MIDlet then constructs a user interface based upon the data.

The user interface consists of a question posed to the user. The response causes a request to be made to the server to add data to the survey. The updated data is then returned to the user.


VoterMidlet
The VoterMidlet class is the only MIDlet in this example. Upon loading, it creates an instance of the VoteResults object:

public class VoterMidlet extends MIDlet implements ScreenCallback
{
    private Display _display;
    // midlet has three screens
    private VoteResults voteResults = new VoteResults( (ScreenCallback) this);

    public VoterMidlet()
    {
        _display = Display.getDisplay(this);
        _display.setCurrent(voteResults);
    }

    public void exit()
    {
       try
       {
          this.destroyApp(true);
       } catch (MIDletStateChangeException e)
       {}
    }
...
      }

ScreenCallback
As shown above, the VoterMidlet implements the ScreenCallback interface. This interface is used to mask certain events from the UI classes that may need a reference to the MIDlet. The ScreenCallback interface contains a single method public void exit(), which the UI screen uses to alert the MIDlet that the user has pressed the "Exit" button.

By coding to the ScreenCallback interface, the other MIDlet methods are hidden from the UI screen developers. This is important because some MIDlet methods have a damaging effect on the program if used incorrectly.

VoteResults
The VoteResults class is a user interface class that displays the voting results to the user. To keep the example simple, this class implements two of the Model-View-Controller classes: View and Controller.

The Constructor takes the ScreenCallback interface as its only argument from the MIDlet. As described above, that interface gives the Screen the ability to call back to the MIDlet for certain methods.

The Constructor is used to initialize the object and create the user interface for the example. The lines that are of particular interest here are the following:

      VoteSummary voteSummary = ResourceUtility.getVoteSummary();

      initialize(voteSummary);

Those lines are responsible for the initial communication between the MIDlet and the JSP page that simulates a real-world system. The ResourceUtility accesses a URL via the HTTP parameter and retrieves information from the JSP. It uses that information to create a VoteSummary object. This interface will be further examined as we proceed with the example.

The initialize() method is then called to create the UI display consisting of two StringItems displaying the prior voting results and a ChoiceGroup containing all the possible votes and the number of votes for that category.

   public void initialize(VoteSummary voteSummary)
   {
      append( getNumVotesString( voteSummary.getNumVotes() )) ;
      append( getAvgVoteString( voteSummary.getAvgVote() ));
      append( showVoteResults( voteSummary.getVotes() ));
   }

Upon user input to the display, the commandAction() method is executed. That method accepts input from the device. If the command entered is equal to "Vote", the currently selected item is retrieved from the ChoiceGroup. A Vote object is constructed and passed to the ResourceUtility.addEntry() method. That method passes information to the JSP, which then adds the new vote to the tally and returns an updated VoteSummary object. It then calls the update() method:

   public void commandAction(Command c, Displayable s)
   {
      String command = c.getLabel();

      if ( command.equals("Exit") )
      {
         _screenCallback.exit();
      }
      else if ( command.equals("Vote") )
      {
         // get the selected item
         int selectedIndex = _voteResults.getSelectedIndex();

         Vote newVote = new Vote(""+ selectedIndex, null, null);

         VoteSummary voteSummary = ResourceUtility.addEntry( newVote);

         update( voteSummary );
     }
  }

VoteSummary
The VoteSummary object contains information about the current voting status. It keeps track of the total number of votes, the average vote, and also maintains a Vector of the votes themselves (rolled-up for each choice):

   public VoteSummary(String numVotes, String avgVote, Vector votes)
   {
      _numVotes = numVotes;
      _avgVote = avgVote;
      _votes = votes;
   }

Based upon the XML that is passed from the JSP to the MIDlet, the VoteSummary object is created.

ResourceUtility
The ResourceUtility class obtains the XML from the remote server. It uses an HttpConnection to request the XML from the JSP and then interfaces with the XMLUtil to convert the XML into a VoteSummary object:

    public static VoteSummary getVoteSummary()
    {
        String xml = loadVoteResults();

        return convertXMLToVoteSummary( xml);
    }

The getVoteSummary() method shown above accesses this object. The method in turn calls the loadVoteResults() method and the convertXMLToVoteSummary() method. The loadVoteResults() method, shown below, wraps the backendComms() method that is also used for adding votes to the backend system:

    public static String loadVoteResults()
    {
        return backendComms(LOAD_URL, "");
    }
    private static String backendComms(String requestURL, String requeststring)
    {
        HttpConnection httpConnection = null;
        DataInputStream dataInputStream = null;
        StringBuffer messagebuffer = new StringBuffer();

        String requestString = requestURL + requeststring;

        Try
        {
            // Open an HTTP connection with the Web server
            httpConnection = (HttpConnection)
                  Connector.open(requestString, Connector.READ_WRITE);

            // Set the request method to GET
            httpConnection.setRequestMethod(HttpConnection.GET);

            // Retrieve the response back from the servlet
            dataInputStream =
               new DataInputStream(httpConnection.openInputStream());

            int inputChar;

            // Check the Content-Length first
            long contentLength = httpConnection.getLength();

            if(contentLength!=-1)
            {
                for(int i = 0;I<contentLength;i++)
                {
                    if((inputChar = dataInputStream.read())!= -1)
                    {
                        messagebuffer.append((char)inputChar);
                    }
                }
            } else {
                // if the content-length is not available
                while ((inputChar = dataInputStream.read()) != -1)
                {
??? from here until ???END lines may have been inserted/deleted                     messagebuffer.append((char) inputChar);
                }
            }
            dataInputStream.close();

        } catch (IOException ioe) {
            messagebuffer = new StringBuffer("ERROR!");
        } catch (Exception e){
            e.printStackTrace();
        } finally {
            try {
                if (httpConnection != null)
                   httpConnection.close();
            } catch (IOException ignored) {}
            try {
                if (dataInputStream != null)
                   dataInputStream.close();
            } catch (IOException ignored) {}
        }
        return messagebuffer.toString();
    }

Let's step through the backendComms() method and examine it more closely. We'll start with the try-catch block. The first step is to open a read/write HttpConnection to the server. As I discussed earlier, that method obtains the XML and writes requests to the server, so we need to use a READ_WRITE connection:

        try
        {
            // Open an HTTP connection with the Web server
            httpConnection = (HttpConnection)
                  Connector.open(requestString, Connector.READ_WRITE);

            // Set the request method to GET
            httpConnection.setRequestMethod(HttpConnection.GET);

            // Retrieve the response back from the JSP
            dataInputStream =
                  new DataInputStream(httpConnection.openInputStream());

To obtain the HttpConnection implementation, we call the Connector.open() method. That class contains static methods that are used to generate the appropriate interface for communications based upon the connection string. In this case, we want an HttpConnection, so we cast the response from the Connector.open() to the HttpConnection interface.

Once we make the request, we can use the openInputStream() method from the HttpConnection object to retrieve the InputStream of results from the JSP:

            if(contentLength!=-1)
            {
                for(int i = 0;I<contentLength;i++)
                {
                    if((inputChar = dataInputStream.read())!= -1)
                    {
                        messagebuffer.append((char)inputChar);
                    }
                }
            } else {
                // if the content-length is not available
                while ((inputChar = dataInputStream.read()) != -1)
                {
                    messagebuffer.append((char) inputChar);
                }
            }

Attempt to read the content length of the response, or if not found, just read until you find the EOF or an error character. Each character read from the DataInputStream is appended to the StringBuffer. The StringBuffer, which contains an XML vote representation, is then passed back to the calling method and in turn passed to the convertXMLToVoteSummary() method, shown below:

    private static VoteSummary convertXMLToVoteSummary(String xml)
    {
        InputStreamReader insr = new InputStreamReader(
           new ByteArrayInputStream(xml.getBytes() ) );

        VoteSummary voteSummary = null;

        Try
        {
            voteSummary = XMLUtil.getVoteResults(insr);

        } catch (IOException ioe)
        {}

        return voteSummary;
    }

That method is used to transform the XML string into a live Java object so that we can use methods to retrieve, rather than parse, data. We use the XMLUtil.getVoteResults() method to do this conversion. Before we get to that method, let's finish with the ResourceUtility object. The addEntry() method, the last method, passes the new vote to the JSP. As I mentioned earlier, it reuses the backendComms() method for efficiency:

    public static VoteSummary addEntry(Vote vote)
    {
        StringBuffer requestString = new StringBuffer();

        requestString.append(QUESTION + vote.toRequestString() );

        String xml = backendComms(LOAD_URL, requestString.toString() );

        return convertXMLToVoteSummary(xml);
    }

XMLUtil
The XMLUtil object has the sole purpose of converting the XML retrieved from the JSP to a VoteSummary object that the application can use. To perform the conversion, it must parse through the data using an xml parser. It's important to use a lightweight parser to minimize the MIDlet's overall size. For this example, I used Enhydra's kxml parser.

We'll step through the getVoteResults() method in this object to follow the conversion process:

   public static VoteSummary getVoteResults(InputStreamReader insr)
      throws IOException
   {
      XmlParser parser = new XmlParser (insr);

      Document document = new Document();
      document.parse( parser );

      //uncomment to see the document written to your console.
      //document.write( new XmlWriter( new OutputStreamWriter( System.out) ) );

The first few lines are used to setup the XmlParser and Document objects. As shown, once the objects are created, the Document.parse() method is called passing the XmlParser as an argument. The resulting document contains an object representation of the XML message passed from the JSP. If you're interested in verifying the message format, the document.write call that was commented out earlier can be uncommented to reveal the document's structure:

      Element voteExampleElement = document.getElement("vote-example");
      Element voteElement = voteExampleElement.getElement("votes");

      String numVotes = getTextFromElement(voteElement, "number");
      String avgVote = getTextFromElement(voteElement, "average");

Having created the Document representation, we can now use DOM methods to obtain the Element objects and retrieve the values from within. The first two lines show the process of stepping through the XML document in an effort to reach the node with the data of interest. Having obtained the voteElement node, we now use a helper method (getTextFromElement()) described below. This helper performs multiple commands to retrieve the Text node from the document and return its value:

      Element choicesElement = voteElement.getElement("choices");
      int childCount = choicesElement.getChildCount();

The two preceding lines are used to obtain the count of nodes present under the choices section. Using childCount, you can construct the code so that it isn't dependent on the current number of votes. This is important because in the future you may need to add more votes to the display. Using these calls, you can do that without modifying the code:

      for (int i = 0; i < childCount; i++)
      {
         if ( choicesElement.getType(i) == Xml.ELEMENT)
         {
            choiceElement = choicesElement.getElement(i);

            choiceValue = getTextFromElement(choiceElement,"value");
            choiceName = getTextFromElement(choiceElement,"name");
            choiceVotes = getTextFromElement(choiceElement,"number");

            vote = new Vote(choiceValue, choiceName, choiceVotes);

            vEntries.addElement(vote);
         }
      }

      return new VoteSummary( numVotes, avgVote, vEntries);

Having obtained the count of the child elements as discussed above, we can loop over them and pull out the vote information. Some of the child nodes are Elements while others are Text nodes containing no data. In our example, we only care about the Elements, so we need that logic within the loop itself. This loop will obtain each of the child Elements from the choices Element and construct a Vote object containing the value (used for calculations), the name, and the quantity of the votes cast for a given choice. This vote is subsequently added to the Vector containing the votes. Finally, this method instantiates a new VoteSummary object containing the three pieces of information that are to be displayed to the user:

private static String getTextFromElement(Element elementRoot,
      String elementName)
{
      String returnText = elementRoot.getElement(elementName).getText();

      return returnText;
      }

The getTextFromElement() method is the helper method referred to in the example. We used this method to make the code more readable. It simply descends the tree one level and obtains the Text node contained within.

voter.html
The last element in our system is the JSP that is simulating the backend system. In this example, I have mocked up a simple JSP that uses class variables to keep track of the vote tallies.

Please review VoterMidlet.zip in Resources to understand the JSP. It simply keeps track of the votes and returns an XML representation of the data.

Final notes: Example
This example demonstrates the interaction between the MIDP and a server-side component using the HttpConnection. By making a request, the MIDP can communicate with the JSP component through normal HTTP GET/POST calls. In this example, the return to the call was an XML message.

Using the kXML parser, the XML is efficiently converted into a live object from which the graphical classes can obtain data. If you notice, nowhere in the application is it specified that there are five choices that should be in the ChoiceGroup. That is configured dynamically based upon the server-side component.

The interesting challenge in coding this example is, as with any multitiered development task, how to efficiently implement the Model-View-Controller pattern without creating redundant code. In this example, the model is the JSP, as it would likely be in a real system. The view is the VoteResults object that is responsible for displaying any information contained in the data. The controller is, in this case, also the VoteResults object because it implements the CommandListener interface. Final notes: Communications
In this article, we've focused on a real-world application of the MIDP communications system for making HTTP requests. This profile can make any number of requests from known ports using standard protocols to any application-specific protocols using proprietary protocols. The API is sufficiently flexible to handle these cases in a relatively similar manner.

An interesting application of the interaction with remote machines occurs if you consider the loss of coverage due to entry into a tunnel or subway. Unlike its WAP cousins, the MIDP-enabled phone will be able to continue running its applications without coverage. Applications taking advantage of this technology will provide the user with offline functionality and the ability to synchronize with the online application upon return to coverage. Consider a MIDP-based email application: The user could continue to read and respond to mails already downloaded into the local RMS datastore (see Part 2). Upon return to coverage, the reply messages could be sent to the server for distribution to their respective recipients.

The currently released version of the J2ME Wireless Toolkit does not support the HTTPS protocol. However, the early access release available from the developer connection does have HTTPS support. With this support in the APIs, you can implement applications for trading stock or making purchases from your Java phone.

Final notes: Series
This three-part series was intended to introduce readers to the J2ME profile MIDP. As with all new Java implementations, it will take some time for the APIs to mature. The series attempted to provide a summary of major classes and aspects of MIDP. However, there is much that was left to the reader for discovery.

Part 1 introduced the reader to the J2MEWTK, discussed the basics of MIDP configuration files, and examined graphical applications.

In Part 2, I examined MIDP's local storage capabilities in detail, using a text-based example. The data storage capabilities are an important part of the MIDP APIs as they let you enable the offline aspects of the MIDP protocol.

This Part 3 discusses MIDP's ability to access remote servers. A device that cannot access external networks for data is not worthwhile, so these APIs are also important for development. From this series, you should feel somewhat comfortable creating MIDP applications.

Within a short timeframe, the Java-enabled devices will become more prevalent and the over-the-air activation will become more standard across platforms. When this happens, the development in the J2ME profile will really take off.