Using Google App Engine as Backend for Android

December 2nd, 2011 · No Comments

If you’re looking for a way to create a backend for your Android application, Google App Engine looks like the perfect choice: You can use Java as you can do for Android and you don’t need to think too much about hosting, as it is all stored in the cloud.

Another benefit is that you can reuse your transfer objects on the client and on server side. But as it is often there are some problems doing this in practice. So you don’t have the same ones I had, I am glad to share my experiences with you.

So first question is what libraries are to use for the client/server communication. At start I tried Restlet 2.0. Looked like a great choice as there are special editions for App Engine and for Android available. In practice it is not very useful as the libraries are to big for Android and I also very much disliked that fully serialized java objects are transfered.

Best approach I found so far is to use Jersey 1.6: It is easy to use and implements the JAX-RS (JSR 311) standard. To set it up on the App Engine, please consult these blog posts from me: Using real POJOs (without JAXB Annotations) as transfer objects with JAX-RS and Storing large images RESTful in the cloud using Google App Engine.

Ok, so far about the server side. To keep things small and simple on the Android side, I mainly created the following wrapper class for the HttpClient to handle the HTTP requests:

import org.codehaus.jackson.map.ObjectMapper;
 
public class HttpUtils {
 
        private static final int SERVER_PORT = 80;
        private static final String SERVER_IP = "myapp.appspot.com"; // use 10.0.2.2 for emulator
 
        private static HttpUtils instance = new HttpUtils();
        private DefaultHttpClient client;
        private ResponseHandler<String> responseHandler;
        private ObjectMapper mapper;
 
        private HttpUtils() {
                super();
                client = new DefaultHttpClient();
                HttpConnectionParams.setConnectionTimeout(client.getParams(), 30000);
                responseHandler = new BasicResponseHandler();
                mapper = new ObjectMapper(); // can reuse, share globally
        }
 
        public static HttpUtils getInstance() {
                return instance;
        }
 
        public String doGet(String path) throws IOException {
                return doGet(path, null);
        }
 
        public String doGet(String path, String query) throws IOException {
                try {
                        URI uri;
                        uri = createURI(path, query);
                        HttpGet get = new HttpGet(uri);
                        HttpResponse response = client.execute(get);
                        int statusCode = response.getStatusLine().getStatusCode();
                        if (statusCode == HttpStatus.SC_OK) {
                                return responseHandler.handleResponse(response);
                        } else {
                                throw new IOException("wrong http status: " + statusCode);
                        }
                } catch (URISyntaxException e) {
                        throw new IOException("uri syntax error");
                } catch (ClientProtocolException e) {
                        throw new IOException("protocol error");
                } 
 
        }
 
        private URI createURI(String path, String query) throws URISyntaxException {
                return URIUtils.createURI("http", SERVER_IP, SERVER_PORT, "rest/" + path, query, null);
        }
 
        public boolean doPut(String path, Object object) throws IOException {
                try {
                        String json = mapper.writeValueAsString(object);
                        URI uri = createURI(path, null);
                        HttpPut put = new HttpPut(uri);
                        put.addHeader("Accept", "application/json");
                        put.addHeader("Content-Type", "application/json");
                        StringEntity entity = new StringEntity(json, "UTF-8");
                        entity.setContentType("application/json");
                        put.setEntity(entity);
                        HttpResponse response = client.execute(put);
                        int statusCode = response.getStatusLine().getStatusCode();
                        return statusCode == HttpStatus.SC_OK;
                } catch (URISyntaxException e) {
                        throw new IOException("uri syntax error");
                } catch (ClientProtocolException e) {
                        throw new IOException("protocol error");
                }
        }
 
