Wednesday, October 8, 2008

HTTP GET/POST from J2ME Application

For network support, J2SE defines over 100 interfaces, classes, and exceptions between the java.io and java.net packages. J2ME defines a subset of this functionality and provides a consolidated package for networking and file access in the javax.microedition.io package. Due to the wide variety of mobile devices, this package merely defines a set of interfaces, leaving the actual implementation up to each vendor.

The abstract networking and file I/O framework defined by the javax.microedition.io classes are referred to as the Generic Connection Framework (GCF). The GCF defines a set of related abstractions to represent different communication methods. The top-level abstraction is called Connection, from which six more interfaces are declared. These seven interfaces are declared as a part of J2ME's Connected Limited Device Configuration (CLDC), which is the configuration used by most Java-enabled wireless devices. This is designed to provide common networking and file I/O capabilities for all CLDC devices.

Code snippet for sending HTTP GET request to a URL is given below.

private String SendHttpGet( String url )
{
    HttpConnection hcon = null;
    DataInputStream dis = null;
    StringBuffer responseMessage = new StringBuffer();

    try
    {
        hcon = ( HttpConnection )Connector.open( url );

        // Obtain a DataInputStream from the HttpConnection
        dis = new DataInputStream( hcon.openInputStream() );

        // Retrieve response from server
        int ch;
        while ( ( ch = dis.read() ) != -1 )
        {
            responseMessage.append( (char) ch );
        }
    }        
    finally
    {
        try
        {
            if ( hcon != null )
                hcon.close();
            if ( dis != null )
                dis.close();
        } catch ( IOException ioe )
        {
            ioe.printStackTrace();
        }
    }

    return responseMessage.toString();
}

Sample code for sending HTTP POST request to a URL is given below.

private String SendHttpPost( String url, String requestString )
{
    HttpConnection hcon = null;
    DataInputStream dis = null;
    DataOutputStream dos = null;
    StringBuffer responseMessage = new StringBuffer();

    try
    {
        // Open HttpConnection with both read and write access
        hcon = ( HttpConnection )Connector.open( url, Connector.READ_WRITE );

        // Set the request method to POST
        hcon.setRequestMethod( HttpConnection.POST );

        // Obtain DataOutputStream for sending the request string
        dos = hcon.openDataOutputStream();
        byte[] request_body = requestString.getBytes();

        // Send request string to server
        for( int i = 0; i < request_body.length; i++ )
        {
            dos.writeByte( request_body[i] );
        }

        // Obtain DataInputStream for receiving server response
        dis = new DataInputStream( hcon.openInputStream() );

        // Retrieve the response from server
        int ch;
        while( ( ch = dis.read() ) != -1 )
        {
            responseMessage.append( (char)ch );
        }
    }
    finally
    {
        try
        {
            if ( hcon != null )
                hcon.close();
            if ( dis != null )
                dis.close();
            if ( dos != null )
                dos.close();
        }
        catch ( IOException ioe )
        {
            ioe.printStackTrace();
        }
    }

    return responseMessage.toString();
}

RecordStore management in J2ME Applications

The Record Management System (RMS) provides a mechanism through which MIDlets can persistently store data and retrieve it later. In a record-oriented approach, J2ME RMS comprises multiple record stores. Each record store can be visualized as a collection of records, which will remain persistent across multiple invocations of the MIDlet. The device platform is responsible for maintaining the integrity of the record stores.

A record store is created in platform-dependent locations, like nonvolatile device memory, which are not directly exposed to the MIDlets. The RMS classes call into the platform-specific native code to perform the actual database operations. Record store implementations ensure that all individual record store operations are atomic, synchronous, and serialized, so no corruption of data will occur with multiple accesses. The record store is timestamped to denote the last time it was modified. It also maintains a version, which is an integer that is incremented for each operation that modifies the contents of the record store. Versions and timestamps are useful for synchronization purposes.

The javax.microedition.rms.RecordStore class represents a RMS record store. Each record in a record store is an array of bytes and has a unique integer identifier. The openRecordStore() is used to open a record store. The record store should be opened to perform any operation in it. Sample code for opening the store is given below.

RecordStore store = RecordStore.openRecordStore(recordStoreName, true );

The second parameter determines whether to create the record store, if it does not exist.

Once all operations are done, the closeRecordStore() is called to close the record store. Also, the deleteRecordStore() can be used to delete the record store. This function accepts the name of the store as the parameter.

Sample code for inserting a record to the record store is given below:

public void createRecord(String data) throws RecordStoreException,
                                            RecordStoreFullException,
                                            RecordStoreNotFoundException,
                                            IOException

{
    ByteArrayOutputStream bout = null;
    DataOutputStream dout = null;
    RecordStore store = null;

    try
    {
        store = RecordStore.openRecordStore("RECORD_STORE_NAME", true );

        bout = new ByteArrayOutputStream();
        dout = new DataOutputStream( bout );

        dout.writeUTF( data );
        dout.flush();

        int numBytes = bout.size();
        byte[] record = bout.getByteArray();

        store.addRecord(record, 0, numBytes);
    }
    finally
    {
        try
        {
            if ( bout != null)
                bout.close();
            if (dout != null)
                dout.close();
            if (store != null)
                store.closeRecordStore();
        }
        catch (Exception ex)
        {
            /* Do Nothing */
        }
    }
}

Sample function for reading a record from the record store is given below. Each record in the record store is uniquely identified by a record number. So the calling function should have the logic for determining the record id.

