GWT 2.2.0 RequestFactory + Spring 3.0.x Integration
MOVED from old blog
I must say it’s been a while since my last post. Lately, I have been really busy with work and part-time projects that I could not even reserve a minute to come here. But anyways today I’m here to reveal some really good work that I was stuck on for almost a week now. In my project, I had decided to implement GWT 2.2.0 along with Spring 3.0.x. Talking about communication: one way to enable communication between client and server is to use GWT RPC mechanism. But RPC from what I have learned is more of a service oriented approach. My first need was to have something in place for CRUD functionality. For data oriented tasks such as CRUD, Google has invented something called RequestFactory (RF). After skimming the Google tutorial, RF sounded like the right approach for CRUDing data. With RF we can also control the amount of data client will present on the UI. Now since I’m using Spring, the challenge was to discover a way to integrate RF with Spring. I spent hours researching on how to do this but couldn’t find a way. That being said there’s enough material available on Google in regards to RPC + Spring integration. After few days of hard luck, I finally invented a solution on how to do this. I would also like to give credit to “niloc132” who’s usually on #gwt and helped with GWT.
TWITTER: Please give credits where due and you can follow me on twitter at @jsinghfoss.
LICENSE: You are free to use this solution for personal or commercial use. You can also modify the solution in any way. In case if you have enhanced the solution, please share with everyone. Provided as-is without any guaranty.
EDIT 4/27/2011 I have noticed few bugs in the below source-code. Once all the bugs are fixed, I will edit this post to include changes. So, stay tuned!
EDIT 5/5/2011 Modified GWTSpringEntityLocator class. Changes include: class does not implement ApplicationContextAware interface anymore, Create method creates an instance of the entity, Removed unused context (as well as set method).
RequestFactory + Spring INTEGRATION SOLUTION
STEP 1 - web.xml
Declare DispatcherServlet and map it to “/gwtRequest”
<servlet> <servlet-name>singhsolution</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>singhsolution</servlet-name> <url-pattern>/gwtRequest</url-pattern> </servlet-mapping>
The above code will intercept all incoming requests whose url pattern contains “/gwtRequest“.
STEP 2 - singhsolution-servlet.xml
Create a servlet mapping file by the name “singhsolution-servlet.xml“. It will contain information related to the servlets managed by Spring.
Inside this file we need to declare a SimplUrlHandlerMapping bean which will map a value to its appropriate controller. In our case, the controller is called “gwtRequestFactoryController“.
<bean> <property name="mappings"> <value> /gwtRequest=gwtRequestFactoryController </value> </property> </bean>
Before we declare our “gwtRequestController” we need to create few classes that will be used by the client to find appropriate entities and services managed by Spring.
STEP 3 - Create the following classes
GWTSpringServiceLayerDecorator
GWTSpringEntityLocator
GWTSpringServiceLocator
public class GWTSpringServiceLayerDecorator extends ServiceLayerDecorator
implements ApplicationContextAware {
private ApplicationContext context;
@Override
public <T extends Locator<?, ?>> T createLocator(Class<T> clazz) {
return context.getBean(clazz);
}
@Override
public Object createServiceInstance(Method contextMethod
Method domainMethod) {
// Check if the request needs a service locator
Class<? extends ServiceLocator> locatorType = getTop()
.resolveServiceLocator(contextMethod, domainMethod);
assert locatorType != null;
// Inject an instance of the locator itself,
// and then get an instance from it
ServiceLocator locator = context.getBean(locatorType);
return locator.getInstance(domainMethod.getDeclaringClass());
}
@Override
public Object invoke(Method domainMethod, Object... args) {
Object response = super.invoke(domainMethod, args);
try {
if (response instanceof Callable) {
return ((Callable<?>) response).call();
} else {
return response;
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public void setApplicationContext(ApplicationContext context)
throws BeansException {
this.context = context;
}
}
GWTSpringEntityLocator will be used to locate JPA entities managed by Spring configuration files.
public class GWTSpringEntityLocator<T extends HasVersionAndId> extends
Locator<T, Long> {
@Autowired
EntityManager entityManager;
@Override
public T create(Class<? extends T> clazz) {
try
{
return clazz.newInstance();
} catch (InstantiationException e)
{
throw new RuntimeException(e);
} catch (IllegalAccessException e)
{
throw new RuntimeException(e);
}
}
@Override
public T find(Class<? extends T> clazz, Long id) {
return entityManager.find(clazz, id);
}
@Override
public Class<T> getDomainType() {
// Unused, and if it becomes used, we're in trouble
throw new UnsupportedOperationException();
}
@Override
public Long getId(T domainObject) {
return domainObject.getId();
}
@Override
public Class<Long> getIdType() {
return Long.class;
}
@Override
public Object getVersion(T domainObject) {
return domainObject.getVersion();
}
}
GWTSpringServiceLocator will be used to locate services managed by Spring configuration files.
public class GWTSpringServiceLocator implements ServiceLocator,
ApplicationContextAware {
private ApplicationContext context;
public Object getInstance(Class<?> clazz) {
return context.getBean(clazz);
}
public void setApplicationContext(ApplicationContext context)
throws BeansException {
this.context = context;
}
}
STEP 4
In this step, we’ll implement a hack that I have created which will manage our RequestFactoryServlet (i.e. Spring will manage our RequestFactoryServlet) containing our customServiceLayerDecorator implementation.
CustomServletWrappingController
Please note that this is an initial hack and I am sure there is room for improvement. The negative side of this hack is we will have to pretty much rewrite the entire ServletWrappingController class as it’s animmutable class. The “afterPropertiesSet” method is where my hack is implemented. The stock implementation of this method creates an instance of a servlet defined in the Spring configuration file. But in my hack I’ve replaced the following line:
this .servletInstance = (Servlet) this .servletClass.newInstance();
to
this.servletInstance = (Servlet) this.getApplicationContext().getBean("requestFactoryServlet");
IMPORTANT NOTE: So as you can see, we are not using the servlet class defined in our Spring configuration file and instead hard coding the Servlet that we intend to use. Clearly, the downside of this approach is your CustomServletWrappingController can only manage one servlet i.e. your RequestFactoryServlet.
Hey but this works!
public class CustomServletWrappingController extends ServletWrappingController {
private Class servletClass;
private String servletName;
private Properties initParameters = new Properties();
private String beanName;
private Servlet servletInstance;
/**
* Set the class of the servlet to wrap. Needs to implement
* <code>javax.servlet.Servlet</code>.
*
* @see javax.servlet.Servlet
*/
public void setServletClass(Class servletClass) {
this.servletClass = servletClass;
}
/**
* Set the name of the servlet to wrap. Default is the bean name of this
* controller.
*/
public void setServletName(String servletName) {
this.servletName = servletName;
}
/**
* Specify init parameters for the servlet to wrap, as name-value pairs.
*/
public void setInitParameters(Properties initParameters) {
this.initParameters = initParameters;
}
public void setBeanName(String name) {
this.beanName = name;
}
/**
* Overriden to implement our hack. It initializes the wrapped Servlet
* instance.
*
* @author jatinder-singh
* @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
*/
@Override
public void afterPropertiesSet() throws Exception {
if (this.servletClass == null) {
throw new IllegalArgumentException("servletClass is required");
}
if (!Servlet.class.isAssignableFrom(this.servletClass)) {
throw new IllegalArgumentException("servletClass ["
+ this.servletClass.getName()
+ "] needs to implement interface [javax.servlet.Servlet]");
}
if (this.servletName == null) {
this.servletName = this.beanName;
}
// We retrieve RF instance instead of using the one defined in
// singhsolution-servlet.xml
// Author: Jatinder Singh
this.servletInstance = (Servlet) this.getApplicationContext().getBean(
"requestFactoryServlet");
this.servletInstance.init(new DelegatingServletConfig());
}
/**
* Invoke the the wrapped Servlet instance.
*
* @see javax.servlet.Servlet#service(javax.servlet.ServletRequest,
* javax.servlet.ServletResponse)
*/
@Override
protected ModelAndView handleRequestInternal(HttpServletRequest request,
HttpServletResponse response) throws Exception {
this.servletInstance.service(request, response);
return null;
}
/**
* Destroy the wrapped Servlet instance.
*
* @see javax.servlet.Servlet#destroy()
*/
public void destroy() {
this.servletInstance.destroy();
}
/**
* Internal implementation of the ServletConfig interface, to be passed to
* the wrapped servlet. Delegates to ServletWrappingController fields and
* methods to provide init parameters and other environment info.
*/
private class DelegatingServletConfig implements ServletConfig {
public String getServletName() {
return servletName;
}
public ServletContext getServletContext() {
return CustomServletWrappingController.this.getServletContext();
}
public String getInitParameter(String paramName) {
return initParameters.getProperty(paramName);
}
public Enumeration getInitParameterNames() {
return initParameters.keys();
}
}
}
If you are here you are pretty much there
. Now edit your singhsolution-servlet.xml file to include all the beans you have created above.
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"> <bean> <property name="mappings"> <value> /gwtRequest=gwtRequestFactoryController </value> </property> </bean> <bean id="gwtSpringServiceLayerDecorator" class="com.jsi.singhsolution.server.servlet.GWTSpringServiceLayerDecorator" /> <bean id="defaultExceptionHandler" class="com.google.gwt.requestfactory.server.DefaultExceptionHandler" /> <!-- RequestFactoryServlet bean is scoped as singleton. Injecting custom SpringServiceLayerDecorator. --> <bean id="requestFactoryServlet" class="com.google.gwt.requestfactory.server.RequestFactoryServlet" scope="singleton"> <constructor-arg ref="defaultExceptionHandler" /> <constructor-arg ref="gwtSpringServiceLayerDecorator" /> </bean> <bean id="gwtSpringEntityLocator" class="com.jsi.singhsolution.server.servlet.GWTSpringEntityLocator" /> <bean id="gwtSpringServiceLocator" class="com.jsi.singhsolution.server.servlet.GWTSpringServiceLocator" /> <!-- IMPORTANT NOTE: CustomServletWrappingController is a hacked version of ServletWrappingController. The servlet class defined here will not be used. Please see CustomServletWrappingController for more information. --> <bean id="gwtRequestFactoryController" class="com.jsi.singhsolution.server.servlet.CustomServletWrappingController"> <property name="servletClass"> <value>com.google.gwt.requestfactory.server.RequestFactoryServlet </value> </property> <property name="servletName"> <value>gwtRequest</value> </property> </bean> </beans>
If you have followed my instructions carefully, you have created all the required classes including the hack that is needed for Spring to manage RequestFactoryServlet in its machinery.
RequestFactory in Action
I am going to assume you know a little bit of GWT. The below code will end up calling a Spring Service called UserDetailsService. Once the user is returned successfully we display its first name.
requestFactory.userDetailsRequest().getUserByUsername("singh").fire( new Receiver<UserEntityProxy>() {
@Override
public void onSuccess(UserEntityProxy user) {
RootPanel.get("result").add(new HTML("Username was retrieved: " + user.getFirstName()));
}
});
How this hack can be improved?
If I could inject my custom ServiceLayerDecorator class in the RequestFactoryServlet class defined in the actual ServletWrappingController bean, will solve our issue of hardcoding the Servlet; and we won’t need a CustomServletWrappingController that we have currently in place.
I don’t really have a code sample for this yet but I may create one in future. If you think the above code can be improved, please drop a comment or email me at j.singh.developer@gmail.com.
A somewhat simpler solution that I came across (and this is pretty much out of the box in 2.4).
Extend RequestFactoryServlet and add a ThreadLocal to the class, set it in the onPost method.
For an example, here is the trunk version of RequestFactoryServlet.
http://code.google.com/p/google-web-toolkit/source/browse/trunk/user/src/com/google/web/bindery/requestfactory/server/RequestFactoryServlet.java
Then your SpringServiceLocator is relatively simple.
Then you just need to
public class SpringServiceLocator implements ServiceLocator {
public Object getInstance(Class arg0) {
ApplicationContext ctx = WebApplicationContextUtils
.getWebApplicationContext(RequestFactoryServlet.getThreadLocalServletContext());
return ctx.getBean(arg0);
}
}
Then in every RequestContext you just have to specify locator=SpringServiceLocator.class and you’re set.
There might be a defiency here in that RequestFactory doesn’t actually use Spring, There might be some deficiencies here and if so I would love to hear them, but so far this has been working fine in our implementation.
@Jeff Larsen
Even i ‘m using the same solution. And it works like a charm,, so far i’ve not encountered any deficiencies.. and the best part is I don’t have to write a dispatcher servlet.
Thanks,
Good stuff guys. Will try and edit this post for new info. Thanks
Great post! took me 30 minutes to set is up.
I’ve got 2 questions tho:
1. Using Jeff Larsen’s tip, is there any other need in CustomServletWrappingController?
2. What are the best practices of unit-testing RF services calling to Spring?
I tried to integrate this sample by using GWT2.4. I got some issues regarding GWTSpringServiceLayerDecorator. In 2.4, the api changed to public Object createServiceInstance(Class requestContext) {..}
Could you provide a impl for this ?