saush

J2ME and Rails

Posted in java, Mobile, Rails, Ruby by sausheong on February 21, 2007

I suspect most people who does mobile Java and Rails (and I know a lot of people does) at one point of time or another would have had a light bulb light up in his mind — Action Web Service and J2ME Web Services (WSA) !

For those whose light bulbs are about to light up, I’m sad to prick that particular bubble — no, it doesn’t work like you imagine it to be. The mine-field seems cluttered for now and I haven’t explored most of the issues working with WSA and action web service (AWS), but one major stumbling block is that AWS produces an WSDL with an RPC/encoded binding style (which is, by the way, not WS-I (Web Services Interoperability) compliant) while WSA specifics categorically that it works with a document/literal binding style.

For those unfamiliar with WSDL binding styles, here’s a quick 10 second primer. A WSDL (Web Service Description Language) document describes a Web service. A WSDL binding describes how the service is bound to a messaging protocol, particularly the SOAP messaging protocol. A WSDL SOAP binding can be either a Remote Procedure Call (RPC) style binding or a document style binding. A SOAP binding can also have an encoded use or a literal use. This gives you four style/use models:

1. RPC/encoded
2. RPC/literal
3. Document/encoded
4. Document/literal

There is also a commonly used binding pattern (commonly used by Microsoft) called the Document/literal wrapped pattern. You can find more information from this article which explanations I loosely copied from.

But all is not lost. If you’re looking for a quick and relatively painless way of move chunks of data from an Rails application to your Java mobile phone application, there is another way. For those who are above ‘workaround’ solutions such as this, read no further! But for those who will happily do what it takes to make the damn thing work, read on …

First of all let’s define what we really want. What we really want is to move large chunks of data from the server application to the mobile phone application (moving data from the mobile phone application to the server is another problem solved in another post). Rails on the other hand is very good at creating web applications which main display output is in a browser readable form of HTML. What if we output XML instead of HTML? We can easily do this using Jim Weirich’s Builder library (using the rxml templates).

To show this, I’m going to create a simple Rails app that allows someone to add in the name and mobile phone number, and display an XML of the total list of people from a REST-like interface.

1. Create a Rails app called UserInfo

rails UserInfo

2. Create the database schema userinfo, and change config/database.yml to connect to it. (this quick sample assumes you’ll be using MySQL)

3. Create the single database table we’ll be using with these:

CREATE TABLE  userinfo.users (
  id int(10) unsigned NOT NULL auto_increment,
  name varchar(45) NOT NULL default '',
  mobile int(10) unsigned NOT NULL default '0',
  PRIMARY KEY  (id)
);

4. Create a scaffold to quickly enter user information

ruby script/generate scaffold User

5. Start the WEBrick server

ruby script/server

6. Go to http://localhost:3000/users and enter some data

7. Add this method in userinfo/app/controllers/users_controller.rb

def info
  @headers["Content-Type"] = "text/xml"
  @users = User.find_all
  render :layout => false
end

8. Create a file called info.rxml in the userinfo/app/views directory with this:

xml.data do
  xml.people do
    for user in @users do
      xml.person(:id => user.id) do
        xml.name(user.name)
        xml.mobile(user.mobile)
      end
    end
  end
end

9. Go to http://localhost:3000/users/info. You should get something like this:

<data>
  <people>
    <person id="1">
      <name>Sau Sheong</name>
      <mobile>123456789</mobile>
    </person>
    <person id="2">
      <name>Robert</name>
      <mobile>987654321</mobile>
    </person>
  </people>
</data>

On the J2ME application in the mobile phone, we don’t really need WSA or JAX-RPC, what we want is a simple way to get and parse the large chunks of data. MIDP already has a way of communicating with servers through the MIDP IO classes (javax.microedition.io.*) so communications is well established. For parsing XML data I used the kXML parser, which is small and convenient. The good thing about kXML is that it is directly usable and you don’t need to wait till the mobile phone manufacturer decides to include WSA into its mobile phones before using it, so it is much more portable.