        public String doPutFile(final String path, final File file) throws URISyntaxException, HttpException,
                        IOException {
                URI uri = createURI(path, null);
                HttpPut put = new HttpPut(uri);
                String mimeType = "binary/octet-stream";
                if(file.getName().matches(".*\\.(jpeg|jpg)"))
                        mimeType = "image/jpeg";
                FileEntity reqEntity = new FileEntity(file, mimeType);
                put.setEntity(reqEntity);
                HttpResponse response = client.execute(put);
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode == HttpStatus.SC_OK) {
                        return responseHandler.handleResponse(response);
                } else {
                        throw new IOException("wrong http status: " + statusCode);
                }
        }
 
        public String doPost(final String path, final String POSTText) throws URISyntaxException, HttpException,
                        IOException {
                URI uri = createURI(path, null);
                HttpPost httpPost = new HttpPost(uri);
                StringEntity entity = new StringEntity(POSTText, "UTF-8");
                BasicHeader basicHeader = new BasicHeader(HTTP.CONTENT_TYPE, "application/json");
                httpPost.getParams().setBooleanParameter("http.protocol.expect-continue", false);
                entity.setContentType(basicHeader);
                httpPost.setEntity(entity);
                HttpResponse response = client.execute(httpPost);
                int statusCode = response.getStatusLine().getStatusCode();
                if (statusCode == HttpStatus.SC_OK) {
                        return responseHandler.handleResponse(response);
                } else {
                        throw new IOException("wrong http status: " + statusCode);
                }
        }
 
        public boolean doDelete(final String path) throws HttpException, IOException, URISyntaxException {
                URI uri = createURI(path, null);
                HttpDelete httpDelete = new HttpDelete(uri);
                httpDelete.addHeader("Accept", "text/html, image/jpeg, *; q=.2, */*; q=.2");
                HttpResponse response = client.execute(httpDelete);
                int statusCode = response.getStatusLine().getStatusCode();
                return statusCode == HttpStatus.SC_OK ? true : false;
        }
 
}

This implementation is far from perfect, especially exception handling and passing parameters need to be improved, but it works so far :)

Using this class it is easy to store a file using the FileServerResource from my former blog post. Just call:

File imageFile = new File(new URI(myimgage.getImageURL()));
String url = HttpUtils.getInstance().doPutFile("file/store", imageFile);

Also it is easy to store a transfer object using the doPut method. Note that is is using the ObjectMapper class from Jackson, the same JSON processor that is also used by Jersey.
Jackson is therefore the only additional library that you need on the Android side which keeps the executable small. If you use the same version of Jackson on the client side as on the server side you’re also ensured that the (un-)marshalling process of your transfer objects works flawlessly on both sides.

Hope you liked this approach – feel free to discuss here further ideas.

→ No CommentsTopics: · , , ,

Storing large images RESTful in the cloud using Google App Engine

December 1st, 2011 · No Comments

In my last article I showed how to store files in the cloud using Google app engine.
Problem there was that the maximum size of the files was 1MB. Not that much for images.

To improve the situation, we just shrink the images with this very simple algorithm by factor 0.9 until the size is less than 1MB:

public class FileTransformer {
 
	public static final Logger log = Logger.getLogger(FileTransformer.class.getName());
 
	private static final int JPEG_QUALITY = 90;
	private static final int MAX_FILE_SIZE = 1024*1024;
 
	public byte[] transform(byte[] ba, String mediaType) {
		if(mediaType.equalsIgnoreCase("image/jpeg")
				|| mediaType.equalsIgnoreCase("image/jpg")) {
			return imageTransform(ba);
		}
		throw new IllegalArgumentException("Unsupported media type: " + mediaType + " for this transformer");
	}
 
	private byte[] imageTransform(final byte[] ba) {
		byte[] result = ba;
		while(result.length>MAX_FILE_SIZE) {
			final Image origImage = ImagesServiceFactory.makeImage(result);
			final Image newImage = shrinkImage(origImage, 0.9f);
			result = newImage.getImageData();
		}
		return result;
	}
 
	private Image shrinkImage(final Image origImage, final float percent) {
		final ImagesService imagesService = ImagesServiceFactory.getImagesService();
		log.fine("orig dimensions is " + origImage.getWidth() + " X " + origImage.getHeight());
		final OutputSettings settings = new OutputSettings(OutputEncoding.JPEG);
		settings.setQuality(JPEG_QUALITY);
 
		final Transform resize = ImagesServiceFactory.makeResize(Math.round(origImage.getWidth()*percent), Math.round(origImage.getHeight()*percent));
		final Image newImage = imagesService.applyTransform(resize, origImage, settings);
		return newImage;
	}
 
}

This FileTransformer class is then just called in the storeFile method of the FileServerResource in the case the file size is large than 1MB:

if (ba.length > 1024 * 1024) {
    ba = new FileTransformer().transform(ba, mediaType);
}

Remark: So far this only works with JPEG media types, otherwise an exception is raised. Feel free to add different compression cases for other media types (e.g. using GZIP on texts). As I said, it is a good idea to store
the media type ;)

→ No CommentsTopics:

Storing files RESTful in the cloud using Google App Engine

