leponceau.org

Programming And Stuff, You Know The Thing…

Providing GWT RPC Service And Other Service Types Using The Same Interface Implementation

Posted at — Mar 7, 2013

The following is a set of files that describes the core techniques used to set up a GWT-RPC+SOAP server for the same set of webservice methods, ie. there is only one server side implementation driving both frontends, GWT-RPC and SOAP 1.1 (that latter one is driven by CXF). It is all wired up using Spring and annotations. There are two interfaces for the same implementation – a separate one for each frontend. These frontend interfaces serve as a method to configure the specific frontend implementation, ie. one could add a third interface to provide a CXF Rest frontend, or a SOAP frontend that is not of document literal wrapped style.

import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.SerializationException;
import com.google.gwt.user.server.rpc.RPC;
import com.google.gwt.user.server.rpc.RPCRequest;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.context.ServletContextAware;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

// http://code.google.com/p/google-web-toolkit-incubator/wiki/IntegratingWithSpring
@SuppressWarnings("serial")
public class GwtRpcController extends RemoteServiceServlet implements
        Controller, ServletContextAware {

    private ServletContext servletContext;

    private RemoteService remoteService;

    private Class remoteServiceClass;

    @Override
    public ModelAndView handleRequest(HttpServletRequest request,
            HttpServletResponse response) throws Exception {
        super.doPost(request, response);
        return null;
    }

    @Override
    public String processCall(String payload) throws SerializationException {
        try {

            RPCRequest rpcRequest = RPC.decodeRequest(payload,
                    this.remoteServiceClass);

            onAfterRequestDeserialized(rpcRequest);

            // delegate work to the spring injected service
            return RPC.invokeAndEncodeResponse(this.remoteService, rpcRequest
                    .getMethod(), rpcRequest.getParameters(), rpcRequest.getSerializationPolicy());
        } catch (IncompatibleRemoteServiceException ex) {
            getServletContext()
                    .log(
                            "An IncompatibleRemoteServiceException was thrown while processing this call.",
                            ex);
            return RPC.encodeResponseForFailure(null, ex);
        }
    }

    @Override
    public ServletContext getServletContext() {
        return servletContext;
    }
    @Override
    public void setServletContext(ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    public void setRemoteService(RemoteService remoteService) {
        this.remoteService = remoteService;
        this.remoteServiceClass = this.remoteService.getClass();
    }

}
import com.google.gwt.user.client.rpc.RemoteService;
import com.google.gwt.user.client.rpc.RemoteServiceRelativePath;
import java.util.ArrayList;
import java.util.List;

@RemoteServiceRelativePath("")
public interface GWTService extends RemoteService {
    String greetme(String username);
}
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.jws.soap.SOAPBinding;
import javax.jws.soap.SOAPBinding.ParameterStyle;
import javax.jws.soap.SOAPBinding.Style;
import javax.jws.soap.SOAPBinding.Use;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlElementWrapper;

@WebService
@SOAPBinding(style = Style.DOCUMENT, use = Use.LITERAL, parameterStyle = ParameterStyle.WRAPPED)

public interface CXFSoapService {
    @XmlElement(required=true)
    String greetme(
            @WebParam(name="userName")
            @XmlElement(nillable=false, required=true)
                    String userName
            );
}
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.jws.WebService;

@SuppressWarnings("serial")
@WebService(endpointInterface = "my.package.CXFSoapService")
@Service
@Transactional
@Lazy(true)
public class SpringServiceImpl implements GWTService, CXFSoapService {
    @Override
    public String greetme(String username) {
        return "Hello, " + username + "!";
    }
}

web.xml:

<web-app version="2.4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/j2ee" xsi:schemalocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
    <display-name>My APP Server</display-name>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:application-context.xml</param-value>
    </context-param>

    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <listener>
        <listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
    </listener>

    <!-- http://technophiliac.wordpress.com/2008/08/24/giving-gwt-a-spring-in-its-step/ -->
    <!-- Initialise the Spring MVC DispatcherServlet -->
    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- Map the DispatcherServlet to only intercept GWT RPC requests -->
    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/gwtrpc</url-pattern>
    </servlet-mapping>

    <!-- CXF JAX-WS SOAP 1.1 -->
    <servlet>
        <servlet-name>CXFServlet</servlet-name>
        <servlet-class>org.apache.cxf.transport.servlet.CXFServlet</servlet-class>
        <load-on-startup>2</load-on-startup>
        <!--async-supported>true</async-supported-->
    </servlet>
    <servlet-mapping>
        <servlet-name>CXFServlet</servlet-name>
        <url-pattern>/soap/*</url-pattern>
    </servlet-mapping>
</web-app>

application-context.xml:

<beans default-lazy-init="true" xmlns:aop="http://www.springframework.org/schema/aop" xmlns:beans="http://www.springframework.org/schema/beans" xmlns:context="http://www.springframework.org/schema/context" xmlns:http="http://cxf.apache.org/transports/http/configuration" xmlns:httpj="http://cxf.apache.org/transports/http-jetty/configuration" xmlns:jaxws="http://cxf.apache.org/jaxws" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/beans" xsi:schemalocation="
        http://cxf.apache.org/configuration/security
            http://cxf.apache.org/schemas/configuration/security.xsd
        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         
        http://cxf.apache.org/jaxws
            http://cxf.apache.org/schemas/jaxws.xsd
        http://cxf.apache.org/transports/http/configuration
            http://cxf.apache.org/schemas/configuration/http-conf.xsd
        http://cxf.apache.org/transports/http-jetty/configuration
            http://cxf.apache.org/schemas/configuration/http-jetty.xsd
        http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
        http://www.springframework.org/schema/tx 
            http://www.springframework.org/schema/tx/spring-tx-3.0.xsd            
             ">
    <import resource="classpath:META-INF/cxf/cxf.xml">
    <import resource="classpath:META-INF/cxf/cxf-servlet.xml">
    <import resource="application-properties.xml">
    <import resource="springldap.xml">

    <context:annotation-config>
    <context:component-scan base-package="my.package" scoped-proxy="interfaces">
    <aop:aspectj-autoproxy proxy-target-class="true">

    <!-- http://stackoverflow.com/questions/5022235/spring-aop-ordering-transactions-before-advise -->
    <tx:annotation-driven order="100" transaction-manager="txManager">

    <bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="txManager">
        <property name="dataSource" ref="jdbcDataSource">
    </property></bean>

    <bean class="my.package.JDBCDataSource" destroy-method="close" id="jdbcDataSource">

    <bean class="my.package.SpringServiceImpl" id="apiServ">
        <aop:scoped-proxy>
    </aop:scoped-proxy></bean>

     <jaxws:endpoint address="/" id="apiService" implementor="#apiServ">
         <jaxws:properties>
             <entry key="faultStackTraceEnabled" value="false">
             <entry key="exceptionMessageCauseEnabled" value="false">
             <entry key="org.apache.cxf.wsdl.create.imports" value="true">
             <entry key="schema-validation-enabled" value="true">
         </entry></entry></entry></entry></jaxws:properties>
         <jaxws:features>
             <bean class="org.apache.cxf.feature.LoggingFeature">
         </bean></jaxws:features>
     </jaxws:endpoint>
</bean></tx:annotation-driven></aop:aspectj-autoproxy></context:component-scan></context:annotation-config></import></import></import></import></beans>