Tuesday, August 10, 2010

Goolge app engine + java + spring + REST + JSON + Flex


http://www.wetfeetblog.com/goolge-app-engine-java-spring-rest-json-flex-part-1/87



Objective

Once you are finished with this article, you will be able to implement REST services in Google Apps Engine using JAVA, Spring 3.0 and JSON. In the next part you will learn how to add flex application to consume the services.

Step one – Getting all required libraries: Google App Engine SDK, Spring and JSON serializer

If you are using Eclipse (my favorite IDE), you can install google plugin from here. If you choose to go another route, you need to download the sdk, create the project and join us back in the next section. Once you install the plugin, restart eclipse and you can see these buttons



you can proceed to the next section.

Step Two – Create new project

Go to new project wizzard



and select Web application project under google. you should get to the project settings screen



choose your project name (can be anything, not necessarily you google application name) and you base package, click finish and you should see the following project structure:



Step Three – Add Spring Support

Go to springsource.org and download latest 3.0 release, unzip it into a folder where you keep your java libs. Copy the following Jars into war\WEB-INF\llib directory:

org.springframework.asm-3.0.x.jar
org.springframework.beans-3.0.x.jar
org.springframework.context-3.0.x.jar
org.springframework.core-3.0.x.jar
org.springframework.expression-3.0.x.jar
org.springframework.oxm-3.0.x.jar
org.springframework.web-3.0.x.jar
org.springframework.web.servlet-3.0.x.jar
Also grab your favorite version of log4j.jar and commons-logging-1.1.1.jar. The trick with the commons-logging is to rename it to something like commons-fix-logging-1.1.1.jar, google app engine replaces this jar with its own version with crippled packages, by providing different name we keep both versions and make spring happy. Once you copied the jars, open project preferences and add those jars to the build library path.



Now lets get to the fun part – configuring spring.

Lets get rid of the generated servlet – just delete it. And open web.xml. My generated one looks like this:



xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">

Rest_json_flex
com.lureto.rjf.Rest_json_flexServlet


Rest_json_flex
/rest_json_flex


index.html




The final version should look like this:



xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">


log4jConfigLocation
/WEB-INF/log4j.properties


org.springframework.web.util.Log4jConfigListener



rest-json-flex
org.springframework.web.servlet.DispatcherServlet
1