Creating the midlet on the mobile phone is a bit more trickier. Without elaborating on the details of creating a midlet (this is a good starting tutorial), I’m going to just plonk in the code. You’ll probably need to download the Java ME toolkit or NetBeans 5.5 and the NetBeans Mobility Pack to compile this properly. Again, I won’t go into the details.

For those who are familiar with J2ME already, this is the additional class I created to parse the XML documents.

/*
 * DataGopher.java
 *
 * Created on February 15, 2007
 */

package com.saush.cardinfo;

import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import javax.microedition.io.Connector;
import javax.microedition.io.HttpConnection;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Form;
import javax.microedition.lcdui.StringItem;
import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParserException;

/**
 *
 * @author Chang Sau Sheong
 */
public abstract class DataGopher extends Form implements Runnable {

    private String url;

    private StringItem message;
    private String okMessage;
    private String failMessage;
    private String doneMessage;
    private Command doneCommand;
    private CommandListener cmdListener;

    DataGopher() {
        super("DataGopher");
    }

    DataGopher( String title, String okMsg, String failMsg, Command done, CommandListener listener, String url){
        super( title );
        okMessage = okMsg;
        failMessage = failMsg;
        doneCommand = done;
        cmdListener = listener;
        this.url = url;
    }

    /**
     * Displays a line of text on the form
     */
    void display( String text ){
        if( message == null ){
            message = new StringItem( null, text );
            append( message );
        } else {
            message.setText( text );
        }
    }

    /**
     * Show completion of processing
     */
    void done( String msg ){
        display( msg != null ? msg : doneMessage );
        addCommand( doneCommand );
        setCommandListener( cmdListener );
    }

    /**
     * Starting the thread
     */
    public void run(){
        try {
            InputStream is = null;
            HttpConnection c = null;
            try {
                c = (HttpConnection)Connector.open(url);
                is = c.openInputStream();
                KXmlParser parser = new KXmlParser();
                parser.setInput(new InputStreamReader(is));
                readXMLData(parser);
            } finally {
                is.close();
                c.close();
            }
            done(null);
        } catch (IOException ex) {
            done(failMessage + " : " + ex.getMessage());
        } catch (Exception ex) {
            done(failMessage + " : " + ex.getMessage());
        }

    }

    void start(){
        display( "Getting data, please wait ..." );
        Thread t = new Thread( this );
        try {
            t.start();
        } catch( Exception ex ){
            done(failMessage + " : " + ex.getMessage());
        }
    }

    protected abstract void readXMLData(KXmlParser parser) throws IOException, XmlPullParserException;

}

This is an abstract class. I abstracted the actual parsing work (kXML is pretty tedious) so you’ll need to extend it properly. Here is the example that parses the XML above.

/*
 * ReadPeople.java
 *
 * Created on February 15, 2007
 */

package com.saush.cardinfo;

import java.io.IOException;
import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import org.kxml2.io.KXmlParser;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;

/**
 *
 * @author Chang Sau Sheong
 */
public     class ReadPeople extends DataGopher {

        ReadPeople( String title, String okMsg, String failMsg, Command done, CommandListener listener, String url){
            super(title, okMsg, failMsg, done, listener, url);
        }

        protected void readXMLData(KXmlParser parser) throws IOException, XmlPullParserException {
            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, "data");
            parser.nextTag();

            parser.require(XmlPullParser.START_TAG, null, "people");
            while (parser.nextTag() != XmlPullParser.END_TAG)
                parse_people(parser);
            parser.require(XmlPullParser.END_TAG, null, "people");

            parser.nextTag();
            parser.require(XmlPullParser.END_TAG, null, "data");
        }

        private void parse_people(KXmlParser parser) throws IOException, XmlPullParserException {
            String text;
            String id;
            parser.require(XmlPullParser.START_TAG, null, "person");
            id = parser.getAttributeValue(0);
            append("id:" + id + "\\n");
            parser.nextTag();

            parser.require(XmlPullParser.START_TAG, null, "name");
            text = parser.nextText();
            append("name:" + text + "\\n");
            parser.require(XmlPullParser.END_TAG, null, "name");

            parser.nextTag();
            parser.require(XmlPullParser.START_TAG, null, "mobile");
            text = parser.nextText();
            append("mobile:" + text + "\\n");
            parser.require(XmlPullParser.END_TAG, null, "mobile");

            parser.nextTag();
            parser.require(XmlPullParser.END_TAG, null, "person");

            append("\\n");
        }

    }

