Sunday, January 9, 2011

Servlet-GSON vs JSONME-JavaME III

This is the third post about JSON and JavaME. In previous releases: first we introduced JSON for JavaMe (J2ME), then we improved the example by adding compression/decompression support. Now, we are going to introduce Base64 format support for sending images in our JSON example.
Although this is not a best practice, because you should send the image's URL  instead of sending the image itself in the JSON String, sometimes you just need to do exactly that.

Requirements:

Servlet-GSON vs JSONME-JavaME: Contains the original classes. More info:
http://www.java-n-me.com/2010/11/servlet-gson-vs-jsonme-javame.html

Servlet-GSON vs JSONME-JavaME II: Second part of the saga. Adds compression/decompression support. More info:
http://www.java-n-me.com/2010/12/servlet-gson-vs-jsonme-javame-ii.html

Base64 encode-decode in JavaMe: Explains how to use Base64 format in JavaMe (J2ME). More info:
http://www.java-n-me.com/2010/12/base64-encode-decode-in-javame.html


You should check the previous requirements if you haven't, because in this post we use some of the classes defined there.

The image we are going to send is in PNG format:



OK, let's begin, first we are going to show you the changes in some of the original classes. As usual, let's start with the server side:

import java.awt.Image;
public class Client {

   /** Name of the client*/
    private String name;

    /** Last name of the client*/
    private String lastName;

    /** Identification of the client*/
    private String id;

    /** Photo of the client*/
    private transient Image photo;


    /** Photo of the client in Base 64 format*/
    private  String photoBase64;


    //Getters and Setters...
}

Our Client class has now two new attributes for the photograph of the client. The java.awt.Image attribute binds to the photograph of the client directly. The String attribute photoBase64 contains the Base64 format of the photograph that is going to be sent in the JSON String. Notice that the attribute photo is marked as transient, meaning that GSON won't use this attribute in the JSON String.

Following is the servlet class which will create a client with a photograph, will get the JSON String representation of the client and his photograph and will compress the data before sending the response to the mobile client. The servlet uses the GSON API, the java.util.zip.GZIPOutputStream and the JSE sun.misc.BASE64Encoder class:


import com.google.gson.Gson;
import java.io.*;
import java.net.URL;
import java.util.Vector;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.*;
import javax.swing.ImageIcon;
import model.Client;
import sun.misc.BASE64Encoder;

public class ClientsServlet extends HttpServlet {
   ...