December 1st, 2011 · No Comments

Do you want to store files RESTful in the cloud? Why not use the Google App Engine for it?

Firstly you will need a entity class that is storing the file in the data store:

@Entity
public class FileEntity {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long key;
 
	private Blob content;
	private String mediaType;
 
	public Long getKey() {
		return key;
	}
 
	public Blob getContent() {
		return content;
	}
 
	public void setContent(Blob content) {
		this.content = content;
	}
 
	public String getMediaType() {
		return mediaType;
	}
 
	public void setMediaType(String mediaType) {
		this.mediaType = mediaType;
	}	
}

Beside the actual content of the file, this entity also stores the media type (you never know, it might be good to know…).
As I just like standards, you also might mention that I used JPA as much as possible (Blob is a Google specific thing, as unfortunately byte[] can not store large amounts. BTW: Blob is also limited by 1MB, so you can not store larger files than 1MB using this approach).

Secondly you will need a JAX-RS resource class that is handling the GET and PUT requests from clients:

@Path("/file")
public class FileServerResource {
 
	public static final Logger log = Logger.getLogger(FileServerResource.class.getName());
 
	@Context
	UriInfo uriInfo;
	@Context
	HttpHeaders headers;
	@Context
	HttpServletResponse response;
 
	@PUT
	@Path("/store")
	@Consumes({ MediaType.TEXT_PLAIN, "image/jpeg", "image/png", "image/gif", "application/zip" })
	@Produces({ MediaType.TEXT_PLAIN })
	public String storeFile(final byte[] b) {
		FileEntity file = EMF.doTransaction(new EMF.Closure<FileEntity>() {
			public FileEntity doit(EntityManager em) {
				byte[] ba = b;
				String mediaType = headers.getMediaType().toString();
				FileEntity file = new FileEntity();
				file.setContent(new Blob(ba));
				file.setMediaType(mediaType);
				em.persist(file);
				return file;
			}
		});
		UriBuilder ub = uriInfo.getBaseUriBuilder();
		URI link = ub.path("/file/get/" + file.getKey().toString()).build();
		return link.toString();
	}
 
	@GET
	@Path("/get/{id}")
	@Produces("*/*")
	public Response getFile(@PathParam("id") final long id) {
		return EMF.doTransaction(new EMF.Closure<Response>() {
			public Response doit(EntityManager em) {
				FileEntity entity = em.find(FileEntity.class, id);
				if (entity == null)
					throw new WebApplicationException(404);
				return Response.ok(entity.getContent().getBytes(), entity.getMediaType()).build();
			}
		});
	}
}

At last you just need a small helper class (EMF.java) that is handling the transactions and of course an JAX-RS implementation (I used Jersey 1.6 – works perfectly with App Engine).

To test the file store, just call:
curl --upload-file mypic.jpg -H "Content-Type: image/jpeg" http://localhost:8888/rest/file/store

This request returns the URL of the file that is stored. Just request this URL to retrieve your file back from the storage.

Have fun and let me know, if you are using this code for some of your projects.

→ No CommentsTopics:

Using real POJOs (without JAXB Annotations) as transfer objects with JAX-RS

December 1st, 2011 · No Comments

Are you annoyed that you have to annotate your POJOs with @XmlRootElement, so they can be used with JAX-RS? If your using Jersey as JAX-RS implementation your lucky: Just add to the <servlet> tag in your web.xml the following snippet:

<init-param>
	<param-name>com.sun.jersey.api.json.POJOMappingFeature</param-name>
	<param-value>true</param-value>
</init-param>

After restarting your servlet, your POJOs are marshalled to JSON as a charme. Enjoy!

→ No CommentsTopics: · , , , , , , , , ,

JBoss-Migration: Von 4 über 5 nach 7

November 18th, 2011 · No Comments

Die Migration einer Java Enterprise-Anwendung auf eine höhere JBoss-Version ist nicht an einem Nachmittag erledigt. Auf welche Probleme man stoßen kann, beschreibe ich in meinem Erfahrungsbericht: Von 4 über 5 nach 7, erschienen im JavaMagazin 10/2011.

Behandelt wird die Migration einer J2EE-1.4-Anwendung von JBoss 4 bis 7.

Vielen Dank an den Verlag (Software & Support Media GmbH), dass ich das PDF in meinem Blog nun zur Verfügung stellen darf.

→ No CommentsTopics: · , , , ,