Finally you’ll need a midlet to call these classes.

/*
 * DataMidlet.java
 *
 * Created on February 15, 2007
 */

package com.saush.cardinfo;

import javax.microedition.lcdui.Command;
import javax.microedition.lcdui.CommandListener;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Displayable;
import javax.microedition.lcdui.Form;
import javax.microedition.midlet.MIDlet;

/**
 *
 * @author  Chang Sau Sheong
 */
public class DataMidlet extends MIDlet implements CommandListener {

    // Commands
    private Command CMD_exit;
    private Command CMD_ok;
    private Command CMD_done;

    // display elements
    private Form FORM_data;

    // others
    private ReadPeople FORM_read;

    public DataMidlet() {
        // initialise the commands
        CMD_exit = new Command("Exit", Command.EXIT, 3);
        CMD_ok = new Command("Ok", Command.OK, 2);
        CMD_done = new Command("Done", Command.OK, 2);

        // initialise the display elements
        FORM_data = getDataForm();
    }

    public void startApp() {
        Display.getDisplay(this).setCurrent(FORM_data);
    }

    public void pauseApp() {
    }

    public void destroyApp(boolean unconditional) {
    }

    public void commandAction(Command command, Displayable displayable) {

        //  FORM_data
        // ----------
        if (displayable == FORM_data) {
            if (command == CMD_exit) {
                exit();
            }
            else if (command == CMD_ok) {
                FORM_read = new ReadPeople("Read data", "Data read", "Read data failed",
                        CMD_done, this, "http://localhost:3001/users/info");
                FORM_read.start();
                Display.getDisplay(this).setCurrent( FORM_read );
            }
        }
        else if (displayable == FORM_read) {
            if (command == CMD_done) {
                 Display.getDisplay(this).setCurrent( FORM_data );
            }
        }
    }

    // private methods here

    public void exit() {
        Display.getDisplay(this).setCurrent(null);
        destroyApp(true);
        notifyDestroyed();
    }

    private Form getDataForm() {
        if (FORM_data == null) {
            FORM_data = new Form("Get data by clicking on the OK command");
            FORM_data.append("Click on the OK command to get the data from the remote site");
            FORM_data.addCommand(CMD_ok);
            FORM_data.addCommand(CMD_exit);
            FORM_data.setCommandListener(this);
        }

        return FORM_data;
    }

}

Here are some screenshots to show what happens. This uses Nokia’s Series 40 SDK, 3rd edition.

01.png

Click on OK to start the network access and read from the server.

02.png
You’ll be asked a security question. Click OK to continue.

03.png
Your Java mobile phone application just read the data from the server!

You’ll notice that this doesn’t really work unless you know what you’re receiving at the mobile phone end. This is not really a web service, it’s more like a REST-like interface request. I’ve avoided security questions as well, as I tried to focus on the part on data transfer.

What I’ve tried to show here is not how something that replaces web services on J2ME mobile phones, but rather a possible alternate solution if you’re just looking at moving data from the mobile phone to the server (I’ll talk about the reverse in another post). Web services has quite a lot of features, a lot of it very useful and mostly imposed due to standardization and generalization. However if you’re looking for a quick solution to a simple problem, then this might suit you more.

About these ads

One Response

Subscribe to comments with RSS.

  1. Ro said, on November 9, 2007 at 4:06 pm

    Hello,

    Can you please post the file structure for this app. or tell where to put the ‘org’ folder.

    I am doing something exactly similar, but get a package not found error. I just wanted to make sure that I am putting the ‘org’ folder in the right place.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 446 other followers

%d bloggers like this: