/*!
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved.
*/
package org.pentaho.platform.web.servlet;
import com.google.gwt.user.client.rpc.IncompatibleRemoteServiceException;
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 com.google.gwt.user.server.rpc.impl.StandardSerializationPolicy;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.pentaho.platform.web.servlet.messages.Messages;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
@SuppressWarnings( "serial" )
/**
* Base class for GWT RPC proxying servlets, which allows developers to write GWT Services as Pojos and have
* the GWT client POST requests to subclasses of this class.
*/
public abstract class AbstractGwtRpcProxyServlet extends RemoteServiceServlet {
private static final Log logger = LogFactory.getLog( AbstractGwtRpcProxyServlet.class );
public AbstractGwtRpcProxyServlet() {
super();
}
/**
* Resolve the target impl that ultimately handles the GWT RPC request, provided a key.
*
* @param servletContextPath
* the portion of the http request path beyond the servlet context, used to uniquely identify the target impl
* @return the object that handles the request
* @throws GwtRpcProxyException
* if no target can be found
*/
protected abstract Object resolveDispatchTarget( String servletContextPath );
protected void doUnexpectedFailure( Throwable e ) {
super.doUnexpectedFailure( e );
logger.error( e );
}
/**
* Returns the dispatch key for this request. This name is the part of the request path beyond the servlet base path.
* I.e. if the GwtRpcPluginProxyServlet is mapped to the "/gwtrpc" context in web.xml, then this method will return
* "testservice" upon a request to "http://localhost:8080/pentaho/gwtrpc/testservice".
*
* @return the part of the request url used to dispatch the request
*/
protected String getDispatchKey() {
HttpServletRequest request = getThreadLocalRequest();
// path info will give us what we want with
String requestPathInfo = request.getPathInfo();
if ( requestPathInfo.startsWith( "/" ) ) { //$NON-NLS-1$
requestPathInfo = requestPathInfo.substring( 1 );
}
if ( requestPathInfo.contains( "/" ) ) { //$NON-NLS-1$
// if the request path happens to be multiple levels deep, return the last element in the path
String[] elements = requestPathInfo.split( "/" ); //$NON-NLS-1$
return elements[elements.length - 1];
}
return requestPathInfo;
}
@SuppressWarnings( "nls" )
protected String getServletContextPath() {
HttpServletRequest request = getThreadLocalRequest();
String path = request.getServletPath() + "/" + request.getPathInfo();
if ( path.contains( "//" ) ) {
path = path.replaceAll( "//", "/" );
}
return path;
}
@Override
public String processCall( String payload ) throws SerializationException {
Map<Class<?>, Boolean> whitelist = new HashMap<Class<?>, Boolean>();
whitelist.put( GwtRpcProxyException.class, Boolean.TRUE );
Map<Class<?>, String> obfuscatedTypeIds = new HashMap<Class<?>, String>();
StandardSerializationPolicy policy = new StandardSerializationPolicy( whitelist, whitelist, obfuscatedTypeIds );
String servletContextPath = getServletContextPath();
Object target = null;
try {
target = resolveDispatchTarget( servletContextPath );
} catch ( GwtRpcProxyException ex ) {
logger.error( Messages.getInstance().getErrorString(
"AbstractGwtRpcProxyServlet.ERROR_0001_FAILED_TO_RESOLVE_DISPATCH_TARGET", servletContextPath ), ex ); //$NON-NLS-1$
return RPC.encodeResponseForFailure( null, ex, policy );
}
final ClassLoader origLoader = Thread.currentThread().getContextClassLoader();
final ClassLoader altLoader = target.getClass().getClassLoader();
try {
// temporarily swap out the context classloader to an alternate classloader if
// the targetBean has been loaded by one other than the context classloader.
// This is necessary, so the RPC class can do a Class.forName and find the service
// class specified in the request
if ( altLoader != origLoader ) {
Thread.currentThread().setContextClassLoader( altLoader );
}
RPCRequest rpcRequest = RPC.decodeRequest( payload, null, this );
onAfterRequestDeserialized( rpcRequest );
// don't require the server side to implement the service interface
Method method = rpcRequest.getMethod();
try {
Method m = target.getClass().getMethod( method.getName(), method.getParameterTypes() );
if ( m != null ) {
method = m;
}
} catch ( Exception e ) {
e.printStackTrace();
}
return RPC.invokeAndEncodeResponse( target, method, rpcRequest.getParameters(), rpcRequest
.getSerializationPolicy() );
} catch ( IncompatibleRemoteServiceException ex ) {
logger.error( Messages.getInstance().getErrorString(
"AbstractGwtRpcProxyServlet.ERROR_0003_RPC_INVOCATION_FAILED", target.getClass().getName() ), ex ); //$NON-NLS-1$
return RPC.encodeResponseForFailure( null, ex );
} finally {
// reset the context classloader if necessary
if ( ( altLoader != origLoader ) && origLoader != null ) {
Thread.currentThread().setContextClassLoader( origLoader );
}
}
}
}