Unfortunately J2ME does not provide the best basis for writing a REST Web Service. As I already mentioned in previous posts the J2ME reference implementation does not support all HTTP methods. To overcome this restriction I wrote a J2ME Rest Client that uses HTTP method emulation like Rails does. Even though I heard that some J2ME platforms support PUT and DELETE I decided to use the standard. Method emulation may become a challenge if you’re not talking to your own services since it require a suitable server implementation like I posted for Grails earlier.
The following code illustrates how the Rest Client works. It uses the BasicAuth class from Sun. I removed the imports, because it should be obvious. In future versions of the class I plan to use one method to read the data from stream an check the status to reduce the file size. But for my first prototype it works that way.
/** * This class provided a REST implementation for J2ME. * @author Christoph Hartmann */ public class RestClient { String userid; String password; // User Agent private String ua; public RestClient() {} public RestClient(String userid, String password) { this.userid = userid; this.password = password; ua = "Profile/" + System.getProperty("microedition.profiles") + " Configuration/" + System.getProperty("microedition.configuration"); } /** * prepare the HTTP connection * @param conn * @throws IOException */ private void configureConncetion(HttpConnection conn) throws IOException { conn.setRequestProperty("User-Agent", ua); String locale = System.getProperty("microedition.locale"); if (locale == null) { locale = "en-US"; } conn.setRequestProperty("Accept-Language", locale); conn.setRequestProperty("Content-Type", "text/plain"); conn.setRequestProperty("Accept", "text/plain"); // set HTTP basic authentification if (userid != null && password != null) { conn.setRequestProperty("Authorization", "Basic " + BasicAuth.encode(userid, password)); } } public HttpConnection getConnection(String url) throws IOException { HttpConnection conn = (HttpConnection) Connector.open(url); configureConncetion(conn); return conn; } public HttpConnection getConnection(String url, int access) throws IOException { HttpConnection conn = (HttpConnection) Connector.open(url, access); configureConncetion(conn); return conn; } /** * READ * @param url * @return * @throws IOException */ public String get(String url) throws IOException { HttpConnection hcon = null; DataInputStream dis = null; StringBuffer responseMessage = new StringBuffer(); try { int redirectTimes = 0; boolean redirect; do { redirect = false; // a standard HttpConnection with READ access hcon = getConnection(url); // obtain a DataInputStream from the HttpConnection dis = new DataInputStream(hcon.openInputStream()); // retrieve the response from the server int ch; while ((ch = dis.read()) != -1) { responseMessage.append((char) ch); }// end while ( ( ch = dis.read() ) != -1 ) // check status code int status = hcon.getResponseCode(); switch (status) { case HttpConnection.HTTP_OK: // Success! break; case HttpConnection.HTTP_TEMP_REDIRECT: case HttpConnection.HTTP_MOVED_TEMP: case HttpConnection.HTTP_MOVED_PERM: // Redirect: get the new location url = hcon.getHeaderField("location"); System.out.println("Redirect: " + url); if (dis != null) dis.close(); if (hcon != null) hcon.close(); hcon = null; redirectTimes++; redirect = true; break; default: // Error: throw exception hcon.close(); throw new IOException("Response status not OK:" + status); } // max 5 redirects } while (redirect == true && redirectTimes < 5); if (redirectTimes == 5) { throw new IOException("Too much redirects"); } } catch (Exception e) { e.printStackTrace(); // TODO bad style responseMessage.append("ERROR: "); } finally { try { if (hcon != null) hcon.close(); if (dis != null) dis.close(); } catch (IOException ioe) { ioe.printStackTrace(); }// end try/catch }// end try/catch/finally return responseMessage.toString(); }// end sendGetRequest( String ) /** * UPDATE * * @param url * @param data * the request body * @throws IOException */ public String post(String url, String data) throws IOException { HttpConnection hcon = null; DataInputStream dis = null; DataOutputStream dos = null; StringBuffer responseMessage = new StringBuffer(); try { int redirectTimes = 0; boolean redirect; do { redirect = false; // an HttpConnection with both read and write access hcon = getConnection(url, Connector.READ_WRITE); // set the request method to POST hcon.setRequestMethod(HttpConnection.POST); // overwrite content type to be form based hcon.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); // set message length if (data != null) { hcon.setRequestProperty("Content-Length", "" + data.length()); } if (data != null) { // obtain DataOutputStream for sending the request string dos = hcon.openDataOutputStream(); byte[] request_body = data.getBytes(); // send request string to server for (int i = 0; i < request_body.length; i++) { dos.writeByte(request_body[i]); }// end for( int i = 0; i < request_body.length; i++ ) dos.flush(); // Including this line may produce // undesiredresults on certain devices } // 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); }// end while( ( ch = dis.read() ) != -1 ) { // check status code int status = hcon.getResponseCode(); switch (status) { case HttpConnection.HTTP_OK: // Success! break; case HttpConnection.HTTP_TEMP_REDIRECT: case HttpConnection.HTTP_MOVED_TEMP: case HttpConnection.HTTP_MOVED_PERM: // Redirect: get the new location url = hcon.getHeaderField("location"); System.out.println("Redirect: " + url); if (dis != null) dis.close(); if (hcon != null) hcon.close(); hcon = null; redirectTimes++; redirect = true; break; default: // Error: throw exception hcon.close(); throw new IOException("Response status not OK:" + status); } // max 5 redirects } while (redirect == true && redirectTimes < 5); if (redirectTimes == 5) { throw new IOException("Too much redirects"); } } catch (Exception e) { e.printStackTrace(); responseMessage.append("ERROR"); } finally { // free up i/o streams and http connection try { if (hcon != null) hcon.close(); if (dis != null) dis.close(); if (dos != null) dos.close(); } catch (IOException ioe) { ioe.printStackTrace(); }// end try/catch }// end try/catch/finally return responseMessage.toString(); } /** * DELETE * not possible on J2ME therefore we use Rails emulation on PUT and * DELETE like * http://localhost:8080/CandyStreamServer/airport/1?_method=delete * @param url */ public String delete(String url) throws IOException { url += "?_method=DELETE"; return post(url, null); } /** * CREATE not possible on J2ME therefore we use Rails emulation on PUT and * DELETE * @param url * @param data */ public String put(String url, String data) throws IOException { data += "&_method=PUT"; url += "?" + data; return post(url, null); } }
Tags: J2ME, Web Service
December 30th, 2008 at 1:38 am
Hi Christoph,
Many thanks for your kind help. I am trying to use this in a SCWS environment. thanks again
Domnic