rest-json-flex
/api/*



index.html





As you can see I have moved log4j.properties from src to WEB-INF location. I like all my config files in one place, you can leave it in src or move it somewhere else, just adjust the path accordingly. Second section defines Spring dispatcher servlet, then we map this servlet to /api/* path. All requests with this pattern will get routed to spring dispathcer servlet.

Step Four – Add JSON support

I looked around for good java JSON library and found couple good candidates. Since Spring uses Jackson JSON I decided to go the same route. Grab the library from here and put jackson-core-1.2.1.jar and jackson-mapper-1.2.1.jar file into projects WEB-INF/lib directory. Choose your favorite license when you downloading the jars.

Step Five – Configure Spring

To configure Spring servlet we need to create servletname-servlet.xml file for spring bean configuration. Our file has to be named rest-json-flex-servlet.xml.



xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">






























First we tell String to scan “com.lureto.rjf” package for any bean annotations.





Next we define message converter to convert messages sent to the server into beans using MappingJacksonHttpMessageConverter












The last part tells spring to render messages sent from the server to the client using JsonView. Here we need to do some bug fixing, as you may have noticed the classes used are from “com.lureto.rjf.spring” and not from “org.springframework.web”.

















MyContentNegotiatingViewResolver.java – at the moment of writing this the 3.0.0.RC1 version of ContentNegotiatingViewResolver has bug with the list being singleton.


package com.lureto.rjf.spring;

import java.util.Arrays;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.springframework.http.MediaType;
import org.springframework.web.servlet.view.ContentNegotiatingViewResolver;

public class MyContentNegotiatingViewResolver extends ContentNegotiatingViewResolver {

protected List getMediaTypes(HttpServletRequest request) {
List result = super.getMediaTypes(request);
if (result.size() == 1)
result = Arrays.asList(result.get(0));
return result;
}


}


JsonView.java – is a copy org.springframework.web.servlet.view.json.MappingJacksonJsonView with one change:


@Override
protected void renderMergedOutputModel(Map model,
HttpServletRequest request,
HttpServletResponse response) throws Exception {
model = filterModel(model);
JsonGenerator generator = objectMapper.getJsonFactory().createJsonGenerator(response.getWriter());
if (prefixJson) {
generator.writeRaw("{} && ");
}
objectMapper.writeValue(generator, model);
}


I had to change response.getOutputStream() to response.getWriter(), since jetty’s implementation of setting content type and encoding uses writer, if you try grabbing a stream after that, you will get an exception.

Step Six – Lets write a little bit of code

First lets define a model object that we will send across the wire. Here is my User.java class:


public class User {

private long id;
private String email;
private String name;

public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}


And implement the controller to send and receive data. Here is my UserController.java:


package com.lureto.rjf;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.log4j.Logger;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;

@Controller
@RequestMapping("/user")
public class UserController {

private static Logger logger = Logger.getLogger( UserController.class );

@ModelAttribute("users")
@RequestMapping(value = "/", method = RequestMethod.GET)
public List listUsers() throws IOException {
List users = new ArrayList();

User user = new User();
user.setId(10001);
user.setEmail("user.one@gmail.com");
user.setName("User UNO");

users.add(user);

return users;
}

@ModelAttribute("user")
@RequestMapping(value = "/", method = RequestMethod.POST)
public User saveUser( @RequestBody User user ) throws IOException {
logger.debug(user);
return user;
}
}


Lest go step my step through this and look at what all these little thing do.
We have marked our class as @Controller which indicates to sprign framework that this is a controller class. @RequestMapping(“/user”) indicates that this controller will handle requests that start with /user, /api/user to be precise, since spring servlet is configured to handle /api/* mappings.


@ModelAttribute("users")
@RequestMapping(value = "/", method = RequestMethod.GET)
public List listUsers() throws IOException {


Here we define a methow which will be invoked when /api/user/ path gets called with GET method. If you running standard config http://localhost:8080/api/user/ would be the url to hit in the browser. @ModelAttribute(“users”) tells spring to put the object returned by this method into users model attribute for the view. Since we have wired everything in xml configuration, if we return the object it will be rendered by default view which serializes beans into JSON strings.
Our method for receiving json objects looks like this:


@ModelAttribute("user")
@RequestMapping(value = "/", method = RequestMethod.POST)
public User saveUser( @RequestBody User user ) throws IOException {
logger.debug(user);
return user;
}


@RequestBody annotation tells Spring framework to take request body and convert it into a bean using message converter. We have a single message converter defined in xml configuration file to be MappingJacksonHttpMessageConverter.

Conclusion

As you can see there is little code to write. In next Spring release the 2 bugs we fixed ourselves will probably be fixed, so this project will have only 2 source files, 3 xml files and 2 properties files. All the work is done by Spring framework, we just need to worry about the business logic and model.
Full project can be downloaded from here.
In the next part we will add flex project, so we can read the json object returned and post user object to the server.

UPDATE – October 27, 2009


Uncaught exception from servlet
java.lang.NullPointerException
at com.google.apphosting.runtime.security.shared.RuntimeVerifier.isInspectable(RuntimeVerifier.java:302)
at com.google.apphosting.runtime.security.shared.intercept.java.lang.Class_.getEnclosingMethod(Class_.java:237)
at org.codehaus.jackson.map.util.ClassUtil.isLocalType(ClassUtil.java:88)
at org.codehaus.jackson.map.deser.BeanDeserializerFactory.isPotentialBeanType(BeanDeserializerFactory.java:613)
at org.codehaus.jackson.map.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:61)
at org.codehaus.jackson.map.deser.StdDeserializerProvider._createDeserializer(StdDeserializerProvider.java:248)
at org.codehaus.jackson.map.deser.StdDeserializerProvider._createAndCacheValueDeserializer(StdDeserializerProvider.java:181)
at org.codehaus.jackson.map.deser.StdDeserializerProvider.findValueDeserializer(StdDeserializerProvider.java:100)
at org.codehaus.jackson.map.ObjectMapper._findRootDeserializer(ObjectMapper.java:1069)
at org.codehaus.jackson.map.ObjectMapper._readMapAndClose(ObjectMapper.java:1002)
at org.codehaus.jackson.map.ObjectMapper.readValue(ObjectMapper.java:818)
...... SNIP ......


After deploying to app engine I found out that jackson json library is using some unsupported introspection calls and had to make a fix inside jackson library to make it run inside google app engine. I have updated the jars inside the project to catch the exception and let parser to continue on its way. Here is the fix:


public static String isLocalType(Class type)
{
try {
// one more: method locals, anonymous, are not good:
if (type.getEnclosingMethod() != null) {
return "local/anonymous";
}

/* But how about non-static inner classes? Can't construct
* easily (theoretically, we could try to check if parent
* happens to be enclosing... but that gets convoluted)
*/
if (type.getEnclosingClass() != null) {
if (!Modifier.isStatic(type.getModifiers())) {
return "non-static member class";
}
}
} catch ( Exception exc ) {}
return null;
}

No comments:

Post a Comment