    /**
     * Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
            throws ServletException, IOException {
        //sets the type of response and the charset used
        //Be careful, in your mobile client, read the response using the same charset
        //if sending the response as text. If you sent it as binary data,
        //there is no problem
        response.setContentType("application/json; charset=UTF-8");

        //create some clients and add them to the vector
        Vector vClients = new Vector();

        //used to convert Image to Base64 format
        BASE64Encoder encoder = new BASE64Encoder();

        Client clientOne = new Client();
        clientOne.setName("Alexis");
        clientOne.setLastName("López Ñ");
        clientOne.setId("123456");
        clientOne.setPhoto(new ImageIcon("images/duke.png").getImage());
        //get the image bytes. Read it as an URL
        byte[] imgBytes =
          getImageBytes("http://localhost:8080/GSON_Servlet/images/duke.png");
        String imageCoded = encoder.encode(imgBytes);
        clientOne.setPhotoBase64(imageCoded);

        //... add more clients...
      
        vClients
           .add(clientOne);

        //convert the clients vector to JSON using GSON, very Easy
        Gson gson = new Gson();
        String jsonOutput = gson.toJson(vClients);

        System.out.println("*****JSON STRING TO RESPONSE*****");
        System.out.println(jsonOutput);
        System.out.println("*********************************");

        System.out.println("Length of String in bytes = "
                                    + jsonOutput.getBytes().length);
        //compress de data
        byte[] compressed = compress(jsonOutput);
        System.out.println("Length of compressed bytes = "
                                    + compressed.length);

        //send the binary response
        ServletOutputStream out = response.getOutputStream();
        out.write(compressed, 0, compressed.length);
        out.flush();
        out.close();
    }
    ...
}

The servlet uses two important methods. One is the +compress(String):byte[] that we have analized in the previous post of this saga. The other one is the +getImageBytes(String):byte[] which is shown next:

/** * Returns the binary representation of the image passed as an URL * @param imgURL URL String of the image * @return array of binary data representing the image * @throws IOException */ public static byte[] getImageBytes(String imgURL) throws IOException { URL imgUrl = new URL(imgURL); ByteArrayOutputStream bout = new ByteArrayOutputStream(); InputStream in = imgUrl.openStream(); int i = 0; while ((i = in.read()) != -1) { bout.write(i); } in.close(); bout.close(); byte[] bytes = bout.toByteArray(); return bytes; }

The last method reads the image from the URL and gets its binary representation. One important thing to keep in mind, is that you should use PNG Images as it is the default format for JavaMe (J2ME) MIDP applications, not doing so may arise exceptions on the mobile client when parsing the Image data. When the servlet is run and it receives a POST request, the following message is shown on the console output:

INFO: *****JSON STRING TO RESPONSE***** 9/01/2011 06:11:23 PM INFO: [{"name":"Alexis","lastName":"López Ñ","id":"123456","photoBase64":"iVBORw0KGgoAAAANSUhEUgAAAP8AAADICAMAAAAQqcftAAAAAXNSR0IArs4c6QAAAwBQTFRFDAAA\r\nCgQKGAAAEAgMGAAIGAAQGAgIGAgQEBAIABAQCBAQEBAQEBAYEBgQEBgYECEYGAgYGBAIGBAQGBAY\r\nGBgIGBgQGBgYGBghGCEYIQAAIQAIIQAQIQgQIQgYIRAQIRAYKQAMKQgQKQAYIRAhIRgQIRgYIRgh\r\nISEYMQAILgUVNwQSQgAYKRAUKRgYKSEYPA0YISEhKRAhKRghKRgpMRQhMRgpPQghPRQhKSEhKSkh\r\nISEpKSYrMSUlNi4pOScvOzU1SBwpRkI9WgYgbBk1RkJKTkdHVkhNVFJMUlJaWlRUXlhaZVtdb2Fk\r\ncWxne2lwf3h5yBUsqDBU1Bcx0SRJ3BU53xs53yJK4TJjin5"
... not all data shown ... "}] INFO: *********************************



INFO: Length of String in bytes = 17425
INFO: Length of compressed bytes = 12890

As you can see, the JSON representation of the client now has the photograph in Base64 format in order to send the image data to the mobile client as part of the JSON String. Also notice that there is some compression in the response to the client.

On the mobile client side, there are also changes in the Client class:

import java.util.Vector;
import javax.microedition.lcdui.Image;
import org.bouncycastle.util.encoders.Base64;
import org.json.me.JSONArray;
import org.json.me.JSONException;
import org.json.me.JSONObject;

public class Client {


    /** Name of the client*/
    private String name;

    /** Constant name of the attribute name*/
    private static final String ATT_NAME = "name";

    /** Last name of the client*/
    private String lastName;

    /** Constant name of the attribute last name*/
    private static final String ATT_LAST_NAME = "lastName";

    /** Identification of the client*/
    private String id;

    /** Constant name of the attribute id*/
    private static final String ATT_ID = "id";

    /** Photo of the client*/
    private  Image photo;

    /** Photo of the client in Base 64 format*/
    private  String photoBase64;


     /** Constant name of the attribute photoBase64*/
    private static final String ATT_PHOTO_BASE_64 = "photoBase64";

    //Getters and Setters...

    /**
     * This method should be used by this class only, that's why it is private.
     * Allows to get a JSONObject from the Client passed as parameter
     * @param client Client Object to convert to JSONObject
     * @return JSONObject representation of the Client passed as parameter
     */
    private static JSONObject toJSONObject(Client client) {
        JSONObject json = new JSONObject();
        try {
            json.put(ATT_NAME, client.getName());
            json.put(ATT_LAST_NAME, client.getLastName());
            json.put(ATT_ID, client.getId());
            //only if the client has a photo associated send it as JSON
            if(client.getPhotoBase64() != null)
            {
                json.put(ATT_PHOTO_BASE_64, client.getPhotoBase64());
            }
        } catch (JSONException ex) {
            ex.printStackTrace();
        }
        return json;
    }

    /**
     * Allows to get a Client Object from a JSON String
     * @param jsonText JSON String to convert to a Client Object
     * @return Client Object created from the String passed as parameter
     */
    public static Client fromJSON(String jsonText) {
        Client client = new Client();
        try {
            JSONObject json = new JSONObject(jsonText);

            //check if the JSON text comes with the attribue Name
            if (json.has(ATT_NAME)) {
                //asign the String value of the attribute name to the client
                client.setName(json.getString(ATT_NAME));
            }

            //check if the JSON text comes with the attribue last name
            if (json.has(ATT_LAST_NAME)) {
                client.setLastName(json.getString(ATT_LAST_NAME));
            }

            //check if the JSON text comes with the attribue id
            if (json.has(ATT_ID)) {
                client.setId(json.getString(ATT_ID));
            }

             //check if the JSON text comes with the attribue photo
            if (json.has(ATT_PHOTO_BASE_64)) {
                client.setPhotoBase64(json.getString(ATT_PHOTO_BASE_64));


                //once the photo in base 64 format has been read,
                //convert it to an Image. IMPORTANT: PNG image format or
                //you may get an Exception
                byte[] decoded = Base64.decode(client.getPhotoBase64());
                Image img = Image.createImage(decoded, 0, decoded.length);
                client.setPhoto(img);
            }
        } catch (JSONException ex) {
            ex.printStackTrace();
        }
        return client;
    }
}

You can find the changes in bold. The important changes are the adition of the attributes for managing the photograph and the modification of the methods for parsing the JSON String to and from an Object. Notice the use of the org.bouncycastle.util.encoders.Base64 class to decode the Base64 format data of the photograph. You can find the original source code of the Client class (mobile client side) in the first post of this saga.

Next is the MIDlet that runs the code:

import com.tinyline.util.GZIPInputStream;
import java.io.*;
import java.util.Vector;
import javax.microedition.io.*;
import javax.microedition.lcdui.Display;
import javax.microedition.lcdui.Form;
import javax.microedition.midlet.*;


public class JSONMIDlet extends MIDlet {


    private Form f = null;


    public JSONMIDlet() {
        //form for the photos
        f = new Form("Photo testing");
    }


    public void pauseApp() {
    }


    public void destroyApp(boolean unconditional) {
        notifyDestroyed();
    }


    public void startApp() {
        try {
            //ask for the clients
            Vector clients = getClients();


            //show information of the clients
            System.out.println("*****CLIENT INFORMATION*****");
            for (int i = 0; i < clients.size(); i++) {
                Client cl = (Client) clients.elementAt(i);


                System.out.println("Client " + (i + 1) 
                                                + " name = " + cl.getName());
                System.out.println("Client " + (i + 1) 
                                                + " last name = " + cl.getLastName());
                System.out.println("Client " + (i + 1) 
                                                + " ID = " + cl.getId());
                System.out.println("Client " + (i + 1) 
                                                + " Photo Base64 = " + cl.getPhotoBase64());
                System.out.println("");


                //if the client has a photo, append it to the form
                if (cl.getPhoto() != null) {
                    f.append(cl.getPhoto());
                }
            }




            Display dsp = Display.getDisplay(this);
            dsp.setCurrent(f);




        } catch (IOException ex) {
            //manage the exception
            ex.printStackTrace();
        }
    }


...//other methods


}

The method +getClients():Vector has not changed and can be found in the last post of this saga (as there is compression/decompression support). Finally the emulator shows the following:


Well, that's it for today. Quite a large post, I hope I was clear enaugh, but if you have any questions do not hesitate to contact me.

see ya soon!


References:

TinyLine Utils. 2010. TinyLine [online].
Available on Internet: http://www.tinyline.com/utils/index.html
[accessed on December 11 2010].

Using JavaScript Object Notation (JSON) in Java ME for Data Interchange. Agosto 2008. Oracle [online].
Available on Internet: http://java.sun.com/developer/technicalArticles/javame/json-me/
[accessed on October the 26th 2010].

meapplicationdevelopers: Subversion. 2007. Oracle [online].
Available on Internet: https://meapplicationdevelopers.dev.java.net/source/browse/meapplicationdevelopers/demobox/mobileajax/lib/json/
[accessed on October the 27th 2010].

The Legion of the Bouncy Castle. bouncycastle.org [online].
Available on Internet: http://www.bouncycastle.org/
[accessed on December 28 2010].