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>

1 comment:

  1. This is best tutorial around there for integrating spring and gwt-rpc.

    it nearly just saved our project.

    We hunted down for days on how to reuse our existing services inside our gwt project. We was also looking for a way to secure it throught anotations of spring security we are already using and we found all those answer in your blog. So many thanks.

    Integration gwt with spring this way also allow you to use spring AOP instead of AspectJ. http://blog.maxmatveev.com/2011/06/gwt-and-spring-security-integration-as.html

    thanks a mile.

    ReplyDelete