Monday, April 19, 2010

Spring-managed GWT Remote Service: exposing Spring bean as GWT RPC

Update: there are easier and much easier ways of doing this.
I want to publish simple solution to expose Spring Bean as Google Web Toolkit Remote Service.
There are some solutions around but they are hard to configure, heavy-weight and often do not provide functionality required to use AOP-based annotations on service methods (like Spring Security @Secured) but I prefer minimal XML configuration (In this example there are only few lines of XML), annotation-driven configuration and functionality without any limitations.

So it is needed to create

a) Spring-managed servlet that will handle RPC calls
b) Remote Service common logic implementation
c) Simple GWT RPC Example

Spring servlet to handle RPC calls

package com.example.server.service;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.stereotype.Component;
import org.springframework.web.HttpRequestHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;

@Component("gwt-request-handler")
public class RemoteServiceHandler implements HttpRequestHandler, ApplicationContextAware {
    private ApplicationContext applicationContext;

    public void handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws ServletException, IOException {
        String requestPath = httpServletRequest.getServletPath();
        @SuppressWarnings({"unchecked"})
        Map<String, RemoteService> services = applicationContext.getBeansOfType(RemoteService.class);
        RemoteService service = null;
        for (String key : services.keySet()) {
            if (requestPath.endsWith(key)) {
                if (service != null) {
                    throw new IllegalStateException("More than one GWT RPC service bean matches request: " + requestPath);
                } else {
                    service = services.get(key);
                }
            }
        }
        if (service == null) {
            String availableServlets = "";
            for (String key : services.keySet()) {
                availableServlets += key + " -> " + services.get(key).getClass().getName() + "\n";
            }
            throw new IllegalStateException("Cannot find GWT RPC service bean for request: " + requestPath + "\nMake sure that service implementation extends RemoteServiceImpl instead of GWT RemoteServiceServlet\nList of available beans:\n" + availableServlets);
        }
        service.doPost(service, httpServletRequest, httpServletResponse);
    }

    public void setApplicationContext(ApplicationContext applicationContext)
            throws BeansException {
        this.applicationContext = applicationContext;
    }
}

it should be configured as servlet in web.xml file:

<servlet>
        <servlet-name>gwt-request-handler</servlet-name>
        <servlet-class>org.springframework.web.context.support.HttpRequestHandlerServlet</servlet-class>
    </servlet>
    <servlet-mapping>
        <servlet-name>gwt-request-handler</servlet-name>
        <url-pattern>*.gwtrpc</url-pattern>
    </servlet-mapping>

Spring HttpRequestHandlerServlet will try to locate bean with name same as servlet name and use it to handle all requests matching specified url-pattern.

Remote Service common implementation


This class will take care of ServletContext because these beans are not actually servlets but GWT requires ServletContext to work.
Also it invokes remote methods in a way that allows AOP annotations to work.
Interface is needed to correct work with AOP based annotations, like @Transactional or @Secured

package com.example.server.service;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public interface RemoteService {
    void doPost(RemoteService service, HttpServletRequest request, HttpServletResponse response);

}

package com.example.server.service;

import com.google.gwt.user.server.rpc.RemoteServiceServlet;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public abstract class RemoteServiceImpl extends RemoteServiceServlet implements RemoteService {
    private RemoteService service = null;

    public void doPost(RemoteService service, HttpServletRequest request, HttpServletResponse response) {
        this.service = service;
        doPost(request, response);
    }

    @Override
    public ServletContext getServletContext() {
        ServletContext context;
        try {
            context = super.getServletContext();
        } catch (Exception e) {
            context = getThreadLocalRequest().getSession().getServletContext();
        }
        return context;
    }

    @Override
    public String processCall(final String payload) throws SerializationException {
        final RPCRequest request = RPC.decodeRequest(payload, this.getClass(), this);
        try {
            Object result = request.getMethod().invoke(service, request.getParameters());
            return RPC.encodeResponseForSuccess(request.getMethod(), result, request.getSerializationPolicy());
        } catch (IllegalAccessException e) {
            return RPC.encodeResponseForFailure(request.getMethod(), e, request.getSerializationPolicy());
        } catch (InvocationTargetException e) {
            Throwable cause = e.getCause();
            return RPC.encodeResponseForFailure(request.getMethod(), cause, request.getSerializationPolicy());
        } catch (IncompatibleRemoteServiceException ex) {
            return RPC.encodeResponseForFailure(null, ex, request.getSerializationPolicy());
        } finally {
            service = null;
        }
    }
}

