Issue ROO-954: Support for transmitting stack traces for sever exceptions.

Review at http://gwt-code-reviews.appspot.com/886801

Review by: amitmanjhi@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8808 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java
index d9e3b79..90e781f 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java
@@ -102,7 +102,9 @@
   public void handleResponseText(String responseText) {
     JsonResults results = JsonResults.fromResults(responseText);
     if (results.getException() != null) {
-      receiver.onFailure(new ServerFailure(results.getException()));
+      ServerFailureRecord cause = results.getException();
+      receiver.onFailure(new ServerFailure(
+          cause.getMessage(), cause.getType(), cause.getTrace()));
       return;
     }
     processRelated(results.getRelated());
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/JsonResults.java b/user/src/com/google/gwt/requestfactory/client/impl/JsonResults.java
index 74be2c5..a39db1e 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/JsonResults.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/JsonResults.java
@@ -32,7 +32,7 @@
   protected JsonResults() {
   }
 
-  public final native String getException() /*-{
+  public final native ServerFailureRecord getException() /*-{
     return this.exception || null;
   }-*/;
 
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/ServerFailureRecord.java b/user/src/com/google/gwt/requestfactory/client/impl/ServerFailureRecord.java
new file mode 100644
index 0000000..1042c35
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/client/impl/ServerFailureRecord.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2010 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.requestfactory.client.impl;
+
+import com.google.gwt.core.client.JavaScriptObject;
+
+/**
+ * Contains details of a server error.
+ */
+public final class ServerFailureRecord extends JavaScriptObject {
+
+  protected ServerFailureRecord() {
+  }
+
+  public native String getMessage() /*-{
+    return this.message || "";
+  }-*/;
+
+  public native String getTrace() /*-{
+    return this.trace || "";
+  }-*/;
+
+  public native String getType() /*-{
+    return this.type || "";
+  }-*/;
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java b/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
new file mode 100644
index 0000000..344b622
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/DefaultExceptionHandler.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2010 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.requestfactory.server;
+
+import com.google.gwt.requestfactory.shared.ServerFailure;
+
+/**
+ * Default implementation for handling exceptions thrown while
+ * processing a request. Suppresses stack traces and the exception
+ * class name.
+ */
+public class DefaultExceptionHandler implements ExceptionHandler {
+  public ServerFailure createServerFailure(Throwable throwable) {
+    return new ServerFailure("Server Error: " + throwable.getMessage(), null,
+        null);
+  }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java b/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java
new file mode 100644
index 0000000..9225a95
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/ExceptionHandler.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2010 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.requestfactory.server;
+
+import com.google.gwt.requestfactory.shared.ServerFailure;
+
+/**
+ * Handles an exception produced while processing a request.
+ */
+public interface ExceptionHandler {
+  /**
+   * Generates a {@link ServerFailure} based on the information
+   * contained in the received {@code exception}.
+   * 
+   * @see DefaultExceptionHandler
+   */
+  ServerFailure createServerFailure(Throwable throwable);
+}
diff --git a/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
index 5d1e767..30d5e6c 100644
--- a/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/JsonRequestProcessor.java
@@ -18,6 +18,7 @@
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.EntityProxyId;
 import com.google.gwt.requestfactory.shared.ProxyFor;
+import com.google.gwt.requestfactory.shared.ServerFailure;
 import com.google.gwt.requestfactory.shared.WriteOperation;
 import com.google.gwt.requestfactory.shared.impl.Property;
 import com.google.gwt.requestfactory.shared.impl.RequestData;
@@ -131,6 +132,8 @@
 
   private OperationRegistry operationRegistry;
 
+  private ExceptionHandler exceptionHandler;
+
   /*
    * <li>Request comes in. Construct the involvedKeys, dvsDataMap and
    * beforeDataMap, using DVS and parameters.
@@ -166,13 +169,12 @@
       Logger.getLogger(this.getClass().getName()).finest("Outgoing response " 
           + response);
       return response;
+    } catch (InvocationTargetException e) {
+      JSONObject exceptionResponse = buildExceptionResponse(e.getCause());
+      throw new RequestProcessingException("Unexpected exception", e,
+          exceptionResponse.toString());
     } catch (Exception e) {
-      JSONObject exceptionResponse = new JSONObject();
-      try {
-        exceptionResponse.put("exception", "Server error");
-      } catch (JSONException jsonException) {
-        throw new IllegalStateException(jsonException);
-      }
+      JSONObject exceptionResponse = buildExceptionResponse(e);
       throw new RequestProcessingException("Unexpected exception", e,
           exceptionResponse.toString());
     }
@@ -759,6 +761,10 @@
     return envelop;
   }
 
+  public void setExceptionHandler(ExceptionHandler exceptionHandler) {
+    this.exceptionHandler = exceptionHandler;
+  }
+
   public void setOperationRegistry(OperationRegistry operationRegistry) {
     this.operationRegistry = operationRegistry;
   }
@@ -819,6 +825,32 @@
         propertyContext));
   }
 
+  private JSONObject buildExceptionResponse(Throwable throwable) {
+    JSONObject exceptionResponse = new JSONObject();
+    ServerFailure failure = exceptionHandler.createServerFailure(throwable);
+    try {
+      JSONObject exceptionMessage = new JSONObject();
+
+      String message = failure.getMessage();
+      String exceptionType = failure.getExceptionType();
+      String stackTraceString = failure.getStackTraceString();
+
+      if (message != null && message.length() != 0) {
+        exceptionMessage.put("message", message);
+      }
+      if (exceptionType != null && exceptionType.length() != 0) {
+        exceptionMessage.put("type", exceptionType);
+      }
+      if (stackTraceString != null && stackTraceString.length() != 0) {
+        exceptionMessage.put("trace", stackTraceString);
+      }
+      exceptionResponse.put("exception", exceptionMessage);
+    } catch (JSONException jsonException) {
+      throw new IllegalStateException(jsonException);
+    }
+    return exceptionResponse;
+  }
+
   @SuppressWarnings("unchecked")
   private Class<? extends EntityProxy> castToRecordClass(Class<?> propertyType) {
     return (Class<? extends EntityProxy>) propertyType;
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
index f3592f6..eef67b8 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java
@@ -75,6 +75,20 @@
     return perThreadResponse.get();
   }
 
+  private final ExceptionHandler exceptionHandler;
+
+  public RequestFactoryServlet() {
+    this(new DefaultExceptionHandler());
+  }
+
+  /**
+   * Use this constructor in subclasses to provide a custom
+   * {@link ExceptionHandler}.
+   */
+  public RequestFactoryServlet(ExceptionHandler exceptionHandler) {
+    this.exceptionHandler = exceptionHandler;
+  }
+
   @Override
   protected void doPost(HttpServletRequest request, HttpServletResponse response)
       throws IOException, ServletException {
@@ -102,6 +116,7 @@
           RequestProcessor<String> requestProcessor = new JsonRequestProcessor();
           requestProcessor.setOperationRegistry(new ReflectionBasedOperationRegistry(
               new DefaultSecurityProvider()));
+          requestProcessor.setExceptionHandler(exceptionHandler);
           response.setHeader("Content-Type",
               RequestFactory.JSON_CONTENT_TYPE_UTF8);
           writer.print(requestProcessor.decodeAndInvokeRequest(jsonRequestString));
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java
index d408f15..93dd64e 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestProcessor.java
@@ -28,6 +28,15 @@
   T decodeAndInvokeRequest(T encodedRequest) throws RequestProcessingException;
 
   /**
+   * Sets the ExceptionHandler to use to convert exceptions caused by
+   * method invocations into failure messages sent back to the client.
+   * 
+   * @param exceptionHandler an implementation, such as
+   *        {@code DefaultExceptionHandler}
+   */
+  void setExceptionHandler(ExceptionHandler exceptionHandler);
+
+  /**
    * Sets the OperationRegistry to be used for looking up invocation metadata.
    * 
    * @param registry an implementation, such as
diff --git a/user/src/com/google/gwt/requestfactory/shared/Receiver.java b/user/src/com/google/gwt/requestfactory/shared/Receiver.java
index 3f1ad48..957d49c 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Receiver.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Receiver.java
@@ -33,7 +33,13 @@
    * RuntimeException with the provided error message.
    */
   public void onFailure(ServerFailure error) {
-    throw new RuntimeException(error.getMessage());
+    String exceptionType = error.getExceptionType();
+    String message = error.getMessage();
+    throw new RuntimeException(exceptionType
+        + ((exceptionType.length() != 0 && message.length() != 0) ? ": " : "")
+        + error.getMessage()
+        + ((exceptionType.length() != 0 || message.length() != 0) ? ": " : "")
+        + error.getStackTraceString());
   }
 
   /**
@@ -43,13 +49,14 @@
 
   /**
    * Called if an object sent to the server could not be validated. The default
-   * implementation calls {@link #onFailure()} if <code>errors</code> is not
-   * empty.
+   * implementation calls {@link #onFailure(ServerFailure)} if <code>errors
+   * </code> is not empty.
    */
   public void onViolation(Set<Violation> errors) {
     if (!errors.isEmpty()) {
       onFailure(new ServerFailure(
-          "The call failed on the server due to a ConstraintViolation"));
+          "The call failed on the server due to a ConstraintViolation",
+          "", ""));
     }
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java b/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java
index 67eef63..3175f30 100644
--- a/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java
+++ b/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java
@@ -20,19 +20,32 @@
  */
 public class ServerFailure {
   private final String message;
+  private final String stackTraceString;
+  private final String exceptionType;
 
   /**
    * Constructs a ServerFailure with a null message.
    */
   public ServerFailure() {
-    this(null);
+    this(null, null, null);
   }
 
-  public ServerFailure(String message) {
+  public ServerFailure(String message, String exceptionType,
+      String stackTraceString) {
     this.message = message;
+    this.exceptionType = exceptionType;
+    this.stackTraceString = stackTraceString;
+  }
+
+  public String getExceptionType() {
+    return exceptionType;
   }
 
   public String getMessage() {
     return message;
   }
+
+  public String getStackTraceString() {
+    return stackTraceString;
+  }
 }
diff --git a/user/test/com/google/gwt/requestfactory/RequestFactoryExceptionHandlerTest.gwt.xml b/user/test/com/google/gwt/requestfactory/RequestFactoryExceptionHandlerTest.gwt.xml
new file mode 100644
index 0000000..39c7db2
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/RequestFactoryExceptionHandlerTest.gwt.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE module PUBLIC "-//Google Inc.//DTD Google Web Toolkit 2.0.1//EN" "http://google-web-toolkit.googlecode.com/svn/tags/2.0.1/distro-source/core/src/gwt-module.dtd">
+<!--
+  Copyright 2010 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.
+-->
+<module>
+  <inherits name='com.google.gwt.requestfactory.RequestFactory'/>
+  <!-- use this old inefficient JSON library just for the time being, replace soon -->
+  <inherits name='com.google.gwt.junit.JUnit'/>
+  <inherits name='com.google.gwt.json.JSON'/>
+  <servlet path='/gwtRequest'
+    class='com.google.gwt.requestfactory.server.RequestFactoryExceptionHandlerServlet' />
+</module>
diff --git a/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java b/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java
index 124550f..c04d1f4 100644
--- a/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java
+++ b/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java
@@ -18,6 +18,7 @@
 import com.google.gwt.junit.tools.GWTTestSuite;
 import com.google.gwt.requestfactory.client.EditorTest;
 import com.google.gwt.requestfactory.client.FindServiceTest;
+import com.google.gwt.requestfactory.client.RequestFactoryExceptionHandlerTest;
 import com.google.gwt.requestfactory.client.RequestFactoryTest;
 import com.google.gwt.requestfactory.client.impl.DeltaValueStoreJsonImplTest;
 import com.google.gwt.requestfactory.client.impl.ProxyJsoImplTest;
@@ -37,6 +38,7 @@
     suite.addTestSuite(ValueStoreJsonImplTest.class);
     suite.addTestSuite(DeltaValueStoreJsonImplTest.class);
     suite.addTestSuite(RequestFactoryTest.class);
+    suite.addTestSuite(RequestFactoryExceptionHandlerTest.class);
     suite.addTestSuite(FindServiceTest.class);
     return suite;
   }
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryExceptionHandlerTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryExceptionHandlerTest.java
new file mode 100644
index 0000000..e3ba01e
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryExceptionHandlerTest.java
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2010 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.requestfactory.client;
+
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.requestfactory.shared.RequestObject;
+import com.google.gwt.requestfactory.shared.ServerFailure;
+import com.google.gwt.requestfactory.shared.SimpleFooProxy;
+import com.google.gwt.requestfactory.shared.SyncResult;
+import com.google.gwt.requestfactory.shared.Violation;
+
+import java.util.Set;
+
+/**
+ * Tests that {@code RequestFactoryServlet} when using a custom
+ * ExceptionHandler.
+ */
+public class RequestFactoryExceptionHandlerTest extends RequestFactoryTest {
+
+  @Override
+  public String getModuleName() {
+    return "com.google.gwt.requestfactory.RequestFactoryExceptionHandlerTest";
+  }
+
+  @Override
+  public void testServerFailure() {
+    delayTestFinish(5000);
+
+    SimpleFooProxy rayFoo = req.create(SimpleFooProxy.class);
+    final RequestObject<SimpleFooProxy> persistRay = req.simpleFooRequest().persistAndReturnSelf(
+        rayFoo);
+    rayFoo = persistRay.edit(rayFoo);
+    // 42 is the crash causing magic number
+    rayFoo.setPleaseCrash(42);
+
+    persistRay.fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onFailure(ServerFailure error) {
+        assertEquals("test message", error.getMessage());
+        assertEquals("java.lang.UnsupportedOperationException",
+            error.getExceptionType());
+        assertFalse(error.getStackTraceString().isEmpty());
+        finishTestAndReset();
+      }
+
+      @Override
+      public void onViolation(Set<Violation> errors) {
+        fail("Failure expected but onViolation() was called");
+      }
+
+      public void onSuccess(SimpleFooProxy response,
+          Set<SyncResult> syncResult) {
+        fail("Failure expected but onSuccess() was called");
+      }
+    });
+  }
+
+}
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
index 13755ec..b40edef 100644
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
@@ -20,6 +20,7 @@
 import com.google.gwt.requestfactory.shared.EntityProxyId;
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.RequestObject;
+import com.google.gwt.requestfactory.shared.ServerFailure;
 import com.google.gwt.requestfactory.shared.SimpleBarProxy;
 import com.google.gwt.requestfactory.shared.SimpleFooProxy;
 import com.google.gwt.requestfactory.shared.SyncResult;
@@ -460,6 +461,37 @@
         });
   }
 
