[seam-commits] Seam SVN: r9547 - trunk/src/remoting/org/jboss/seam/remoting/gwt.
seam-commits at lists.jboss.org
seam-commits at lists.jboss.org
Tue Nov 11 02:24:02 EST 2008
Author: shane.bryzak at jboss.com
Date: 2008-11-11 02:24:02 -0500 (Tue, 11 Nov 2008)
New Revision: 9547
Added:
trunk/src/remoting/org/jboss/seam/remoting/gwt/SeamRPCRequest.java
Removed:
trunk/src/remoting/org/jboss/seam/remoting/gwt/GWT13Service.java
Modified:
trunk/src/remoting/org/jboss/seam/remoting/gwt/GWTService.java
Log:
JBSEAM-2933
Deleted: trunk/src/remoting/org/jboss/seam/remoting/gwt/GWT13Service.java
===================================================================
--- trunk/src/remoting/org/jboss/seam/remoting/gwt/GWT13Service.java 2008-11-11 05:52:25 UTC (rev 9546)
+++ trunk/src/remoting/org/jboss/seam/remoting/gwt/GWT13Service.java 2008-11-11 07:24:02 UTC (rev 9547)
@@ -1,107 +0,0 @@
-package org.jboss.seam.remoting.gwt;
-
-import static org.jboss.seam.ScopeType.APPLICATION;
-import static org.jboss.seam.annotations.Install.BUILT_IN;
-
-import java.lang.reflect.Constructor;
-
-import org.jboss.seam.annotations.Create;
-import org.jboss.seam.annotations.Install;
-import org.jboss.seam.annotations.Name;
-import org.jboss.seam.annotations.Scope;
-import org.jboss.seam.annotations.intercept.BypassInterceptors;
-
-import com.google.gwt.user.client.rpc.SerializationException;
-import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader;
-import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter;
-
-/**
- *
- * @author Shane Bryzak
- */
- at Scope(APPLICATION)
- at Name("org.jboss.seam.remoting.gwt.gwtRemoteService")
- at Install(value = false, precedence = BUILT_IN, classDependencies = {"com.google.gwt.user.client.rpc.RemoteService"})
- at BypassInterceptors
- at Deprecated
-public class GWT13Service extends GWTService
-{
- private static final String SERIALIZABLE_TYPE_CLASS = "com.google.gwt.user.server.rpc.impl.ServerSerializableTypeOracleImpl";
-
- private Object serializableTypeOracle;
-
- private Constructor streamReaderConstructor;
- private Constructor streamWriterConstructor;
-
- @Create
- public void startup() throws Exception
- {
- try
- {
- log.trace("GWT13Service starting up");
-
- Class serializableType = Class.forName(SERIALIZABLE_TYPE_CLASS);
- String[] packagePaths = getPackagePaths();
- Constructor typeConstructor = serializableType.getConstructor(new Class[] { packagePaths.getClass() });
-
- serializableTypeOracle = typeConstructor.newInstance((Object[]) packagePaths);
-
- streamReaderConstructor = ServerSerializationStreamReader.class.getConstructor(
- new Class[] { serializableType } );
- streamWriterConstructor = ServerSerializationStreamWriter.class.getConstructor(
- new Class[] { serializableType } );
- }
- catch (Exception ex)
- {
- log.error("Error initializing GWT13Service. Please ensure " +
- "the GWT 1.3 libraries are in the classpath.");
- throw ex;
- }
- }
-
- @Override
- protected String createResponse(ServerSerializationStreamWriter stream,
- Class responseType, Object responseObj, boolean isException)
- {
- stream.prepareToWrite();
- if (responseType != void.class)
- {
- try
- {
- stream.serializeValue(responseObj, responseType);
- } catch (SerializationException e)
- {
- responseObj = e;
- isException = true;
- }
- }
-
- return (isException ? "{EX}" : "{OK}") + stream.toString();
- }
-
- @Override
- public ServerSerializationStreamReader getStreamReader()
- {
- try
- {
- return (ServerSerializationStreamReader) streamReaderConstructor.newInstance(serializableTypeOracle);
- }
- catch (Exception ex)
- {
- throw new RuntimeException("Unable to create stream reader", ex);
- }
- }
-
- @Override
- public ServerSerializationStreamWriter getStreamWriter()
- {
- try
- {
- return (ServerSerializationStreamWriter) streamWriterConstructor.newInstance(serializableTypeOracle);
- }
- catch (Exception ex)
- {
- throw new RuntimeException("Unable to create stream writer", ex);
- }
- }
-}
Modified: trunk/src/remoting/org/jboss/seam/remoting/gwt/GWTService.java
===================================================================
--- trunk/src/remoting/org/jboss/seam/remoting/gwt/GWTService.java 2008-11-11 05:52:25 UTC (rev 9546)
+++ trunk/src/remoting/org/jboss/seam/remoting/gwt/GWTService.java 2008-11-11 07:24:02 UTC (rev 9547)
@@ -1,59 +1,55 @@
package org.jboss.seam.remoting.gwt;
-import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
-import java.io.UnsupportedEncodingException;
import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.text.ParseException;
+import java.util.Arrays;
import java.util.HashMap;
-import java.util.zip.GZIPOutputStream;
+import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
+import org.jboss.seam.core.ConversationPropagation;
import org.jboss.seam.log.LogProvider;
import org.jboss.seam.log.Logging;
-import org.jboss.seam.remoting.gwt.GWTToSeamAdapter.ReturnedObject;
import org.jboss.seam.servlet.ContextualHttpServletRequest;
import org.jboss.seam.web.AbstractResource;
-import com.google.gwt.user.client.rpc.SerializableException;
+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.RPCServletUtils;
+import com.google.gwt.user.server.rpc.RemoteServiceServlet;
+import com.google.gwt.user.server.rpc.SerializationPolicy;
+import com.google.gwt.user.server.rpc.SerializationPolicyLoader;
+import com.google.gwt.user.server.rpc.SerializationPolicyProvider;
+import com.google.gwt.user.server.rpc.UnexpectedException;
import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader;
import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter;
/**
- * Abstract base class for GWT integration.
+ * Abstract base class for GWT 1.5 integration.
*
* @author Shane Bryzak
*/
-public abstract class GWTService extends AbstractResource
+public abstract class GWTService extends AbstractResource implements SerializationPolicyProvider
{
protected static final LogProvider log = Logging.getLogProvider(GWTService.class);
-
- /*
- * These members are used to get and set the different HttpServletResponse
- * and HttpServletRequest headers.
- */
- private static final String ACCEPT_ENCODING = "Accept-Encoding";
- private static final String CHARSET_UTF8 = "UTF-8";
- private static final String CONTENT_ENCODING = "Content-Encoding";
- private static final String CONTENT_ENCODING_GZIP = "gzip";
- private static final String CONTENT_TYPE_TEXT_PLAIN_UTF8 = "text/plain; charset=utf-8";
- private static final String GENERIC_FAILURE_MSG = "The call failed on the server; see server log for details";
- private static final HashMap TYPE_NAMES;
- /**
- * Controls the compression threshold at and below which no compression will
- * take place.
- */
- private static final int UNCOMPRESSED_BYTE_SIZE_LIMIT = 256;
+ private static final HashMap<String, Class<?>> TYPE_NAMES;
static
{
- TYPE_NAMES = new HashMap();
+ TYPE_NAMES = new HashMap<String, Class<?>>();
TYPE_NAMES.put("Z", boolean.class);
TYPE_NAMES.put("B", byte.class);
TYPE_NAMES.put("C", char.class);
@@ -62,59 +58,38 @@
TYPE_NAMES.put("I", int.class);
TYPE_NAMES.put("J", long.class);
TYPE_NAMES.put("S", short.class);
+
}
+
+ /**
+ * A cache of moduleBaseURL and serialization policy strong name to
+ * {@link SerializationPolicy}.
+ */
+ private final Map<String, SerializationPolicy> serializationPolicyCache = new HashMap<String, SerializationPolicy>();
@Override
public String getResourcePath()
{
return "/gwt";
}
-
+
protected abstract ServerSerializationStreamReader getStreamReader();
+
protected abstract ServerSerializationStreamWriter getStreamWriter();
- protected abstract String createResponse(ServerSerializationStreamWriter stream,
- Class responseType, Object responseObj, boolean isException);
- /**
- * Return true if the response object accepts Gzip encoding. This is done by
- * checking that the accept-encoding header specifies gzip as a supported
- * encoding.
- */
- private static boolean acceptsGzipEncoding(HttpServletRequest request)
- {
- assert (request != null);
+ protected abstract String createResponse(
+ ServerSerializationStreamWriter stream, Class responseType,
+ Object responseObj, boolean isException);
- String acceptEncoding = request.getHeader(ACCEPT_ENCODING);
- if (null == acceptEncoding)
- {
- return false;
- }
-
- return (acceptEncoding.indexOf(CONTENT_ENCODING_GZIP) != -1);
- }
-
- /**
- * This method attempts to estimate the number of bytes that a string will
- * consume when it is sent out as part of an HttpServletResponse. This really
- * a hack since we are assuming that every character will consume two bytes
- * upon transmission. This is definitely not true since some characters
- * actually consume more than two bytes and some consume less. This is even
- * less accurate if the string is converted to UTF8. However, it does save us
- * from converting every string that we plan on sending back to UTF8 just to
- * determine that we should not compress it.
- */
- private static int estimateByteSize(final String buffer)
- {
- return (buffer.length() * 2);
- }
-
// private final Set knownImplementedInterfaces = new HashSet();
- private final ThreadLocal perThreadRequest = new ThreadLocal();
+ private final ThreadLocal<HttpServletRequest> perThreadRequest = new ThreadLocal<HttpServletRequest>();
- private final ThreadLocal perThreadResponse = new ThreadLocal();
+ private final ThreadLocal<HttpServletResponse> perThreadResponse = new ThreadLocal<HttpServletResponse>();
/**
* This is called internally.
+ *
+ * @see RemoteServiceServlet#doPost
*/
@Override
public final void getResource(final HttpServletRequest request,
@@ -132,44 +107,38 @@
public void process() throws Exception
{
- Throwable caught;
try
{
// Read the request fully.
//
- String requestPayload = readPayloadAsUtf8(request);
+ String requestPayload = RemoteServiceServlet_readContent(request);
+ RemoteServiceServlet_onBeforeRequestDeserialized(requestPayload);
+
// Invoke the core dispatching logic, which returns the
- // serialized
- // result.
- //
+ // serialized result
String responsePayload = processCall(requestPayload);
+ RemoteServiceServlet_onAfterResponseSerialized(responsePayload);
+
// Write the response.
//
- writeResponse(request, response, responsePayload);
- return;
- } catch (IOException e)
- {
- caught = e;
- } catch (ServletException e)
- {
- caught = e;
- } catch (SerializationException e)
- {
- caught = e;
+ RemoteServiceServlet_writeResponse(request, response,
+ responsePayload);
+
} catch (Throwable e)
{
- caught = e;
+ RemoteServiceServlet_doUnexpectedFailure(e);
}
- respondWithFailure(response, caught);
}
@Override
protected void restoreConversationId()
{
- // no conversation support for gwt requests
+ ConversationPropagation.instance().setConversationId(
+ GWTService.this.perThreadRequest.get().getParameter(
+ "conversationId"));
}
@Override
@@ -189,135 +158,420 @@
*/
public String processCall(String payload) throws SerializationException
{
-
- // Let subclasses see the serialized request.
- //
- onBeforeRequestDeserialized(payload);
-
// Create a stream to deserialize the request.
//
- ServerSerializationStreamReader streamReader = getStreamReader();
- streamReader.prepareToRead(payload);
-
- // Read the service interface
+ // ServerSerializationStreamReader streamReader = getStreamReader();
+ // streamReader.prepareToRead(payload);
//
- String serviceIntfName = streamReader.readString();
-
- // Read the method name.
+ // // Read the service interface
+ // //
+ // String serviceIntfName = streamReader.readString();
//
- String methodName = streamReader.readString();
+ // // Read the method name.
+ // //
+ // String methodName = streamReader.readString();
+ //
+ // // Read the number and names of the parameter classes from the stream.
+ // // We have to do this so that we can find the correct overload of the
+ // // method.
+ // //
+ // int paramCount = streamReader.readInt();
+ // Class[] paramTypes = new Class[paramCount];
+ // for (int i = 0; i < paramTypes.length; i++)
+ // {
+ // String paramClassName = streamReader.readString();
+ // try
+ // {
+ // paramTypes[i] = getClassOrPrimitiveFromName(paramClassName);
+ // } catch (ClassNotFoundException e)
+ // {
+ // throw new SerializationException("Unknown parameter " + i
+ // + " type '" + paramClassName + "'", e);
+ // }
+ // }
+ //
+ // // Deserialize the parameters.
+ // //
+ // Object[] args = new Object[paramCount];
+ // for (int i = 0; i < args.length; i++)
+ // {
+ // args[i] = streamReader.deserializeValue(paramTypes[i]);
+ // }
- // Read the number and names of the parameter classes from the stream.
- // We have to do this so that we can find the correct overload of the
- // method.
- //
- int paramCount = streamReader.readInt();
- Class[] paramTypes = new Class[paramCount];
- for (int i = 0; i < paramTypes.length; i++)
+ try
{
- String paramClassName = streamReader.readString();
- try
- {
- paramTypes[i] = getClassOrPrimitiveFromName(paramClassName);
- } catch (ClassNotFoundException e)
- {
- throw new SerializationException("Unknown parameter " + i
- + " type '" + paramClassName + "'", e);
- }
+ SeamRPCRequest rpcRequest = RPC_decodeRequest(payload,
+ this.getClass(), this);
+
+ return RPC_invokeAndEncodeResponse(this, rpcRequest.getMethod(),
+ rpcRequest.getParameterTypes(), rpcRequest.getParameters(),
+ rpcRequest.getSerializationPolicy());
+ } catch (IncompatibleRemoteServiceException ex)
+ {
+ getServletContext()
+ .log(
+ "An IncompatibleRemoteServiceException was thrown while processing this call.",
+ ex);
+ return RPC.encodeResponseForFailure(null, ex);
}
- // Deserialize the parameters.
+ // Make the call via reflection.
//
- Object[] args = new Object[paramCount];
- for (int i = 0; i < args.length; i++)
+ // String responsePayload = GENERIC_FAILURE_MSG;
+ // ServerSerializationStreamWriter streamWriter = getStreamWriter();
+ // Throwable caught = null;
+ // try
+ // {
+ // GWTToSeamAdapter.ReturnedObject returnedObject =
+ // adapter.callWebRemoteMethod(
+ // serviceIntfName, methodName, paramTypes, args);
+ // Class returnType = returnedObject.returnType;
+ // Object returnVal = returnedObject.returnedObject;
+ // // Class returnType = serviceIntfMethod.getReturnType();
+ // // Object returnVal = serviceIntfMethod.invoke(this, args);
+ // responsePayload = createResponse(streamWriter, returnType, returnVal,
+ // false);
+ // } catch (IllegalArgumentException e)
+ // {
+ // caught = e;
+ // } catch (IllegalAccessException e)
+ // {
+ // caught = e;
+ // } catch (InvocationTargetException e)
+ // {
+ // // Try to serialize the caught exception if the client is expecting it,
+ // // otherwise log the exception server-side.
+ // caught = e;
+ // Throwable cause = e.getCause();
+ // if (cause != null)
+ // {
+ // // Update the caught exception to the underlying cause
+ // caught = cause;
+ // // Serialize the exception back to the client if it's a declared
+ // // exception
+ // if (cause instanceof SerializableException)
+ // {
+ // Class thrownClass = cause.getClass();
+ // responsePayload = createResponse(streamWriter, thrownClass,
+ // cause, true);
+ // // Don't log the exception on the server
+ // caught = null;
+ // }
+ // }
+ // }
+ //
+ // if (caught != null)
+ // {
+ // responsePayload = GENERIC_FAILURE_MSG;
+ // ServletContext servletContext = getServletContext();
+ // // servletContext may be null (for example, when unit testing)
+ // if (servletContext != null)
+ // {
+ // // Log the exception server side
+ // servletContext.log("Exception while dispatching incoming RPC call",
+ // caught);
+ // }
+ // }
+ }
+
+ /**
+ * Gets the <code>HttpServletRequest</code> object for the current call. It
+ * is stored thread-locally so that simultaneous invocations can have
+ * different request objects.
+ */
+ protected final HttpServletRequest getThreadLocalRequest()
+ {
+ return perThreadRequest.get();
+ }
+
+ /**
+ * Gets the <code>HttpServletResponse</code> object for the current call. It
+ * is stored thread-locally so that simultaneous invocations can have
+ * different response objects.
+ */
+ protected final HttpServletResponse getThreadLocalResponse()
+ {
+ return perThreadResponse.get();
+ }
+
+ /**
+ * Returns an {@link RPCRequest} that is built by decoding the contents of an
+ * encoded RPC request and optionally validating that type can handle the
+ * request. If the type parameter is not <code>null</code>, the
+ * implementation checks that the type is assignable to the
+ * {@link com.google.gwt.user.client.rpc.RemoteService} interface requested
+ * in the encoded request string.
+ *
+ * <p>
+ * If the serializationPolicyProvider parameter is not <code>null</code>, it
+ * is asked for a {@link SerializationPolicy} to use to restrict the set of
+ * types that can be decoded from the request. If this parameter is
+ * <code>null</code>, then only subtypes of
+ * {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or
+ * types which have custom field serializers can be decoded.
+ * </p>
+ *
+ * <p>
+ * Invoking this method with <code>null</code> for the type parameter,
+ * <code>decodeRequest(encodedRequest, null)</code>, is equivalent to calling
+ * <code>decodeRequest(encodedRequest)</code>.
+ * </p>
+ *
+ * @param encodedRequest
+ * a string that encodes the
+ * {@link com.google.gwt.user.client.rpc.RemoteService} interface,
+ * the service method, and the arguments to pass to the service
+ * method
+ * @param type
+ * if not <code>null</code>, the implementation checks that the
+ * type is assignable to the
+ * {@link com.google.gwt.user.client.rpc.RemoteService} interface
+ * encoded in the encoded request string.
+ * @param serializationPolicyProvider
+ * if not <code>null</code>, the implementation asks this provider
+ * for a {@link SerializationPolicy} which will be used to restrict
+ * the set of types that can be decoded from this request
+ * @return an {@link RPCRequest} instance
+ *
+ * @throws NullPointerException
+ * if the encodedRequest is <code>null</code>
+ * @throws IllegalArgumentException
+ * if the encodedRequest is an empty string
+ * @throws IncompatibleRemoteServiceException
+ * if any of the following conditions apply:
+ * <ul>
+ * <li>if the types in the encoded request cannot be deserialized</li>
+ * <li>if the {@link ClassLoader} acquired from
+ * <code>Thread.currentThread().getContextClassLoader()</code>
+ * cannot load the service interface or any of the types specified
+ * in the encodedRequest</li>
+ * <li>the requested interface is not assignable to
+ * {@link com.google.gwt.user.client.rpc.RemoteService}</li>
+ * <li>the service method requested in the encodedRequest is not a
+ * member of the requested service interface</li>
+ * <li>the type parameter is not <code>null</code> and is not
+ * assignable to the requested
+ * {@link com.google.gwt.user.client.rpc.RemoteService} interface
+ * </ul>
+ */
+ public static SeamRPCRequest RPC_decodeRequest(String encodedRequest,
+ Class<?> type, SerializationPolicyProvider serializationPolicyProvider)
+ {
+ if (encodedRequest == null)
{
- args[i] = streamReader.deserializeValue(paramTypes[i]);
+ throw new NullPointerException("encodedRequest cannot be null");
}
- GWTToSeamAdapter adapter = GWTToSeamAdapter.instance();
+ if (encodedRequest.length() == 0)
+ {
+ throw new IllegalArgumentException("encodedRequest cannot be empty");
+ }
- // Make the call via reflection.
- //
- String responsePayload = GENERIC_FAILURE_MSG;
- ServerSerializationStreamWriter streamWriter = getStreamWriter();
- Throwable caught = null;
+ ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+
try
{
- ReturnedObject returnedObject = adapter.callWebRemoteMethod(
- serviceIntfName, methodName, paramTypes, args);
- Class returnType = returnedObject.returnType;
- Object returnVal = returnedObject.returnedObject;
- // Class returnType = serviceIntfMethod.getReturnType();
- // Object returnVal = serviceIntfMethod.invoke(this, args);
- responsePayload = createResponse(streamWriter, returnType, returnVal,
- false);
- } catch (IllegalArgumentException e)
- {
- caught = e;
- } catch (IllegalAccessException e)
- {
- caught = e;
- } catch (InvocationTargetException e)
- {
- // Try to serialize the caught exception if the client is expecting it,
- // otherwise log the exception server-side.
- caught = e;
- Throwable cause = e.getCause();
- if (cause != null)
+ ServerSerializationStreamReader streamReader = new ServerSerializationStreamReader(
+ classLoader, serializationPolicyProvider);
+ streamReader.prepareToRead(encodedRequest);
+
+ // Read the name of the RemoteService interface
+ String serviceIntfName = streamReader.readString();
+
+ /*
+ * todo?? if (type != null) { if (!implementsInterface(type,
+ * serviceIntfName)) { // The service does not implement the requested
+ * interface throw new IncompatibleRemoteServiceException(
+ * "Blocked attempt to access interface '" + serviceIntfName +
+ * "', which is not implemented by '" + printTypeName(type) +
+ * "'; this is either misconfiguration or a hack attempt"); } }
+ */
+
+ SerializationPolicy serializationPolicy = streamReader
+ .getSerializationPolicy();
+ Class<?> serviceIntf;
+ try
{
- // Update the caught exception to the underlying cause
- caught = cause;
- // Serialize the exception back to the client if it's a declared
- // exception
- if (cause instanceof SerializableException)
+ serviceIntf = RPC_getClassFromSerializedName(serviceIntfName,
+ classLoader);
+ if (!RemoteService.class.isAssignableFrom(serviceIntf))
{
- Class thrownClass = cause.getClass();
- responsePayload = createResponse(streamWriter, thrownClass,
- cause, true);
- // Don't log the exception on the server
- caught = null;
+ // The requested interface is not a RemoteService interface
+ throw new IncompatibleRemoteServiceException(
+ "Blocked attempt to access interface '"
+ + printTypeName(serviceIntf)
+ + "', which doesn't extend RemoteService; this is either misconfiguration or a hack attempt");
}
+ } catch (ClassNotFoundException e)
+ {
+ throw new IncompatibleRemoteServiceException(
+ "Could not locate requested interface '" + serviceIntfName
+ + "' in default classloader", e);
}
- }
- if (caught != null)
- {
- responsePayload = GENERIC_FAILURE_MSG;
- ServletContext servletContext = getServletContext();
- // servletContext may be null (for example, when unit testing)
- if (servletContext != null)
+ String serviceMethodName = streamReader.readString();
+
+ int paramCount = streamReader.readInt();
+ Class<?>[] parameterTypes = new Class[paramCount];
+
+ for (int i = 0; i < parameterTypes.length; i++)
{
- // Log the exception server side
- servletContext.log("Exception while dispatching incoming RPC call",
- caught);
+ String paramClassName = streamReader.readString();
+ try
+ {
+ parameterTypes[i] = RPC_getClassFromSerializedName(
+ paramClassName, classLoader);
+ } catch (ClassNotFoundException e)
+ {
+ throw new IncompatibleRemoteServiceException("Parameter " + i
+ + " of is of an unknown type '" + paramClassName + "'", e);
+ }
}
- }
- // Let subclasses see the serialized response.
- //
- onAfterResponseSerialized(responsePayload);
+ try
+ {
+ Method method = serviceIntf.getMethod(serviceMethodName,
+ parameterTypes);
- return responsePayload;
+ Object[] parameterValues = new Object[parameterTypes.length];
+ for (int i = 0; i < parameterValues.length; i++)
+ {
+ parameterValues[i] = streamReader
+ .deserializeValue(parameterTypes[i]);
+ }
+
+ return new SeamRPCRequest(method, parameterValues, parameterTypes,
+ serializationPolicy);
+
+ } catch (NoSuchMethodException e)
+ {
+ throw new IncompatibleRemoteServiceException(
+ formatMethodNotFoundErrorMessage(serviceIntf,
+ serviceMethodName, parameterTypes));
+ }
+ } catch (SerializationException ex)
+ {
+ throw new IncompatibleRemoteServiceException(ex.getMessage(), ex);
+ }
}
/**
- * Gets the <code>HttpServletRequest</code> object for the current call. It
- * is stored thread-locally so that simultaneous invocations can have
- * different request objects.
+ * Returns the {@link Class} instance for the named class or primitive type.
+ *
+ * @param serializedName
+ * the serialized name of a class or primitive type
+ * @param classLoader
+ * the classLoader used to load {@link Class}es
+ * @return Class instance for the given type name
+ * @throws ClassNotFoundException
+ * if the named type was not found
*/
- protected final HttpServletRequest getThreadLocalRequest()
+ private static Class<?> RPC_getClassFromSerializedName(
+ String serializedName, ClassLoader classLoader)
+ throws ClassNotFoundException
{
- return (HttpServletRequest) perThreadRequest.get();
+ Class<?> value = TYPE_NAMES.get(serializedName);
+ if (value != null)
+ {
+ return value;
+ }
+
+ return Class.forName(serializedName, false, classLoader);
}
/**
- * Gets the <code>HttpServletResponse</code> object for the current call.
- * It is stored thread-locally so that simultaneous invocations can have
- * different response objects.
+ * Returns a string that encodes the result of calling a service method,
+ * which could be the value returned by the method or an exception thrown by
+ * it.
+ *
+ * <p>
+ * If the serializationPolicy parameter is not <code>null</code>, it is used
+ * to determine what types can be encoded as part of this response. If this
+ * parameter is <code>null</code>, then only subtypes of
+ * {@link com.google.gwt.user.client.rpc.IsSerializable IsSerializable} or
+ * types which have custom field serializers may be encoded.
+ * </p>
+ *
+ * <p>
+ * This method does no security checking; security checking must be done on
+ * the method prior to this invocation.
+ * </p>
+ *
+ * @param target
+ * instance on which to invoke the serviceMethod
+ * @param serviceMethod
+ * the method to invoke
+ * @param args
+ * arguments used for the method invocation
+ * @param serializationPolicy
+ * determines the serialization policy to be used
+ * @return a string which encodes either the method's return or a checked
+ * exception thrown by the method
+ *
+ * @throws NullPointerException
+ * if the serviceMethod or the serializationPolicy are
+ * <code>null</code>
+ * @throws SecurityException
+ * if the method cannot be accessed or if the number or type of
+ * actual and formal arguments differ
+ * @throws SerializationException
+ * if an object could not be serialized by the stream
+ * @throws UnexpectedException
+ * if the serviceMethod throws a checked exception that is not
+ * declared in its signature
*/
- protected final HttpServletResponse getThreadLocalResponse()
+ public static String RPC_invokeAndEncodeResponse(Object target,
+ Method serviceMethod, Class[] paramTypes, Object[] args,
+ SerializationPolicy serializationPolicy) throws SerializationException
{
- return (HttpServletResponse) perThreadResponse.get();
+ if (serviceMethod == null)
+ {
+ throw new NullPointerException("serviceMethod");
+ }
+
+ if (serializationPolicy == null)
+ {
+ throw new NullPointerException("serializationPolicy");
+ }
+
+ String responsePayload;
+ try
+ {
+ GWTToSeamAdapter adapter = GWTToSeamAdapter.instance();
+
+ String serviceIntfName = serviceMethod.getDeclaringClass().getName();
+
+ GWTToSeamAdapter.ReturnedObject returnedObject = adapter
+ .callWebRemoteMethod(serviceIntfName, serviceMethod.getName(),
+ paramTypes, args);
+
+ // Object result = serviceMethod.invoke(target, args);
+
+ responsePayload = RPC.encodeResponseForSuccess(serviceMethod,
+ returnedObject.returnedObject, serializationPolicy);
+ } catch (IllegalAccessException e)
+ {
+ SecurityException securityException = new SecurityException(
+ formatIllegalAccessErrorMessage(target, serviceMethod));
+ securityException.initCause(e);
+ throw securityException;
+ } catch (IllegalArgumentException e)
+ {
+ SecurityException securityException = new SecurityException(
+ formatIllegalArgumentErrorMessage(target, serviceMethod, args));
+ securityException.initCause(e);
+ throw securityException;
+ } catch (InvocationTargetException e)
+ {
+ // Try to encode the caught exception
+ //
+ Throwable cause = e.getCause();
+
+ responsePayload = RPC.encodeResponseForFailure(serviceMethod, cause,
+ serializationPolicy);
+ }
+
+ return responsePayload;
}
/**
@@ -325,7 +579,8 @@
* returned to the client. The default implementation does nothing and need
* not be called by subclasses.
*/
- protected void onAfterResponseSerialized(String serializedResponse)
+ protected void RemoteServiceServlet_onAfterResponseSerialized(
+ String serializedResponse)
{
}
@@ -334,228 +589,388 @@
* payload before it is deserialized into objects. The default implementation
* does nothing and need not be called by subclasses.
*/
- protected void onBeforeRequestDeserialized(String serializedRequest)
+ protected void RemoteServiceServlet_onBeforeRequestDeserialized(
+ String serializedRequest)
{
}
/**
- * Determines whether the response to a given servlet request should or
- * should not be GZIP compressed. This method is only called in cases where
- * the requestor accepts GZIP encoding.
- * <p>
- * This implementation currently returns <code>true</code> if the response
- * string's estimated byte length is longer than 256 bytes. Subclasses can
- * override this logic.
- * </p>
+ * Override this method in order to control the parsing of the incoming
+ * request. For example, you may want to bypass the check of the Content-Type
+ * and character encoding headers in the request, as some proxies re-write
+ * the request headers. Note that bypassing these checks may expose the
+ * servlet to some cross-site vulnerabilities.
*
* @param request
- * the request being served
- * @param response
- * the response that will be written into
- * @param responsePayload
- * the payload that is about to be sent to the client
- * @return <code>true</code> if responsePayload should be GZIP compressed,
- * otherwise <code>false</code>.
+ * the incoming request
+ * @return the content of the incoming request encoded as a string.
*/
- protected boolean shouldCompressResponse(HttpServletRequest request,
- HttpServletResponse response, String responsePayload)
+ protected String RemoteServiceServlet_readContent(HttpServletRequest request)
+ throws ServletException, IOException
{
- return estimateByteSize(responsePayload) > UNCOMPRESSED_BYTE_SIZE_LIMIT;
+ return RPCServletUtils.readContentAsUtf8(request, true);
}
- /**
- * Returns the {@link Class} instance for the named class.
- *
- * @param name
- * the name of a class or primitive type
- * @return Class instance for the given type name
- * @throws ClassNotFoundException
- * if the named type was not found
- */
- private Class getClassFromName(String name) throws ClassNotFoundException
+ public final SerializationPolicy getSerializationPolicy(
+ String moduleBaseURL, String strongName)
{
- return Class.forName(name, false, this.getClass().getClassLoader());
+
+ SerializationPolicy serializationPolicy = getCachedSerializationPolicy(
+ moduleBaseURL, strongName);
+ if (serializationPolicy != null)
+ {
+ return serializationPolicy;
+ }
+
+ serializationPolicy = doGetSerializationPolicy(getThreadLocalRequest(),
+ moduleBaseURL, strongName);
+
+ if (serializationPolicy == null)
+ {
+ // Failed to get the requested serialization policy; use the default
+ getServletContext()
+ .log(
+ "WARNING: Failed to get the SerializationPolicy '"
+ + strongName
+ + "' for module '"
+ + moduleBaseURL
+ + "'; a legacy, 1.3.3 compatible, serialization policy will be used. You may experience SerializationExceptions as a result.");
+ serializationPolicy = RPC.getDefaultSerializationPolicy();
+ }
+
+ // This could cache null or an actual instance. Either way we will not
+ // attempt to lookup the policy again.
+ putCachedSerializationPolicy(moduleBaseURL, strongName,
+ serializationPolicy);
+
+ return serializationPolicy;
}
- /**
- * Returns the {@link Class} instance for the named class or primitive type.
- *
- * @param name
- * the name of a class or primitive type
- * @return Class instance for the given type name
- * @throws ClassNotFoundException
- * if the named type was not found
- */
- private Class getClassOrPrimitiveFromName(String name)
- throws ClassNotFoundException
+ private SerializationPolicy getCachedSerializationPolicy(
+ String moduleBaseURL, String strongName)
{
- Object value = TYPE_NAMES.get(name);
- if (value != null)
+ synchronized (serializationPolicyCache)
{
- return (Class) value;
+ return serializationPolicyCache.get(moduleBaseURL + strongName);
}
+ }
- return getClassFromName(name);
+ private void putCachedSerializationPolicy(String moduleBaseURL,
+ String strongName, SerializationPolicy serializationPolicy)
+ {
+ synchronized (serializationPolicyCache)
+ {
+ serializationPolicyCache.put(moduleBaseURL + strongName,
+ serializationPolicy);
+ }
}
/**
- * Obtain the special package-prefixes we use to check for custom serializers
- * that would like to live in a package that they cannot. For example,
- * "java.util.ArrayList" is in a sealed package, so instead we use this
- * prefix to check for a custom serializer in
- * "com.google.gwt.user.client.rpc.core.java.util.ArrayList". Right now, it's
- * hard-coded because we don't have a pressing need for this mechanism to be
- * extensible, but it is imaginable, which is why it's implemented this way.
+ * Gets the {@link SerializationPolicy} for given module base URL and strong
+ * name if there is one.
+ *
+ * Override this method to provide a {@link SerializationPolicy} using an
+ * alternative approach.
+ *
+ * @param request
+ * the HTTP request being serviced
+ * @param moduleBaseURL
+ * as specified in the incoming payload
+ * @param strongName
+ * a strong name that uniquely identifies a serialization policy
+ * file
+ * @return a {@link SerializationPolicy} for the given module base URL and
+ * strong name, or <code>null</code> if there is none
*/
- protected String[] getPackagePaths()
+ protected SerializationPolicy doGetSerializationPolicy(
+ HttpServletRequest request, String moduleBaseURL, String strongName)
{
- return new String[] { "com.google.gwt.user.client.rpc.core" };
- }
+ // The request can tell you the path of the web app relative to the
+ // container root.
+ String contextPath = request.getContextPath();
- private String readPayloadAsUtf8(HttpServletRequest request)
- throws IOException, ServletException
- {
- int contentLength = request.getContentLength();
- if (contentLength == -1)
+ String modulePath = null;
+ if (moduleBaseURL != null)
{
- // Content length must be known.
- throw new ServletException("Content-Length must be specified");
+ try
+ {
+ modulePath = new URL(moduleBaseURL).getPath();
+ } catch (MalformedURLException ex)
+ {
+ // log the information, we will default
+ getServletContext().log(
+ "Malformed moduleBaseURL: " + moduleBaseURL, ex);
+ }
}
- String contentType = request.getContentType();
- boolean contentTypeIsOkay = false;
- // Content-Type must be specified.
- if (contentType != null)
+ SerializationPolicy serializationPolicy = null;
+
+ /*
+ * Check that the module path must be in the same web app as the servlet
+ * itself. If you need to implement a scheme different than this, override
+ * this method.
+ */
+ if (modulePath == null || !modulePath.startsWith(contextPath))
{
- // The type must be plain text or text/x-gwt-rpc
- if (contentType.startsWith("text/plain") || contentType.startsWith("text/x-gwt-rpc"))
+ String message = "ERROR: The module path requested, "
+ + modulePath
+ + ", is not in the same web application as this servlet, "
+ + contextPath
+ + ". Your module may not be properly configured or your client and server code maybe out of date.";
+ getServletContext().log(message);
+ } else
+ {
+ // Strip off the context path from the module base URL. It should be a
+ // strict prefix.
+ String contextRelativePath = modulePath
+ .substring(contextPath.length());
+
+ String serializationPolicyFilePath = SerializationPolicyLoader
+ .getSerializationPolicyFileName(contextRelativePath + strongName);
+
+ // Open the RPC resource file read its contents.
+ InputStream is = getServletContext().getResourceAsStream(
+ serializationPolicyFilePath);
+ try
{
- // And it must be UTF-8 encoded (or unspecified, in which case we
- // assume that it's either UTF-8 or ASCII).
- if (contentType.indexOf("charset=") == -1)
+ if (is != null)
{
- contentTypeIsOkay = true;
- } else if (contentType.indexOf("charset=utf-8") != -1)
+ try
+ {
+ serializationPolicy = SerializationPolicyLoader
+ .loadFromStream(is, null);
+ } catch (ParseException e)
+ {
+ getServletContext().log(
+ "ERROR: Failed to parse the policy file '"
+ + serializationPolicyFilePath + "'", e);
+ } catch (IOException e)
+ {
+ getServletContext().log(
+ "ERROR: Could not read the policy file '"
+ + serializationPolicyFilePath + "'", e);
+ }
+ } else
{
- contentTypeIsOkay = true;
+ String message = "ERROR: The serialization policy file '"
+ + serializationPolicyFilePath
+ + "' was not found; did you forget to include it in this deployment?";
+ getServletContext().log(message);
}
- }
- }
- if (!contentTypeIsOkay)
- {
- throw new ServletException(
- "Content-Type must be 'text/plain' or 'text/x-gwt-rpc' with 'charset=utf-8' (or unspecified charset)");
- }
- InputStream in = request.getInputStream();
- try
- {
- byte[] payload = new byte[contentLength];
- int offset = 0;
- int len = contentLength;
- int byteCount;
- while (offset < contentLength)
+ } finally
{
- byteCount = in.read(payload, offset, len);
- if (byteCount == -1)
+ if (is != null)
{
- throw new ServletException("Client did not send "
- + contentLength + " bytes as expected");
+ try
+ {
+ is.close();
+ } catch (IOException e)
+ {
+ // Ignore this error
+ }
}
- offset += byteCount;
- len -= byteCount;
}
- return new String(payload, "UTF-8");
- } finally
- {
- if (in != null)
- {
- in.close();
- }
}
+
+ return serializationPolicy;
}
+ private void RemoteServiceServlet_writeResponse(HttpServletRequest request,
+ HttpServletResponse response, String responsePayload)
+ throws IOException
+ {
+ boolean gzipEncode = RPCServletUtils.acceptsGzipEncoding(request)
+ && shouldCompressResponse(request, response, responsePayload);
+
+ RPCServletUtils.writeResponse(getServletContext(), response,
+ responsePayload, gzipEncode);
+ }
+
/**
- * Called when the machinery of this class itself has a problem, rather than
- * the invoked third-party method. It writes a simple 500 message back to the
- * client.
+ * Override this method to control what should happen when an exception
+ * escapes the {@link #processCall(String)} method. The default
+ * implementation will log the failure and send a generic failure response to
+ * the client.
+ * <p/>
+ *
+ * An "expected failure" is an exception thrown by a service method that is
+ * declared in the signature of the service method. These exceptions are
+ * serialized back to the client, and are not passed to this method. This
+ * method is called only for exceptions or errors that are not part of the
+ * service method's signature, or that result from SecurityExceptions,
+ * SerializationExceptions, or other failures within the RPC framework.
+ * <p/>
+ *
+ * Note that if the desired behavior is to both send the GENERIC_FAILURE_MSG
+ * response AND to rethrow the exception, then this method should first send
+ * the GENERIC_FAILURE_MSG response itself (using getThreadLocalResponse),
+ * and then rethrow the exception. Rethrowing the exception will cause it to
+ * escape into the servlet container.
+ *
+ * @param e
+ * the exception which was thrown
*/
- private void respondWithFailure(HttpServletResponse response,
- Throwable caught)
+ protected void RemoteServiceServlet_doUnexpectedFailure(Throwable e)
{
ServletContext servletContext = getServletContext();
- servletContext.log("Exception while dispatching incoming RPC call",
- caught);
- try
- {
- response.setContentType("text/plain");
- response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- response.getWriter().write(GENERIC_FAILURE_MSG);
- } catch (IOException e)
- {
- servletContext
- .log(
- "sendError() failed while sending the previous failure to the client",
- caught);
- }
+ RPCServletUtils.writeResponseForUnexpectedFailure(servletContext,
+ getThreadLocalResponse(), e);
}
- private void writeResponse(HttpServletRequest request,
+ /**
+ * Determines whether the response to a given servlet request should or
+ * should not be GZIP compressed. This method is only called in cases where
+ * the requester accepts GZIP encoding.
+ * <p>
+ * This implementation currently returns <code>true</code> if the response
+ * string's estimated byte length is longer than 256 bytes. Subclasses can
+ * override this logic.
+ * </p>
+ *
+ * @param request
+ * the request being served
+ * @param response
+ * the response that will be written into
+ * @param responsePayload
+ * the payload that is about to be sent to the client
+ * @return <code>true</code> if responsePayload should be GZIP compressed,
+ * otherwise <code>false</code>.
+ */
+ protected boolean shouldCompressResponse(HttpServletRequest request,
HttpServletResponse response, String responsePayload)
- throws IOException
{
+ return RPCServletUtils
+ .exceedsUncompressedContentLengthLimit(responsePayload);
+ }
- byte[] reply = responsePayload.getBytes(CHARSET_UTF8);
- String contentType = CONTENT_TYPE_TEXT_PLAIN_UTF8;
+ private static String formatMethodNotFoundErrorMessage(Class<?> serviceIntf,
+ String serviceMethodName, Class<?>[] parameterTypes)
+ {
+ StringBuffer sb = new StringBuffer();
- if (acceptsGzipEncoding(request)
- && shouldCompressResponse(request, response, responsePayload))
+ sb.append("Could not locate requested method '");
+ sb.append(serviceMethodName);
+ sb.append("(");
+ for (int i = 0; i < parameterTypes.length; ++i)
{
- // Compress the reply and adjust headers.
- //
- ByteArrayOutputStream output = null;
- GZIPOutputStream gzipOutputStream = null;
- Throwable caught = null;
- try
+ if (i > 0)
{
- output = new ByteArrayOutputStream(reply.length);
- gzipOutputStream = new GZIPOutputStream(output);
- gzipOutputStream.write(reply);
- gzipOutputStream.finish();
- gzipOutputStream.flush();
- response.setHeader(CONTENT_ENCODING, CONTENT_ENCODING_GZIP);
- reply = output.toByteArray();
- } catch (UnsupportedEncodingException e)
- {
- caught = e;
- } catch (IOException e)
- {
- caught = e;
- } finally
- {
- if (null != gzipOutputStream)
- {
- gzipOutputStream.close();
- }
- if (null != output)
- {
- output.close();
- }
+ sb.append(", ");
}
+ sb.append(printTypeName(parameterTypes[i]));
+ }
+ sb.append(")'");
- if (caught != null)
- {
- getServletContext().log("Unable to compress response", caught);
- response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
- return;
- }
+ sb.append(" in interface '");
+ sb.append(printTypeName(serviceIntf));
+ sb.append("'");
+
+ return sb.toString();
+ }
+
+ private static String formatIllegalAccessErrorMessage(Object target,
+ Method serviceMethod)
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append("Blocked attempt to access inaccessible method '");
+ sb.append(getSourceRepresentation(serviceMethod));
+ sb.append("'");
+
+ if (target != null)
+ {
+ sb.append(" on target '");
+ sb.append(printTypeName(target.getClass()));
+ sb.append("'");
}
- // Send the reply.
+ sb.append("; this is either misconfiguration or a hack attempt");
+
+ return sb.toString();
+ }
+
+ private static String formatIllegalArgumentErrorMessage(Object target,
+ Method serviceMethod, Object[] args)
+ {
+ StringBuffer sb = new StringBuffer();
+ sb.append("Blocked attempt to invoke method '");
+ sb.append(getSourceRepresentation(serviceMethod));
+ sb.append("'");
+
+ if (target != null)
+ {
+ sb.append(" on target '");
+ sb.append(printTypeName(target.getClass()));
+ sb.append("'");
+ }
+
+ sb.append(" with invalid arguments");
+
+ if (args != null && args.length > 0)
+ {
+ sb.append(Arrays.asList(args));
+ }
+
+ return sb.toString();
+ }
+
+ /**
+ * Returns the source representation for a method signature.
+ *
+ * @param method
+ * method to get the source signature for
+ * @return source representation for a method signature
+ */
+ private static String getSourceRepresentation(Method method)
+ {
+ return method.toString().replace('$', '.');
+ }
+
+ /**
+ * Straight copy from
+ * {@link com.google.gwt.dev.util.TypeInfo#getSourceRepresentation(Class)} to
+ * avoid runtime dependency on gwt-dev.
+ */
+ private static String printTypeName(Class<?> type)
+ {
+ // Primitives
//
- response.setContentLength(reply.length);
- response.setContentType(contentType);
- response.setStatus(HttpServletResponse.SC_OK);
- response.getOutputStream().write(reply);
+ if (type.equals(Integer.TYPE))
+ {
+ return "int";
+ } else if (type.equals(Long.TYPE))
+ {
+ return "long";
+ } else if (type.equals(Short.TYPE))
+ {
+ return "short";
+ } else if (type.equals(Byte.TYPE))
+ {
+ return "byte";
+ } else if (type.equals(Character.TYPE))
+ {
+ return "char";
+ } else if (type.equals(Boolean.TYPE))
+ {
+ return "boolean";
+ } else if (type.equals(Float.TYPE))
+ {
+ return "float";
+ } else if (type.equals(Double.TYPE))
+ {
+ return "double";
+ }
+
+ // Arrays
+ //
+ if (type.isArray())
+ {
+ Class<?> componentType = type.getComponentType();
+ return printTypeName(componentType) + "[]";
+ }
+
+ // Everything else
+ //
+ return type.getName().replace('$', '.');
}
}
Added: trunk/src/remoting/org/jboss/seam/remoting/gwt/SeamRPCRequest.java
===================================================================
--- trunk/src/remoting/org/jboss/seam/remoting/gwt/SeamRPCRequest.java (rev 0)
+++ trunk/src/remoting/org/jboss/seam/remoting/gwt/SeamRPCRequest.java 2008-11-11 07:24:02 UTC (rev 9547)
@@ -0,0 +1,47 @@
+package org.jboss.seam.remoting.gwt;
+
+import com.google.gwt.user.server.rpc.SerializationPolicy;
+
+import java.lang.reflect.Method;
+
+/**
+ * @author Tomaz Cerar
+ * @version $Revision$
+ * @modifiedBy $Author$
+ * @modified $Date$
+ */
+public class SeamRPCRequest
+{
+ private final java.lang.reflect.Method method;
+ private final java.lang.Object[] parameters;
+ private final Class[] parameterTypes;
+ private final com.google.gwt.user.server.rpc.SerializationPolicy serializationPolicy;
+
+ public SeamRPCRequest(Method method, Object[] parameters,
+ Class[] parameterTypes, SerializationPolicy serializationPolicy) {
+ this.method = method;
+ this.parameters = parameters;
+ this.parameterTypes = parameterTypes;
+ this.serializationPolicy = serializationPolicy;
+ }
+
+ public Method getMethod()
+ {
+ return method;
+ }
+
+ public Object[] getParameters()
+ {
+ return parameters;
+ }
+
+ public Class[] getParameterTypes()
+ {
+ return parameterTypes;
+ }
+
+ public SerializationPolicy getSerializationPolicy()
+ {
+ return serializationPolicy;
+ }
+}
More information about the seam-commits
mailing list