Simple Remote Service

Here is implementation of very simple remote service managed by Spring.
You can use any AOP annotation and/or other spring features like autowiring without any limitation.
package com.example.client.service;

import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
import com.google.gwt.core.client.GWT;

@RemoteServiceRelativePath(TestService.URL)
public interface TestService extends RemoteService {
    public static final String URL = "services/TestService.gwtrpc";

    public String hello(String userName);

    public static class Instance {
        private static final TestServiceAsync instance = (TestServiceAsync) GWT.create(TestService.class);

        public static TestServiceAsync get() {
            return instance;
        }
    }
}

package com.example.server.service;

import com.example.client.service.TestService;
import org.springframework.stereotype.Component;

@Component(TestService.URL)
public class TestServiceImpl extends RemoteServiceImpl implements TestService {
    public String hello(String userName) {
        return "Hello " + userName;
    }
}

Spring context configuration


<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
    <context:component-scan base-package="com.example.server.service"/>
</beans>

Wednesday, April 7, 2010

Unique constraint on App Engine Datastore

Another challenge I faced on GAE Datastore is inability to use unique constraints. Sometimes it is absolutely needed to have such constraints and this is my case.

Following solution is not universal but it will help in many cases. Idea is to create entity primary key based on field(s) that should be unique.

Consider following example:
public class Link implements Serializable {
    private Key key;
    private String parentId;
    private String childId;
    //getters and setters
}
It is needed to force that only one link exist for given pair (parent, child) and it can be done in the following way:
@PersistenceCapable
public class Link implements Serializable, StoreCallback {
    @Persistent
    @PrimaryKey
    private Key key;
    private String parentId;
    private String childId;
    //getters and setters
    @Override
    public void jdoPreStore() {
        key = KeyFactory.createKey(Link.class.getSimpleName(), parentId + "::" + childId);
    }
}
Of course any divider can be used instead of "::". Actually there are slightly more complex rules to ensure that generated string is uniquely identifies parentId and childId pair but it is unrelated to topic.

Tuesday, April 6, 2010

Google App Engine datastore case insensitive queries

I started working on small App Engine based project and very soon noticed that there is no possibility to execute case insensitive queries on App Engine Datastore. This was absolute must-have for my application so I performed some googling and came with simple yet effective solution I want to share.

The idea is to store lower-case versions of fields that are used in case-insensitive queries.
So I have class Person like this one (simplified):
@PersistenceCapable
public class Person implements Serializable {
 @PrimaryKey
 @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
 private Key key;
 private String firstName;
 private String lastName;
 //other fields
 //getters and setters
}
and I need to search persons by first/last name ignoring case.
Class should be extended to look like this one:
@PersistenceCapable
public class Person implements Serializable, javax.jdo.listener.StoreCallback {
 @PrimaryKey
 @Persistent(valueStrategy = IdGeneratorStrategy.IDENTITY)
 private Key key;
 private String firstName;
 private String lastName;
 private String firstNameLC;
 private String lastNameLC;
 @Override
 public void jdoPreStore() {
  firstNameLC = firstName == null ? null : firstName.toLowerCase();
  lastNameLC = lastName == null ? null : lastName.toLowerCase();
 } 
 //other fields
 //getters and setters
}
This forces firstNameLC and lastNameLC fields to be filled with lowercase versions of firstName and lastName fields on every update.
Note that you don't need getters and setters for lowercase version fields.
Query can be performed like this one:
{
    PersistenceManagerFactory factory = JDOHelper.getPersistenceManagerFactory("transactions-optional");
    PersistenceManager pm = factory.getPersistenceManager();
    List<person> persons = (List<person>) pm.newQuery("SELECT FROM Person WHERE firstNameLC == name PARAMETERS String name").execute("jOhN".toLowerCase());
}

That's all

UPD: found same solution on GOOGLE APP ENGINE JAVA PERSISTENCE blog. I have no idea why I missed it before.

First post

I am planning to use this blog to share my know-hows, how-tos and simple hints related to Java  programming.
At present my English level is far from perfect and one of the aims of this blog to improve it.
So any corrections/suggestions/etc are welcome.