+  public void testServerFailure() {
+    delayTestFinish(5000);
+
+    SimpleFooProxy rayFoo = req.create(SimpleFooProxy.class);
+    final RequestObject<SimpleFooProxy> persistRay = req.simpleFooRequest().persistAndReturnSelf(
+        rayFoo);
+    rayFoo = persistRay.edit(rayFoo);
+    // 42 is the crash causing magic number
+    rayFoo.setPleaseCrash(42);
+
+    persistRay.fire(new Receiver<SimpleFooProxy>() {
+      @Override
+      public void onFailure(ServerFailure error) {
+        assertEquals("Server Error: test message", error.getMessage());
+        assertEquals("", error.getExceptionType());
+        assertEquals("", error.getStackTraceString());
+        finishTestAndReset();
+      }
+
+      @Override
+      public void onViolation(Set<Violation> errors) {
+        fail("Failure expected but onViolation() was called");
+      }
+
+      public void onSuccess(SimpleFooProxy response,
+          Set<SyncResult> syncResult) {
+        fail("Failure expected but onSuccess() was called");
+      }
+    });
+  }
+
   public void testStableId() {
     delayTestFinish(5000);
 
diff --git a/user/test/com/google/gwt/requestfactory/client/impl/SimpleFooProxyProperties.java b/user/test/com/google/gwt/requestfactory/client/impl/SimpleFooProxyProperties.java
index cbd9ee1..07ba06b 100644
--- a/user/test/com/google/gwt/requestfactory/client/impl/SimpleFooProxyProperties.java
+++ b/user/test/com/google/gwt/requestfactory/client/impl/SimpleFooProxyProperties.java
@@ -68,4 +68,6 @@
 
   static final Property<SimpleFooProxy> fooField = new Property<SimpleFooProxy>(
       "fooField", SimpleFooProxy.class);
+
+  static final Property<Integer> pleaseCrash = new Property<Integer>("pleaseCrash", Integer.class);
 }