public String getRecord(int recordID) throws RecordStoreNotOpenException,
                                                                    InvalidRecordIDException,
                                                                    RecordStoreException,
                                                                    IOException,
                                                                    Exception
{
    ByteArrayInputStream bin = null;
    DataInputStream din = null;
    RecordStore store = null;
    String record = "";

    try
    {
        store = RecordStore.openRecordStore("RECORD_STORE_NAME", false );

        byte[] rawData = store.getRecord(recordId);

        bin = new ByteArrayInputStream( rawData );
        din = new DataInputStream( bin );

        record = din.readUTF();
    }
    finally
    {
        try
        {
            if (bin != null)
                bin.close();
            if (din != null)
                din.close();
            if (store != null)
                store.closeRecordStore();
        }
        catch (Exception ex)
        {
            /* Do Nothing */
        }
    }

    return record;
}

Updating a particular record involves getting a handle for that record and setting new information. The setRecord() is used to update the record, which accepts the record id as one of its parameters. Similarly, the deleteRecord() is used to delete a record from the record store. This function also accepts the record id as the parameter.

One point to be noted is; since the memory available in mobile devices is limited, there is strict limitation for storage. The application should have proper logic for cleanup and data management, wherever necessary.

Send/Receive SMS from MIDlet application

The javax.wireless.messaging has good support for sending/receiving SMS from a MIDlet application. Text or binary messages can be prepared and send using a few lines of code. Sample class for sending the SMS is given below.

class SMSSender implements Runnable
{
    String destinationAddress;
    String smsPort;
    String message;

    SMSSender(String address, String port, String msg)
    {
        this.destinationAddress = address;
        this.smsPort = port;
        this.message = msg;
    }

    public void run()
    {
        //String address = "sms://" + destinationAddress + ":" + smsPort;
        String address = "sms://" + destinationAddress;

        MessageConnection smsconn = null;

        try
        {
            /** Open the message connection. */
            smsconn = (MessageConnection)Connector.open(address);

            TextMessage txtmessage =
                (TextMessage)smsconn.newMessage(MessageConnection.TEXT_MESSAGE);
            txtmessage.setAddress(address);
            txtmessage.setPayloadText(message);

            smsconn.send(txtmessage);
        }
        catch (Throwable t)
        {
            t.printStackTrace();
        }
        finally
        {
            if (smsconn != null)
            {
                try
                {
                    smsconn.close();
                } catch (IOException ioe)
                {
                    ioe.printStackTrace();
                }
            }
        }
    }
}

For this code to work, the following packages should be imported to the MIDlet application.

import javax.microedition.io.Connector;
import javax.wireless.messaging.MessageConnection;
import javax.wireless.messaging.TextMessage;

Next, the MIDlet should have permission to send/receive SMS. This permission is granted by adding the required entries to the MIDlet-Permissions attribute in the JAD file. Sample JAD file entry is given below.

MIDlet-Permissions: javax.microedition.io.Connector.sms, javax.wireless.messaging.sms.send, javax.wireless.messaging.mms.receive

In a NetBeans project, this can be done easily from the Project Properties -> Application Descriptor. The required attribute can be added using the GUI option, as shown.

               NetBeans-J2ME-AppDescriptor

For the SMS functionality to work, the device configuration used should be CDLC-1.1 or higher and the device profile should be MIDP-2.0 or higher. In a NetBeans project, this can be set easily from Project Properties -> Platform.

Couple of important points. 1) The address can be of the form "sms://address:port". But this will not work in all devices. 2) Since the sending function works asynchronously, the code for sending SMS must be executed in a separate thread.

Receiving SMS is not as straight forward as sending it. First of all, the port number should be specified for accepting the message. While sending an SMS, it is necessary to specify the port number. The message will be delivered to the default inbox of the target device. For receiving, since the port number is to be specified, only messages arriving at that port can be processed by the MIDlet. This is because of security reasons - MIDlets are not allowed to access the default inbox. (if this is allowed, an application written with wrong intention can scan the user's inbox and it will be possible to extract personal information easily). One major problem here is; many devices will not support specifying port number for sending messages. Sample code for receiving SMS is given below.

class SMSReceiver implements Runnable, MessageListener
{
    String portNumber = "";
    MessageConnection conn = null;
    boolean finished = false;

    SMSReceiver(String portNo)
    {
        portNumber = portNo;
    }

    public void run()
    {
        if (conn == null)
        {
            try
            {
                conn = (MessageConnection)Connector.open("sms://:" + portNumber);
                conn.setMessageListener(this);
            }
            catch (IOException ex)
            {
                ex.printStackTrace();
            }
        }

        try
        {
            while (!finished)
            {
                Message msg = conn.receive();
                if ( msg != null )
                {
                    if (msg instanceof TextMessage)
                    {
                        this.processMessage(msg);
                    }
                    else if (msg instanceof BinaryMessage)
                    {
                        /* Binary message */
                    }
                }
            }
        }
        catch (Exception ex)
        {
            ex.printStackTrace();
        }
    }

    public void notifyIncomingMessage(MessageConnection conn)
    {
        /* Add code for notification handling */
    }

    public void stopListening()
    {
        finished = true;
    }

    private void processMessage(Message msg)
    {
        /* Add message processing code */
    }
}

When new message arrive at the listening port, notifyIncomingMessage() will be invoked automatically. receive() is a blocking call and so, code for listening for message should be written in a separate thread.

One last point; when this application is executed in the simulator, it will ask permission to use text message functionality.

                                                             Confirmation

This is because the code is in untrusted domain, by default. To avoid this confirmation message, code should be signed with a valid certificate. This process will create the necessary entries in the JAD file and the application will be able to use text message service, without the need of user intervention. Some of the devices like Nokia 6600 will accept unsigned applications, but this is not the case with many other device manufacturers. In the simulator, it will be possible to switch to the trusted or manufacturer domain. But to sign the code for the real device, we need to get the signature from a service provider such as VeriSign or Thawte.