This patch contains a refactoring of the RemoteServiceServlet class to separate out the RPC encoding, decoding, and invocation logic into a separate utility class.  I added some unit tests and made minor modifications to the original patch to correct problems discovered during subsequent testing.  Currently, the patch passes all unit tests in hosted and web modes.

Patch by: rjellinghaus (unit test, and minor cleanup by me)
Review by: bruce



git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@677 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/doc/build.xml b/doc/build.xml
index a9dab35..c0306e4 100644
--- a/doc/build.xml
+++ b/doc/build.xml
@@ -243,7 +243,7 @@
 					<arg value="-sourcepath" />
 					<arg pathref="USER_SOURCE_PATH" />
 					<arg value="-examplepackages" />
-					<arg value="com.google.gwt.examples;com.google.gwt.examples.i18n;com.google.gwt.examples.http.client" />
+					<arg value="com.google.gwt.examples;com.google.gwt.examples.i18n;com.google.gwt.examples.http.client;com.google.gwt.examples.rpc.server" />
 					<arg value="-packages" />
 					<arg value="${USER_PKGS}" />
 				</java>
diff --git a/user/javadoc/com/google/gwt/examples/rpc/server/AdvancedExample.java b/user/javadoc/com/google/gwt/examples/rpc/server/AdvancedExample.java
new file mode 100644
index 0000000..b454200
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/rpc/server/AdvancedExample.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2007 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.examples.rpc.server;
+
+import com.google.gwt.user.server.rpc.RPC;
+import com.google.gwt.user.server.rpc.RPCRequest;
+
+import java.io.IOException;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+/**
+ * Example demonstrating a more complex RPC integration scenario.
+ */
+public class AdvancedExample extends HttpServlet {
+  /**
+   * An example of how you could integrate GWTs RPC functionality without using
+   * the {@link com.google.gwt.user.server.rpc.RemoteServiceServlet}. Note that
+   * it also shows how mapping between and RPC interface and some other POJO
+   * could be performed.
+   */
+  public void doPost(HttpServletRequest httpRequest,
+      HttpServletResponse httpResponse) throws ServletException, IOException {
+    String payload = readPayloadAsUtf8(httpRequest);
+
+    try {
+      RPCRequest rpcRequest = RPC.decodeRequest(payload);
+
+      Object targetInstance = getInstanceToHandleRequest(httpRequest,
+          rpcRequest);
+
+      Method targetMethod = maybeMapRequestedMethod(targetInstance,
+          rpcRequest.getMethod());
+
+      Object[] targetParameters = maybeMapParameters(rpcRequest.getParameters());
+
+      try {
+        Object result = targetMethod.invoke(targetInstance, targetParameters);
+
+        result = maybeMapResult(rpcRequest.getMethod(), result);
+
+        /*
+         * Encode the object that will be given to the client code's
+         * AsyncCallback::onSuccess(Object) method.
+         */
+        String encodedResult = RPC.encodeResponseForSuccess(
+            rpcRequest.getMethod(), result);
+
+        sendResponseForSuccess(httpResponse, encodedResult);
+      } catch (IllegalArgumentException e) {
+        throw new SecurityException("Blocked attempt to invoke method "
+            + targetMethod, e);
+      } catch (IllegalAccessException e) {
+        throw new SecurityException("Blocked attempt to access inaccessible method " 
+            + targetMethod
+            + (targetInstance != null ? " on target " + targetInstance : ""), e);
+      } catch (InvocationTargetException e) {
+        Throwable cause = e.getCause();
+
+        Throwable mappedThrowable = maybeMapThrowable(cause,
+            rpcRequest.getMethod());
+
+        /*
+         * Encode the exception that will be passed back to the client's client
+         * code's AsyncCallback::onFailure(Throwable) method.
+         */
+        String failurePayload = RPC.encodeResponseForFailure(
+            rpcRequest.getMethod(), mappedThrowable);
+
+        sendResponseForFailure(httpResponse, failurePayload);
+      }
+    } catch (Throwable e) {
+      /*
+       * Return a generic error which will be passed to the client code's
+       * AsyncCallback::onFailure(Throwable) method.
+       */
+      sendResponseForGenericFailure(httpResponse);
+    }
+  }
+
+  private Object getInstanceToHandleRequest(HttpServletRequest httpRequest,
+      RPCRequest rpcRequest) {
+    return null;
+  }
+
+  private Method maybeMapRequestedMethod(Object targetInstance, Method method) {
+    return null;
+  }
+
+  private Object[] maybeMapParameters(Object[] parameters) {
+    return null;
+  }
+
+  private Object maybeMapResult(Method method, Object targetResult) {
+    return null;
+  }
+
+  private Throwable maybeMapThrowable(Throwable cause, Method method) {
+    return null;
+  }
+
+  private String readPayloadAsUtf8(HttpServletRequest httpRequest) {
+    return null;
+  }
+
+  private void sendResponseForFailure(HttpServletResponse httpResponse,
+      String failurePayload) {
+  }
+
+  private void sendResponseForGenericFailure(HttpServletResponse httpResponse) {
+  }
+
+  private void sendResponseForSuccess(HttpServletResponse httpResponse,
+      String encodedResult) {
+  }
+}
diff --git a/user/javadoc/com/google/gwt/examples/rpc/server/CanonicalExample.java b/user/javadoc/com/google/gwt/examples/rpc/server/CanonicalExample.java
new file mode 100644
index 0000000..1c8c38c
--- /dev/null
+++ b/user/javadoc/com/google/gwt/examples/rpc/server/CanonicalExample.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2007 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.examples.rpc.server;
+
+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;
+
+/**
+ * Canonical RPC integration example.
+ */
+public class CanonicalExample extends RemoteServiceServlet {
+  /**
+   * Process the RPC request encoded into the payload string and return a string
+   * that encodes either the method return or an exception thrown by it.
+   */
+  public String processCall(String payload) throws SerializationException {
+    RPCRequest rpcRequest = RPC.decodeRequest(payload, this.getClass());
+
+    return RPC.invokeAndEncodeResponse(this, rpcRequest.getMethod(),
+        rpcRequest.getParameters());
+  }
+}
diff --git a/user/src/com/google/gwt/user/server/rpc/RPC.java b/user/src/com/google/gwt/user/server/rpc/RPC.java
new file mode 100644
index 0000000..741ad53
--- /dev/null
+++ b/user/src/com/google/gwt/user/server/rpc/RPC.java
@@ -0,0 +1,637 @@
+/*
+ * Copyright 2007 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.server.rpc;
+
+import com.google.gwt.dev.util.TypeInfo;
+import com.google.gwt.user.client.rpc.RemoteService;
+import com.google.gwt.user.client.rpc.SerializationException;
+import com.google.gwt.user.server.rpc.impl.ServerSerializableTypeOracle;
+import com.google.gwt.user.server.rpc.impl.ServerSerializableTypeOracleImpl;
+import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader;
+import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Utility class for integrating with the RPC system. This class exposes methods
+ * for decoding of RPC requests, encoding of RPC responses, and invocation of
+ * RPC calls on service objects. The operations exposed by this class can be
+ * reused by framework implementors such as Spring and G4jsf to support a wide
+ * range of service invocation policies.
+ * 
+ * <h3>Canonical Example</h3>
+ * The following example demonstrates the canonical way to use this class.
+ * 
+ * {@example com.google.gwt.examples.rpc.server.CanonicalExample#processCall(String)}
+ * 
+ * <h3>Advanced Example</h3>
+ * The following example shows a more advanced way of using this class to create
+ * an adapter between GWT RPC entities and POJOs.
+ * 
+ * {@example com.google.gwt.examples.rpc.server.AdvancedExample#doPost(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)}
+ */
+public final class RPC {
+
+  /**
+   * Maps primitive wrapper classes to their corresponding primitive class.
+   */
+  private static final Map PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS = new HashMap();
+
+  /**
+   * Oracle used in stream construction. Encapsulates a set of static,
+   * synchronized caches.
+   */
+  private static ServerSerializableTypeOracle serializableTypeOracle;
+
+  /**
+   * Static map of classes to sets of interfaces (e.g. classes). Optimizes
+   * lookup of interfaces for security.
+   */
+  private static Map/* <Class, Set<String> > */serviceToImplementedInterfacesMap;
+
+  private static final HashMap TYPE_NAMES;
+  static {
+    PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Boolean.class, Boolean.TYPE);
+    PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Byte.class, Byte.TYPE);
+    PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Character.class,
+        Character.TYPE);
+    PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Double.class, Double.TYPE);
+    PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Float.class, Float.TYPE);
+    PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Integer.class, Integer.TYPE);
+    PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Long.class, Long.TYPE);
+    PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.put(Short.class, Short.TYPE);
+
+    TYPE_NAMES = new HashMap();
+    TYPE_NAMES.put("Z", boolean.class);
+    TYPE_NAMES.put("B", byte.class);
+    TYPE_NAMES.put("C", char.class);
+    TYPE_NAMES.put("D", double.class);
+    TYPE_NAMES.put("F", float.class);
+    TYPE_NAMES.put("I", int.class);
+    TYPE_NAMES.put("J", long.class);
+    TYPE_NAMES.put("S", short.class);
+
+    serializableTypeOracle = new ServerSerializableTypeOracleImpl(
+        getPackagePaths());
+
+    serviceToImplementedInterfacesMap = new HashMap();
+  }
+
+  /**
+   * Returns an {@link RPCRequest} that is built by decoding the contents of an
+   * encoded RPC request.
+   * 
+   * <p>
+   * This method is equivalent to calling {@link #decodeRequest(String, Class)}
+   * with <code>null</code> for the type parameter.
+   * </p>
+   * 
+   * @param encodedRequest a string that encodes the {@link RemoteService}
+   *          interface, the service method to call, and the arguments to for
+   *          the service method
+   * @return an {@link RPCRequest} instance
+   * 
+   * @throws SerializationException if the encodedRequest contents cannot be
+   *           deserialized
+   * @throws SecurityException if any of the following conditions apply:
+   *           <ul>
+   *           <li><code>RPC.class.getClassLoader()</code> cannot load the
+   *           service interface requested in the encoded request</li>
+   *           <li>the requested interface is not assignable to
+   *           {@link RemoteService}</li>
+   *           <li>the service method requested in the encoded request is not a
+   *           member of the requested service interface</li>
+   *           </ul>
+   */
+  public static RPCRequest decodeRequest(String encodedRequest)
+      throws SerializationException {
+    return decodeRequest(encodedRequest, null);
+  }
+
+  /**
+   * 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 RemoteService} interface requested in the encoded request string.
+   * 
+   * <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 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 RemoteService} interface encoded
+   *          in the encoded request string.
+   * @return an {@link RPCRequest} instance
+   * 
+   * @throws NullPointerException if the encodedRequest is <code>null</code>
+   * @throws IllegalArgumentException if the encodedRequest is an empty string
+   * @throws SerializationException if the types in the encoded request cannot
+   *           be deserialized
+   * @throws SecurityException if any of the following conditions apply:
+   *           <ul>
+   *           <li><code>RPC.class.getClassLoader()</code> cannot load the
+   *           service interface requested in the encodedRequest</li>
+   *           <li>the requested interface is not assignable to
+   *           {@link 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 RemoteService} interface
+   *           </ul>
+   */
+  public static RPCRequest decodeRequest(String encodedRequest, Class type)
+      throws SerializationException {
+    if (encodedRequest == null) {
+      throw new NullPointerException("encodedRequest cannot be null");
+    }
+
+    if (encodedRequest.length() == 0) {
+      throw new IllegalArgumentException("encodedRequest cannot be empty");
+    }
+
+    ServerSerializationStreamReader streamReader = new ServerSerializationStreamReader(
+        serializableTypeOracle);
+    streamReader.prepareToRead(encodedRequest);
+
+    String serviceIntfName = streamReader.readString();
+
+    if (type != null) {
+      if (!implementsInterface(type, serviceIntfName)) {
+        // The service does not implement the requested interface
+        throw new SecurityException("Blocked attempt to access interface '"
+            + serviceIntfName + "', which is not implemented by '"
+            + TypeInfo.getSourceRepresentation(type, "")
+            + "'; this is either misconfiguration or a hack attempt");
+      }
+    }
+
+    Class serviceIntf;
+    try {
+      serviceIntf = getClassFromSerializedName(serviceIntfName);
+      if (!RemoteService.class.isAssignableFrom(serviceIntf)) {
+        // The requested interface is not a RemoteService interface
+        throw new SecurityException(
+            "Blocked attempt to access interface '"
+                + TypeInfo.getSourceRepresentation(serviceIntf, "")
+                + "', which doesn't extend RemoteService; this is either misconfiguration or a hack attempt");
+      }
+    } catch (ClassNotFoundException e) {
+      throw new SecurityException("Could not locate requested interface '"
+          + serviceIntfName + "' in default classloader", e);
+    }
+
+    String serviceMethodName = streamReader.readString();
+
+    int paramCount = streamReader.readInt();
+    Class[] parameterTypes = new Class[paramCount];
+
+    for (int i = 0; i < parameterTypes.length; i++) {
+      String paramClassName = streamReader.readString();
+      try {
+        parameterTypes[i] = getClassFromSerializedName(paramClassName);
+      } catch (ClassNotFoundException e) {
+        throw new SerializationException("Unknown parameter " + i + " type '"
+            + paramClassName + "'", e);
+      }
+    }
+
+    Method method = findInterfaceMethod(serviceIntf, serviceMethodName,
+        parameterTypes, true);
+
+    if (method == null) {
+      throw new SecurityException(formatMethodNotFoundErrorMessage(serviceIntf,
+          serviceMethodName, parameterTypes));
+    }
+
+    Object[] parameterValues = new Object[parameterTypes.length];
+    for (int i = 0; i < parameterValues.length; i++) {
+      parameterValues[i] = streamReader.deserializeValue(parameterTypes[i]);
+    }
+
+    return new RPCRequest(method, parameterValues);
+  }
+
+  /**
+   * Returns a string that encodes an exception. It is an error to try to encode
+   * an exception that is not in the method's list of checked exceptions.
+   * 
+   * @param serviceMethod the method that threw the exception
+   * @param cause the {@link Throwable} that was thrown
+   * @return a string that encodes the exception, if the exception was expected
+   * 
+   * @throws NullPointerException if either the serviceMethod or the cause are
+   *           <code>null</code>
+   * @throws SerializationException if the result cannot be serialized
+   * @throws UnexpectedException if the result was an unexpected exception (a
+   *           checked exception not declared in the serviceMethod's signature)
+   */
+  public static String encodeResponseForFailure(Method serviceMethod,
+      Throwable cause) throws SerializationException {
+    if (serviceMethod == null) {
+      throw new NullPointerException("serviceMethod cannot be null");
+    }
+
+    if (cause == null) {
+      throw new NullPointerException("cause cannot be null");
+    }
+
+    if (RPC.isExpectedException(serviceMethod, cause)) {
+      return encodeResponse(cause.getClass(), cause, true);
+    } else {
+      throw new UnexpectedException("Service method '"
+          + getSourceRepresentation(serviceMethod)
+          + "' threw an unexpected exception: " + cause.toString(), cause);
+    }
+  }
+
+  /**
+   * Returns a string that encodes the object. It is an error to try to encode
+   * an object that is not assignable to the service method's return type.
+   * 
+   * @param serviceMethod the method whose result we are encoding
+   * @param object the instance that we wish to encode
+   * @return a string that encodes the object, if the object is compatible with
+   *         the service method's declared return type
+   * 
+   * @throws IllegalArgumentException if the result is not assignable to the
+   *           service method's return type
+   * @throws NullPointerException if the service method is <code>null</code>
+   * @throws SerializationException if the result cannot be serialized
+   */
+  public static String encodeResponseForSuccess(Method serviceMethod,
+      Object object) throws SerializationException {
+    if (serviceMethod == null) {
+      throw new NullPointerException("serviceMethod cannot be null");
+    }
+
+    Class methodReturnType = serviceMethod.getReturnType();
+    if (methodReturnType != void.class && object != null) {
+      Class actualReturnType;
+      if (methodReturnType.isPrimitive()) {
+        actualReturnType = getPrimitiveClassFromWrapper(object.getClass());
+      } else {
+        actualReturnType = object.getClass();
+      }
+
+      if (actualReturnType == null
+          || !methodReturnType.isAssignableFrom(actualReturnType)) {
+        throw new IllegalArgumentException("Type '"
+            + TypeInfo.getSourceRepresentation(object.getClass(), "")
+            + "' does not match the return type in the method's signature: '"
+            + getSourceRepresentation(serviceMethod) + "'");
+      }
+    }
+
+    return encodeResponse(methodReturnType, object, false);
+  }
+
+  /**
+   * 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>
+   * 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
+   * @return a string which encodes either the method's return or a checked
+   *         exception thrown by the method
+   * 
+   * @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
+   */
+  public static String invokeAndEncodeResponse(Object target,
+      Method serviceMethod, Object[] args) throws SerializationException {
+
+    if (serviceMethod == null) {
+      throw new NullPointerException("serviceMethod cannot be null");
+    }
+
+    String responsePayload;
+    try {
+      Object result = serviceMethod.invoke(target, args);
+
+      responsePayload = encodeResponseForSuccess(serviceMethod, result);
+    } catch (IllegalAccessException e) {
+      throw new SecurityException(formatIllegalAccessErrorMessage(target,
+          serviceMethod), e);
+    } catch (IllegalArgumentException e) {
+      throw new SecurityException(formatIllegalArgumentErrorMessage(target,
+          serviceMethod, args), e);
+    } catch (InvocationTargetException e) {
+      // Try to encode the caught exception
+      //
+      Throwable cause = e.getCause();
+
+      responsePayload = encodeResponseForFailure(serviceMethod, cause);
+    }
+
+    return responsePayload;
+  }
+
+  /**
+   * Returns a string that encodes the results of an RPC call. Private overload
+   * that takes a flag signaling the preamble of the response payload.
+   * 
+   * @param serviceMethod the method whose return object we are encoding
+   * @param object the object that we wish to send back to the client
+   * @param wasThrown if true, the object being returned was an exception thrown
+   *          by the service method; if false, it was the result of the service
+   *          method's invocation
+   * @return a string that encodes the response from a service method
+   * @throws SerializationException if the object cannot be serialized
+   */
+  private static String encodeResponse(Class responseClass, Object object,
+      boolean wasThrown) throws SerializationException {
+
+    ServerSerializationStreamWriter stream = new ServerSerializationStreamWriter(
+        serializableTypeOracle);
+
+    stream.prepareToWrite();
+    if (responseClass != void.class) {
+      stream.serializeValue(object, responseClass);
+    }
+
+    String bufferStr = (wasThrown ? "{EX}" : "{OK}") + stream.toString();
+    return bufferStr;
+  }
+
+  /**
+   * Find the invoked method on either the specified interface or any super.
+   */
+  private static Method findInterfaceMethod(Class intf, String methodName,
+      Class[] paramTypes, boolean includeInherited) {
+    try {
+      return intf.getDeclaredMethod(methodName, paramTypes);
+    } catch (NoSuchMethodException e) {
+      if (includeInherited) {
+        Class[] superintfs = intf.getInterfaces();
+        for (int i = 0; i < superintfs.length; i++) {
+          Method method = findInterfaceMethod(superintfs[i], methodName,
+              paramTypes, true);
+          if (method != null) {
+            return method;
+          }
+        }
+      }
+
+      return null;
+    }
+  }
+
+  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(TypeInfo.getSourceRepresentation(target.getClass(), ""));
+      sb.append("'");
+    }
+
+    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(TypeInfo.getSourceRepresentation(target.getClass(), ""));
+      sb.append("'");
+    }
+
+    sb.append(" with invalid arguments");
+
+    if (args != null && args.length > 0) {
+      sb.append(Arrays.asList(args));
+    }
+
+    return sb.toString();
+  }
+
+  private static String formatMethodNotFoundErrorMessage(Class serviceIntf,
+      String serviceMethodName, Class[] parameterTypes) {
+    StringBuffer sb = new StringBuffer();
+
+    sb.append("Could not locate requested method '");
+    sb.append(serviceMethodName);
+    sb.append("(");
+    for (int i = 0; i < parameterTypes.length; ++i) {
+      if (i > 0) {
+        sb.append(", ");
+      }
+      sb.append(TypeInfo.getSourceRepresentation(parameterTypes[i], ""));
+    }
+    sb.append(")'");
+
+    sb.append(" in interface '");
+    sb.append(TypeInfo.getSourceRepresentation(serviceIntf, ""));
+    sb.append("'");
+
+    return sb.toString();
+  }
+
+  /**
+   * Returns the {@link Class} instance for the named class or primitive type.
+   * 
+   * @param serializedName the serialized 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 static Class getClassFromSerializedName(String serializedName)
+      throws ClassNotFoundException {
+    Object value = TYPE_NAMES.get(serializedName);
+    if (value != null) {
+      return (Class) value;
+    }
+
+    return Class.forName(serializedName, false, RPC.class.getClassLoader());
+  }
+
+  /**
+   * 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.
+   */
+  private static String[] getPackagePaths() {
+    return new String[] {"com.google.gwt.user.client.rpc.core"};
+  }
+
+  /**
+   * Returns the {@link java.lang.Class Class} for a primitive type given its
+   * corresponding wrapper {@link java.lang.Class Class}.
+   * 
+   * @param wrapperClass primitive wrapper class
+   * @return primitive class
+   */
+  private static Class getPrimitiveClassFromWrapper(Class wrapperClass) {
+    return (Class) PRIMITIVE_WRAPPER_CLASS_TO_PRIMITIVE_CLASS.get(wrapperClass);
+  }
+
+  /**
+   * 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('$', '.');
+  }
+
+  /**
+   * Used to determine whether the specified interface name is implemented by
+   * the service class. This is done without loading the class (for security).
+   */
+  private static boolean implementsInterface(Class service, String intfName) {
+    synchronized (serviceToImplementedInterfacesMap) {
+      // See if it's cached.
+      //
+      Set/* <String> */interfaceSet = (Set) serviceToImplementedInterfacesMap.get(service);
+      if (interfaceSet != null) {
+        if (interfaceSet.contains(intfName)) {
+          return true;
+        }
+      } else {
+        interfaceSet = new HashSet/* <String> */();
+        serviceToImplementedInterfacesMap.put(service, interfaceSet);
+      }
+
+      if (!service.isInterface()) {
+        while ((service != null) && !RemoteServiceServlet.class.equals(service)) {
+          Class[] intfs = service.getInterfaces();
+          for (int i = 0; i < intfs.length; i++) {
+            Class intf = intfs[i];
+            if (implementsInterfaceRecursive(intf, intfName)) {
+              interfaceSet.add(intfName);
+              return true;
+            }
+          }
+
+          // did not find the interface in this class so we look in the
+          // superclass
+          //
+          service = service.getSuperclass();
+        }
+      } else {
+        if (implementsInterfaceRecursive(service, intfName)) {
+          interfaceSet.add(intfName);
+          return true;
+        }
+      }
+
+      return false;
+    }
+  }
+
+  /**
+   * Only called from implementsInterface().
+   */
+  private static boolean implementsInterfaceRecursive(Class clazz,
+      String intfName) {
+    assert (clazz.isInterface());
+
+    if (clazz.getName().equals(intfName)) {
+      return true;
+    }
+
+    // search implemented interfaces
+    Class[] intfs = clazz.getInterfaces();
+    for (int i = 0; i < intfs.length; i++) {
+      Class intf = intfs[i];
+      if (implementsInterfaceRecursive(intf, intfName)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Returns true if the {@link java.lang.reflect.Method Method} definition on
+   * the service is specified to throw the exception contained in the
+   * InvocationTargetException or false otherwise. NOTE we do not check that the
+   * type is serializable here. We assume that it must be otherwise the
+   * application would never have been allowed to run.
+   * 
+   * @param serviceIntfMethod the method from the RPC request
+   * @param cause the exception that the method threw
+   * @return true if the exception's type is in the method's signature
+   */
+  private static boolean isExpectedException(Method serviceIntfMethod,
+      Throwable cause) {
+    assert (serviceIntfMethod != null);
+    assert (cause != null);
+
+    Class[] exceptionsThrown = serviceIntfMethod.getExceptionTypes();
+    if (exceptionsThrown.length <= 0) {
+      // The method is not specified to throw any exceptions
+      //
+      return false;
+    }
+
+    Class causeType = cause.getClass();
+
+    for (int index = 0; index < exceptionsThrown.length; ++index) {
+      Class exceptionThrown = exceptionsThrown[index];
+      assert (exceptionThrown != null);
+
+      if (exceptionThrown.isAssignableFrom(causeType)) {
+        return true;
+      }
+    }
+
+    return false;
+  }
+
+  /**
+   * Static classes have no constructability.
+   */
+  private RPC() {
+  }
+}
diff --git a/user/src/com/google/gwt/user/server/rpc/RPCRequest.java b/user/src/com/google/gwt/user/server/rpc/RPCRequest.java
new file mode 100644
index 0000000..e21ed52
--- /dev/null
+++ b/user/src/com/google/gwt/user/server/rpc/RPCRequest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2007 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.server.rpc;
+
+import java.lang.reflect.Method;
+
+/**
+ * Describes an incoming RPC request in terms of a resolved
+ * {@link Method} and an array of arguments.
+ */
+public final class RPCRequest {
+
+  /**
+   * The method for this request.
+   */
+  private final Method method;
+
+  /**
+   * The parameters for this request.
+   */
+  private final Object[] parameters;
+
+  /**
+   * Construct an RPCRequest.
+   */
+  public RPCRequest (Method method, Object[] parameters) {
+    this.method = method;
+    this.parameters = parameters;
+  }
+
+  /**
+   * Get the request's method.
+   */
+  public Method getMethod () {
+    return method;
+  }
+
+  /**
+   * Get the request's parameters.
+   */
+  public Object[] getParameters () {
+    return parameters;
+  }
+}
diff --git a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
index d329c2a..ce65ebb 100644
--- a/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
+++ b/user/src/com/google/gwt/user/server/rpc/RemoteServiceServlet.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2006 Google Inc.
+ * Copyright 2007 Google Inc.
  * 
  * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  * use this file except in compliance with the License. You may obtain a copy of
@@ -15,22 +15,11 @@
  */
 package com.google.gwt.user.server.rpc;
 
-import com.google.gwt.user.client.rpc.RemoteService;
 import com.google.gwt.user.client.rpc.SerializationException;
-import com.google.gwt.user.server.rpc.impl.ServerSerializableTypeOracle;
-import com.google.gwt.user.server.rpc.impl.ServerSerializableTypeOracleImpl;
-import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamReader;
-import com.google.gwt.user.server.rpc.impl.ServerSerializationStreamWriter;
 
 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.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
 import java.util.zip.GZIPOutputStream;
 
 import javax.servlet.ServletContext;
@@ -45,7 +34,6 @@
  * outgoing responses for client/server RPCs.
  */
 public class RemoteServiceServlet extends HttpServlet {
-
   /*
    * These members are used to get and set the different HttpServletResponse and
    * HttpServletRequest headers.
@@ -56,7 +44,6 @@
   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
@@ -64,18 +51,6 @@
    */
   private static final int UNCOMPRESSED_BYTE_SIZE_LIMIT = 256;
 
-  static {
-    TYPE_NAMES = new HashMap();
-    TYPE_NAMES.put("Z", boolean.class);
-    TYPE_NAMES.put("B", byte.class);
-    TYPE_NAMES.put("C", char.class);
-    TYPE_NAMES.put("D", double.class);
-    TYPE_NAMES.put("F", float.class);
-    TYPE_NAMES.put("I", int.class);
-    TYPE_NAMES.put("J", long.class);
-    TYPE_NAMES.put("S", short.class);
-  }
-
   /**
    * Return true if the response object accepts Gzip encoding. This is done by
    * checking that the accept-encoding header specifies gzip as a supported
@@ -107,50 +82,77 @@
   }
 
   /**
-   * Find the invoked method on either the specified interface or any super.
+   * Read the payload as UTF-8 from the request stream.
    */
-  private static Method findInterfaceMethod(Class intf, String methodName,
-      Class[] paramTypes, boolean includeInherited) {
-    try {
-      return intf.getDeclaredMethod(methodName, paramTypes);
-    } catch (NoSuchMethodException e) {
-      if (includeInherited) {
-        Class[] superintfs = intf.getInterfaces();
-        for (int i = 0; i < superintfs.length; i++) {
-          Method method = findInterfaceMethod(superintfs[i], methodName,
-              paramTypes, true);
-          if (method != null) {
-            return method;
-          }
+  private static String readPayloadAsUtf8(HttpServletRequest request)
+      throws IOException, ServletException {
+    int contentLength = request.getContentLength();
+    if (contentLength == -1) {
+      // Content length must be known.
+      throw new ServletException("Content-Length must be specified");
+    }
+
+    String contentType = request.getContentType();
+    boolean contentTypeIsOkay = false;
+    // Content-Type must be specified.
+    if (contentType != null) {
+      // The type must be plain text.
+      if (contentType.startsWith("text/plain")) {
+        // 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) {
+          contentTypeIsOkay = true;
+        } else if (contentType.indexOf("charset=utf-8") != -1) {
+          contentTypeIsOkay = true;
         }
       }
-
-      return null;
+    }
+    if (!contentTypeIsOkay) {
+      throw new ServletException(
+          "Content-Type must be 'text/plain' 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) {
+        byteCount = in.read(payload, offset, len);
+        if (byteCount == -1) {
+          throw new ServletException("Client did not send " + contentLength
+              + " bytes as expected");
+        }
+        offset += byteCount;
+        len -= byteCount;
+      }
+      return new String(payload, "UTF-8");
+    } finally {
+      if (in != null) {
+        in.close();
+      }
     }
   }
 
-  private final Set knownImplementedInterfaces = new HashSet();
-
   private final ThreadLocal perThreadRequest = new ThreadLocal();
 
   private final ThreadLocal perThreadResponse = new ThreadLocal();
 
-  private final ServerSerializableTypeOracle serializableTypeOracle;
-
   /**
    * The default constructor.
    */
   public RemoteServiceServlet() {
-    serializableTypeOracle = new ServerSerializableTypeOracleImpl(
-        getPackagePaths());
   }
 
   /**
-   * This is called internally.
+   * Standard HttpServlet method: handle the POST.
+   * 
+   * This doPost method swallows ALL exceptions, logs them in the
+   * ServletContext, and returns a GENERIC_FAILURE_MSG response with status code
+   * 500.
    */
   public final void doPost(HttpServletRequest request,
       HttpServletResponse response) {
-    Throwable caught;
     try {
       // Store the request & response objects in thread-local storage.
       //
@@ -161,166 +163,91 @@
       //
       String requestPayload = readPayloadAsUtf8(request);
 
+      // Let subclasses see the serialized request.
+      //
+      onBeforeRequestDeserialized(requestPayload);
+
       // Invoke the core dispatching logic, which returns the serialized
       // result.
       //
       String responsePayload = processCall(requestPayload);
 
+      // Let subclasses see the serialized response.
+      //
+      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;
     } catch (Throwable e) {
-      caught = e;
+      // Give a subclass a chance to either handle the exception or rethrow it
+      //
+      doUnexpectedFailure(e);
+    } finally {
+      // null the thread-locals to avoid holding request/response
+      //
+      perThreadRequest.set(null);
+      perThreadResponse.set(null);
     }
-
-    respondWithFailure(response, caught);
   }
 
   /**
+   * Process a call originating from the given request. Uses the
+   * {@link RPC#invokeAndEncodeResponse(Object, java.lang.reflect.Method, Object[])}
+   * method to do the actual work.
+   * <p>
+   * Subclasses may optionally override this method to handle the payload in any
+   * way they desire (by routing the request to a framework component, for
+   * instance). The {@link HttpServletRequest} and {@link HttpServletResponse}
+   * can be accessed via the {@link #getThreadLocalRequest()} and
+   * {@link #getThreadLocalResponse()} methods.
+   * </p>
    * This is public so that it can be unit tested easily without HTTP.
+   * 
+   * @param payload the UTF-8 request payload
+   * @throws SerializationException if cannot deserialize the request, or cannot
+   *           serialize the response
+   * @throws SecurityException if there is a problem locating or invoking the
+   *           service method on the destination object
+   * @throws UnexpectedException if the invocation throws a checked exception
+   *           that is not declared in the service method's signature
+   * @throws RuntimeException if the service method throws an unchecked
+   *           exception (the exception will be the one thrown by the service)
    */
   public String processCall(String payload) throws SerializationException {
+    RPCRequest rpcRequest = RPC.decodeRequest(payload, this.getClass());
+    return RPC.invokeAndEncodeResponse(this, rpcRequest.getMethod(),
+        rpcRequest.getParameters());
+  }
 
-    // Let subclasses see the serialized request.
+  /**
+   * 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
+   */
+  protected void doUnexpectedFailure(Throwable e) {
+    ServletContext servletContext = getServletContext();
+    servletContext.log("Exception while dispatching incoming RPC call", e);
+
+    // Send GENERIC_FAILURE_MSG with 500 status.
     //
-    onBeforeRequestDeserialized(payload);
-
-    // Create a stream to deserialize the request.
-    //
-    ServerSerializationStreamReader streamReader = new ServerSerializationStreamReader(
-        serializableTypeOracle);
-    streamReader.prepareToRead(payload);
-
-    // Read the service interface
-    //
-    String serviceIntfName = streamReader.readString();
-
-    // TODO(mmendez): need a way to check the type signature of the service intf
-    // Verify that this very servlet implements the specified interface name.
-    //
-    if (!isImplementedRemoteServiceInterface(serviceIntfName)) {
-      // Bad payload, possible hack attempt.
-      //
-      throw new SecurityException(
-          "Blocked attempt to access interface '"
-              + serviceIntfName
-              + "', which is either not implemented by this servlet or which doesn't extend RemoteService; this is either misconfiguration or a hack attempt");
-    }
-
-    // Actually get the service interface, so that we can query its methods.
-    //
-    Class serviceIntf;
-    try {
-      serviceIntf = getClassFromName(serviceIntfName);
-    } catch (ClassNotFoundException e) {
-      throw new SerializationException("Unknown service interface class '"
-          + serviceIntfName + "'", e);
-    }
-
-    // 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);
-      }
-    }
-
-    // For security, make sure the method is found in the service interface
-    // and not just one that happens to be defined on this class.
-    //
-    Method serviceIntfMethod = findInterfaceMethod(serviceIntf, methodName,
-        paramTypes, true);
-
-    // If it wasn't found, don't continue.
-    //
-    if (serviceIntfMethod == null) {
-      // Bad payload, possible hack attempt.
-      //
-      throw new SecurityException(
-          "Method '"
-              + methodName
-              + "' (or a particular overload) on interface '"
-              + serviceIntfName
-              + "' was not found, this is either misconfiguration or a hack attempt");
-    }
-
-    // Deserialize the parameters.
-    //
-    Object[] args = new Object[paramCount];
-    for (int i = 0; i < args.length; i++) {
-      args[i] = streamReader.deserializeValue(paramTypes[i]);
-    }
-
-    // Make the call via reflection.
-    //
-    String responsePayload = GENERIC_FAILURE_MSG;
-    ServerSerializationStreamWriter streamWriter = new ServerSerializationStreamWriter(
-        serializableTypeOracle);
-    Throwable caught = null;
-    try {
-      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 (isExpectedException(serviceIntfMethod, cause)) {
-          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);
-      }
-    }
-
-    // Let subclasses see the serialized response.
-    //
-    onAfterResponseSerialized(responsePayload);
-
-    return responsePayload;
+    respondWithFailure(getThreadLocalResponse());
   }
 
   /**
@@ -379,239 +306,25 @@
   }
 
   /**
-   * @param stream
-   * @param responseType
-   * @param responseObj
-   * @param isException
-   * @return response
-   */
-  private 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;
-      }
-    }
-
-    String bufferStr = (isException ? "{EX}" : "{OK}") + stream.toString();
-    return bufferStr;
-  }
-
-  /**
-   * 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 {
-    return Class.forName(name, false, this.getClass().getClassLoader());
-  }
-
-  /**
-   * 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 {
-    Object value = TYPE_NAMES.get(name);
-    if (value != null) {
-      return (Class) value;
-    }
-
-    return getClassFromName(name);
-  }
-
-  /**
-   * 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.
-   */
-  private String[] getPackagePaths() {
-    return new String[] {"com.google.gwt.user.client.rpc.core"};
-  }
-
-  /**
-   * Returns true if the {@link java.lang.reflect.Method Method} definition on
-   * the service is specified to throw the exception contained in the
-   * InvocationTargetException or false otherwise. NOTE we do not check that the
-   * type is serializable here. We assume that it must be otherwise the
-   * application would never have been allowed to run.
-   * 
-   * @param serviceIntfMethod
-   * @param e
-   * @return is expected exception
-   */
-  private boolean isExpectedException(Method serviceIntfMethod, Throwable cause) {
-    assert (serviceIntfMethod != null);
-    assert (cause != null);
-
-    Class[] exceptionsThrown = serviceIntfMethod.getExceptionTypes();
-    if (exceptionsThrown.length <= 0) {
-      // The method is not specified to throw any exceptions
-      //
-      return false;
-    }
-
-    Class causeType = cause.getClass();
-
-    for (int index = 0; index < exceptionsThrown.length; ++index) {
-      Class exceptionThrown = exceptionsThrown[index];
-      assert (exceptionThrown != null);
-
-      if (exceptionThrown.isAssignableFrom(causeType)) {
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  /**
-   * Used to determine whether the specified interface name is implemented by
-   * this class without loading the class (for security).
-   */
-  private boolean isImplementedRemoteServiceInterface(String intfName) {
-    synchronized (knownImplementedInterfaces) {
-      // See if it's cached.
-      //
-      if (knownImplementedInterfaces.contains(intfName)) {
-        return true;
-      }
-
-      Class cls = getClass();
-
-      // Unknown, so walk up the class hierarchy to find the first class that
-      // implements the requested interface
-      //
-      while ((cls != null) && !RemoteServiceServlet.class.equals(cls)) {
-        Class[] intfs = cls.getInterfaces();
-        for (int i = 0; i < intfs.length; i++) {
-          Class intf = intfs[i];
-          if (isImplementedRemoteServiceInterfaceRecursive(intfName, intf)) {
-            knownImplementedInterfaces.add(intfName);
-            return true;
-          }
-        }
-
-        // did not find the interface in this class so we look in the
-        // superclass
-        cls = cls.getSuperclass();
-      }
-
-      return false;
-    }
-  }
-
-  /**
-   * Only called from isImplementedInterface().
-   */
-  private boolean isImplementedRemoteServiceInterfaceRecursive(String intfName,
-      Class intfToCheck) {
-    assert (intfToCheck.isInterface());
-
-    if (intfToCheck.getName().equals(intfName)) {
-      // The name is right, but we also verify that it is assignable to
-      // RemoteService.
-      // 
-      if (RemoteService.class.isAssignableFrom(intfToCheck)) {
-        return true;
-      } else {
-        return false;
-      }
-    }
-
-    Class[] intfs = intfToCheck.getInterfaces();
-    for (int i = 0; i < intfs.length; i++) {
-      Class intf = intfs[i];
-      if (isImplementedRemoteServiceInterfaceRecursive(intfName, intf)) {
-        return true;
-      }
-    }
-
-    return false;
-  }
-
-  private String readPayloadAsUtf8(HttpServletRequest request)
-      throws IOException, ServletException {
-    int contentLength = request.getContentLength();
-    if (contentLength == -1) {
-      // Content length must be known.
-      throw new ServletException("Content-Length must be specified");
-    }
-
-    String contentType = request.getContentType();
-    boolean contentTypeIsOkay = false;
-    // Content-Type must be specified.
-    if (contentType != null) {
-      // The type must be plain text.
-      if (contentType.startsWith("text/plain")) {
-        // 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) {
-          contentTypeIsOkay = true;
-        } else if (contentType.indexOf("charset=utf-8") != -1) {
-          contentTypeIsOkay = true;
-        }
-      }
-    }
-    if (!contentTypeIsOkay) {
-      throw new ServletException(
-          "Content-Type must be 'text/plain' 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) {
-        byteCount = in.read(payload, offset, len);
-        if (byteCount == -1) {
-          throw new ServletException("Client did not send " + contentLength
-              + " bytes as expected");
-        }
-        offset += byteCount;
-        len -= byteCount;
-      }
-      return new String(payload, "UTF-8");
-    } finally {
-      if (in != null) {
-        in.close();
-      }
-    }
-  }
-
-  /**
    * 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.
    */
-  private void respondWithFailure(HttpServletResponse response, Throwable caught) {
-    ServletContext servletContext = getServletContext();
-    servletContext.log("Exception while dispatching incoming RPC call", caught);
+  private void respondWithFailure(HttpServletResponse response) {
     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);
+      getServletContext().log(
+          "respondWithFailure failed while sending the previous failure to the client",
+          e);
     }
   }
 
+  /**
+   * Write the response payload to the response stream.
+   */
   private void writeResponse(HttpServletRequest request,
       HttpServletResponse response, String responsePayload) throws IOException {
 
@@ -633,8 +346,6 @@
         gzipOutputStream.flush();
         response.setHeader(CONTENT_ENCODING, CONTENT_ENCODING_GZIP);
         reply = output.toByteArray();
-      } catch (UnsupportedEncodingException e) {
-        caught = e;
       } catch (IOException e) {
         caught = e;
       } finally {
diff --git a/user/src/com/google/gwt/user/server/rpc/UnexpectedException.java b/user/src/com/google/gwt/user/server/rpc/UnexpectedException.java
new file mode 100644
index 0000000..04bac90
--- /dev/null
+++ b/user/src/com/google/gwt/user/server/rpc/UnexpectedException.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2007 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.server.rpc;
+
+/**
+ * The GWT RPC class throws UnexpectedException when a service method, being
+ * invoked by GWT RPC, throws a checked exception that is not in the service
+ * method's signature.  Such exceptions are "unexpected" and the specific
+ * exception will be the cause of the UnexpectedException.
+ */
+public class UnexpectedException extends RuntimeException {
+  /**
+   * Construct an UnexpectedException.
+   * 
+   * @param message the detail message
+   * @param cause the cause of this {@link UnexpectedException}
+   */
+  public UnexpectedException (String message, Throwable cause) {
+    super(message, cause);
+  }
+}
diff --git a/user/src/com/google/gwt/user/server/rpc/package.html b/user/src/com/google/gwt/user/server/rpc/package.html
index f7aade4..ceed157 100644
--- a/user/src/com/google/gwt/user/server/rpc/package.html
+++ b/user/src/com/google/gwt/user/server/rpc/package.html
@@ -2,7 +2,32 @@
 <body>
 Classes used in server-side implementation of remote procedure calls.
 
-Of primary interest is RemoteServiceServlet, which is the base class of all RPC
-servers.
+<p>
+The {@link com.google.gwt.user.server.rpc.RemoteServiceServlet RemoteServiceServlet}
+class provides the most convenient implementation
+of server-side GWT RPC.  This class can be used in two ways:  it can be
+subclassed by servlets that directly implement one or more service
+interfaces, in which case incoming RPC calls will be directed to the
+servlet subclass itself; or it can be overridden to give finer control over
+routing RPC calls within a server framework.  (For more details on the
+latter, see the {@link com.google.gwt.user.server.rpc.RemoteServiceServlet#processCall(String) RemoteServiceServlet.processCall(String)} method.)
+</p>
+
+<p>
+Alternatively, GWT RPC can be integrated into an existing framework, by using 
+the {@link com.google.gwt.user.server.rpc.RPC RPC} class to perform GWT 
+RPC decoding, invocation, and encoding.  RemoteServiceServlet need not 
+be subclassed at all in this case, though reading its source is advisable.
+</p>
+
+<p>
+Note that the default RemoteServiceServlet implementation never throws
+exceptions to the servlet container.  All exceptions that escape the the 
+{@link com.google.gwt.user.server.rpc.RemoteServiceServlet#processCall(String) RemoteServiceServlet.processCall(String)}
+method will be caught, logged in the servlet context, and will cause a generic 
+failure message to be sent to the GWT client -- with a 500 status code.  To 
+customize this behavior, override 
+{@link com.google.gwt.user.server.rpc.RemoteServiceServlet#doUnexpectedFailure(java.lang.Throwable) RemoteServiceServlet.doUnexpectedFailure(java.lang.Throwable)}.
+</p>
 </body>
 </html>
diff --git a/user/test/com/google/gwt/user/RPCSuite.java b/user/test/com/google/gwt/user/RPCSuite.java
index 3dcc3eb..ffd0e8e 100644
--- a/user/test/com/google/gwt/user/RPCSuite.java
+++ b/user/test/com/google/gwt/user/RPCSuite.java
@@ -19,7 +19,9 @@
 import com.google.gwt.user.client.rpc.CustomFieldSerializerTest;
 import com.google.gwt.user.client.rpc.InheritanceTest;
 import com.google.gwt.user.client.rpc.ObjectGraphTest;
+import com.google.gwt.user.client.rpc.RemoteServiceServletTest;
 import com.google.gwt.user.client.rpc.ValueTypesTest;
+import com.google.gwt.user.server.rpc.RPCTest;
 
 import junit.framework.Test;
 import junit.framework.TestSuite;
@@ -30,13 +32,16 @@
 public class RPCSuite {
   public static Test suite() {
     TestSuite suite = new TestSuite("Test for com.google.gwt.user.client.rpc");
+    
 
+    suite.addTestSuite(RPCTest.class);
     suite.addTestSuite(ValueTypesTest.class);
     suite.addTestSuite(InheritanceTest.class);
     suite.addTestSuite(CollectionsTest.class);
     suite.addTestSuite(CustomFieldSerializerTest.class);
     suite.addTestSuite(ObjectGraphTest.class);
-
+    suite.addTestSuite(RemoteServiceServletTest.class);
+    
     return suite;
   }
 }
diff --git a/user/test/com/google/gwt/user/server/rpc/RPCTest.java b/user/test/com/google/gwt/user/server/rpc/RPCTest.java
new file mode 100644
index 0000000..d6ec304
--- /dev/null
+++ b/user/test/com/google/gwt/user/server/rpc/RPCTest.java
@@ -0,0 +1,420 @@
+/*
+ * Copyright 2007 Google Inc.
+ * 
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License. You may obtain a copy of
+ * the License at
+ * 
+ * http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package com.google.gwt.user.server.rpc;
+
+import com.google.gwt.user.client.rpc.RemoteService;
+import com.google.gwt.user.client.rpc.SerializableException;
+import com.google.gwt.user.client.rpc.SerializationException;
+
+import junit.framework.TestCase;
+
+import java.lang.reflect.Method;
+
+/**
+ * Tests for the {@link com.google.gwt.user.server.rpc.RPC RPC} class.
+ */
+public class RPCTest extends TestCase {
+
+  private static interface A extends RemoteService {
+    void method1() throws SerializableException;
+
+    int method2();
+
+    int method3(int val);
+  }
+
+  private static interface B {
+    void method1();
+  }
+
+  private final String VALID_ENCODED_REQUEST = "0\uffff" + // version
+      "0\uffff" + // flags
+      "2\uffff" + // string table entry count
+      A.class.getName() + "\uffff" + // string table entry #0
+      "method2" + "\uffff" + // string table entry #1
+      "1\uffff" + // interface name
+      "2\uffff" + // method name
+      "0\uffff"; // param count
+
+  private final String INVALID_METHOD_REQUEST = "0\uffff" + // version
+      "0\uffff" + // flags
+      "2\uffff" + // string table entry count
+      A.class.getName() + "\uffff" + // string table entry #0
+      "method3" + "\uffff" + // string table entry #1
+      "1\uffff" + // interface name
+      "2\uffff" + // method name
+      "0\uffff"; // param count
+
+  private final String INVALID_INTERFACE_REQUEST = "0\uffff" + // version
+      "0\uffff" + // flags
+      "2\uffff" + // string table entry count
+      B.class.getName() + "\uffff" + // string table entry #0
+      "method1" + "\uffff" + // string table entry #1
+      "1\uffff" + // interface name
+      "2\uffff" + // method name
+      "0\uffff"; // param count
+
+  /**
+   * Tests for method {@link RPC#decodeRequest(String)}
+   * 
+   * <p/> Cases:
+   * <ol>
+   * <li>String == null</li>
+   * <li>String == ""</li>
+   * <li>Valid request
+   * </ol>
+   */
+  public void testDecodeRequestString() {
+    // Case 1
+    try {
+      RPC.decodeRequest(null);
+      fail("Expected NullPointerException");
+    } catch (NullPointerException e) {
+      // expected to get here
+    } catch (SerializationException e) {
+      fail(e.getMessage());
+    }
+
+    // Case 2
+    try {
+      RPC.decodeRequest("");
+      fail("Expected IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+      // expected to get here
+    } catch (SerializationException e) {
+      fail(e.getMessage());
+    }
+
+    // Case 3
+    try {
+      RPCRequest request = RPC.decodeRequest(VALID_ENCODED_REQUEST);
+    } catch (SerializationException e) {
+      e.printStackTrace();
+    }
+  }
+
+  /**
+   * Tests for method {@link RPC#decodeRequest(String, Class)}
+   * 
+   * <p/> Cases:
+   * <ol>
+   * <li>String == null</li>
+   * <li>String == ""</li>
+   * <li>Class is null</li>
+   * <li>Class implements RemoteService subinterface</li>
+   * <li>Class implements the requested interface but it is not a subtype of
+   * RemoteService</li>
+   * <li>Class implements RemoteService derived interface but the method does
+   * not exist
+   * </ol>
+   * 
+   * @throws NoSuchMethodException
+   * @throws SecurityException
+   */
+  public void testDecodeRequestStringClass() throws SecurityException,
+      NoSuchMethodException {
+    // Case 1
+    try {
+      RPC.decodeRequest(null, null);
+      fail("Expected NullPointerException");
+    } catch (NullPointerException e) {
+      // expected to get here
+    } catch (SerializationException e) {
+      fail(e.getMessage());
+    }
+
+    // Case 2
+    try {
+      RPC.decodeRequest("", null);
+      fail("Expected IllegalArgumentException");
+    } catch (IllegalArgumentException e) {
+      // expected to get here
+    } catch (SerializationException e) {
+      fail(e.getMessage());
+    }
+
+    // Case 3
+    try {
+      RPCRequest request = RPC.decodeRequest(VALID_ENCODED_REQUEST, null);
+      assertEquals(A.class.getMethod("method2", null), request.getMethod());
+      assertTrue(request.getParameters().length == 0);
+    } catch (SerializationException e) {
+      e.printStackTrace();
+    }
+
+    // Case 4
+    try {
+      RPCRequest request = RPC.decodeRequest(VALID_ENCODED_REQUEST, A.class);
+      assertEquals(A.class.getMethod("method2", null), request.getMethod());
+      assertTrue(request.getParameters().length == 0);
+    } catch (SerializationException e) {
+      e.printStackTrace();
+    }
+
+    // Case 5
+    try {
+      RPCRequest request = RPC.decodeRequest(INVALID_INTERFACE_REQUEST, B.class);
+    } catch (SecurityException e) {
+      // should get here
+      System.out.println(e.getMessage());
+    } catch (Throwable e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+
+    // Case 6
+    try {
+      RPCRequest request = RPC.decodeRequest(INVALID_METHOD_REQUEST, A.class);
+    } catch (SecurityException e) {
+      // should get here
+      System.out.println(e.getMessage());
+    } catch (Throwable e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  /**
+   * Tests for method {@link RPC#encodeResponseForFailure(Method, Throwable)}
+   * 
+   * Cases:
+   * <ol>
+   * <li>Method == null</li>
+   * <li>Object == null</li>
+   * <li>Method is not specified to throw an exception of the given type</li>
+   * <li>Method is specified to throw an exception of the given type</li>
+   * </ol>
+   * 
+   */
+  public void testEncodeResponseForFailure() {
+    // Case 1
+    try {
+      RPC.encodeResponseForFailure(null, new Throwable());
+      fail("Expected NullPointerException");
+    } catch (NullPointerException e) {
+      // expected to get here
+    } catch (Throwable e) {
+      fail(e.getMessage());
+    }
+
+    Method A_method1 = null;
+    try {
+      A_method1 = A.class.getMethod("method1", null);
+    } catch (Throwable e) {
+      fail(e.getMessage());
+    }
+
+    // Case 2
+    try {
+      RPC.encodeResponseForFailure(A_method1, null);
+      fail("Expected NullPointerException");
+    } catch (NullPointerException e) {
+      // expected to get here
+    } catch (Throwable e) {
+      fail(e.getMessage());
+    }
+
+    // Case 3
+    try {
+      RPC.encodeResponseForFailure(A.class.getMethod("method1", null),
+          new IllegalArgumentException());
+      fail("Expected UnexpectedException");
+    } catch (UnexpectedException e) {
+      // expected to get here
+      System.out.println(e.getMessage());
+    } catch (Throwable e) {
+      fail(e.getMessage());
+    }
+
+    // Case 4
+    try {
+      String str = RPC.encodeResponseForFailure(A.class.getMethod("method1",
+          null), new SerializableException());
+      assertTrue(str.contains("SerializableException"));
+    } catch (Throwable e) {
+      fail(e.getMessage());
+    }
+  }
+
+  /**
+   * Tests for {@link RPC#encodeResponseForSuccess(Method, Object)}
+   * 
+   * Cases:
+   * <ol>
+   * <li>Method == null</li>
+   * <li>Object == null</li>
+   * <li>Method is not specified to return the given type</li>
+   * <li>Method is specified to return the given type</li>
+   * </ol>
+   */
+  public void testEncodeResponseForSuccess() {
+    Method A_method1 = null;
+    Method A_method2 = null;
+    try {
+      A_method1 = A.class.getMethod("method1", null);
+      A_method2 = A.class.getMethod("method2", null);
+    } catch (Throwable e) {
+      fail(e.getMessage());
+    }
+
+    // Case 1
+    try {
+      RPC.encodeResponseForSuccess(null, new Object());
+      fail("Expected NullPointerException");
+    } catch (NullPointerException e) {
+      // expected to get here
+    } catch (Throwable e) {
+      fail(e.getMessage());
+    }
+
+    // Case 2
+    try {
+      RPC.encodeResponseForSuccess(A_method1, null);
+    } catch (NullPointerException e) {
+      // expected to get here
+    } catch (Throwable e) {
+      fail(e.getMessage());
+    }
+
+    // Case 3
+    try {
+      RPC.encodeResponseForSuccess(A_method2, new SerializableException());
+    } catch (IllegalArgumentException e) {
+      // expected to get here
+      System.out.println(e.getMessage());
+    } catch (Throwable e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+
+    // Case 4
+    try {
+      RPC.encodeResponseForSuccess(A_method2, new Integer(1));
+    } catch (Throwable e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+  }
+
+  /**
+   * Tests for {@link RPC#invokeAndEncodeResponse(Object, Method, Object[])}
+   * 
+   * Cases:
+   * <ol>
+   * <li>Method == null</li>
+   * <li>Object does not implement Method</li>
+   * <li>Method parameters do not match given parameters
+   * <li>Method throws exception that it is not specified to
+   * <li>Method throws exception that it is specified to throw
+   * </ol>
+   * 
+   * @throws NoSuchMethodException
+   * @throws SecurityException
+   * 
+   */
+  public void testInvokeAndEncodeResponse() throws SecurityException,
+      NoSuchMethodException {
+    // Case 1
+    try {
+      RPC.invokeAndEncodeResponse(null, null, null);
+    } catch (NullPointerException e) {
+      // expected to get here
+    } catch (Throwable e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+
+    Method A_method1 = A.class.getMethod("method1", null);
+
+    // Case 2
+    try {
+      RPC.invokeAndEncodeResponse(new B() {
+        public void method1() {
+        }
+      }, A_method1, null);
+    } catch (SecurityException e) {
+      // expected to get here
+      System.out.println(e.getMessage());
+    } catch (Throwable e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+
+    // Case 3
+    try {
+      RPC.invokeAndEncodeResponse(new A() {
+        public void method1() throws SerializableException {
+        }
+
+        public int method2() {
+          return 0;
+        }
+
+        public int method3(int val) {
+          return 0;
+        }
+      }, A_method1, new Integer[] {new Integer(1)});
+    } catch (SecurityException e) {
+      // expected to get here
+      System.out.println(e.getMessage());
+    } catch (Throwable e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+
+    // Case 4
+    try {
+      RPC.invokeAndEncodeResponse(new A() {
+        public void method1() throws SerializableException {
+          throw new IllegalArgumentException();
+        }
+
+        public int method2() {
+          return 0;
+        }
+
+        public int method3(int val) {
+          return 0;
+        }
+      }, A_method1, null);
+    } catch (UnexpectedException e) {
+      // expected to get here
+      System.out.println(e.getMessage());
+    } catch (Throwable e) {
+      e.printStackTrace();
+      fail(e.getMessage());
+    }
+
+    // Case 5
+    try {
+      RPC.invokeAndEncodeResponse(new A() {
+        public void method1() throws SerializableException {
+          throw new SerializableException();
+        }
+
+        public int method2() {
+          return 0;
+        }
+
+        public int method3(int val) {
+          return 0;
+        }
+      }, A_method1, null);
+    } catch (Throwable e) {
+      fail(e.getMessage());
+    }
+  }
+}