\ No newline at end of file
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryExceptionHandlerServlet.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryExceptionHandlerServlet.java
new file mode 100644
index 0000000..689e0d0
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryExceptionHandlerServlet.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2010 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.requestfactory.server;
+
+import com.google.gwt.requestfactory.shared.ServerFailure;
+
+/**
+ * A RequestFactoryServlet that forwards all exception information.
+ */
+public class RequestFactoryExceptionHandlerServlet
+    extends RequestFactoryServlet {
+  public RequestFactoryExceptionHandlerServlet() {
+    super(new ExceptionHandler() {
+      @Override
+      public ServerFailure createServerFailure(Throwable throwable) {
+        return new ServerFailure(throwable.getMessage(),
+            throwable.getClass().getName(), "my stack trace");
+      }
+    });
+  }
+}
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
index 4b25282..f8fdf2c 100644
--- a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
@@ -125,6 +125,7 @@
   private Boolean boolField;
 
   private Boolean otherBoolField;
+  private Integer pleaseCrashField;
 
   private SimpleBar barField;
   private SimpleFoo fooField;
@@ -143,6 +144,7 @@
     boolField = true;
     nullField = null;
     barNullField = null;
+    pleaseCrashField = 0;
   }
 
   public Long countSimpleFooWithUserNameSideEffect() {
@@ -243,6 +245,10 @@
     return password;
   }
 
+  public Integer getPleaseCrash() {
+    return pleaseCrashField;
+  }
+
   /**
    * @return the shortField
    */
@@ -360,6 +366,13 @@
     this.otherBoolField = otherBoolField;
   }
 
+  public void setPleaseCrash(Integer crashIf42) {
+    if (crashIf42 == 42) {
+      throw new UnsupportedOperationException("test message");
+    }
+    pleaseCrashField = crashIf42;
+  }
+
   public void setPassword(String password) {
     this.password = password;
   }
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleFooProxy.java b/user/test/com/google/gwt/requestfactory/shared/SimpleFooProxy.java
index 6a22760..1a8e839 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleFooProxy.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleFooProxy.java
@@ -59,6 +59,8 @@
   
   Boolean getOtherBoolField();
   
+  Integer getPleaseCrash();
+
   String getPassword();
 
   Short getShortField();
@@ -97,6 +99,8 @@
 
   void setPassword(String password);
 
+  void setPleaseCrash(Integer dummy);
+
   void setShortField(Short s);
 
   void setUserName(String userName);