Updates RequestFactory's Receiver type with onViolation() and onFailure() methods, making it an abstract type.
Patch by: bobv
Review by: amitmanjhi, rjrjr

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


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8771 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
index 1211386..6abdaf8 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
@@ -27,6 +27,7 @@
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.RequestObject;
 import com.google.gwt.requestfactory.shared.SyncResult;
+import com.google.gwt.requestfactory.shared.Violation;
 import com.google.gwt.sample.dynatablerf.client.events.EditPersonEvent;
 import com.google.gwt.sample.dynatablerf.client.widgets.PersonEditor;
 import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory;
@@ -112,11 +113,14 @@
     }
     dialog.hide();
     request.fire(new Receiver<Void>() {
+      @Override
       public void onSuccess(Void response, Set<SyncResult> syncResults) {
-        for (SyncResult result : syncResults) {
-          if (result.hasViolations()) {
-            System.out.println(result.getViolations());
-          }
+      }
+
+      @Override
+      public void onViolation(Set<Violation> errors) {
+        for (Violation error : errors) {
+          System.out.println(error.getPath() + " " + error.getMessage());
         }
       }
     });
diff --git a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseTree.java b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseTree.java
index 90bd469..58e0baa 100644
--- a/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseTree.java
+++ b/samples/expenses/src/main/java/com/google/gwt/sample/expenses/client/ExpenseTree.java
@@ -112,7 +112,7 @@
    * The {@link ListDataProvider} used for Employee lists.
    */
   private class EmployeeListDataProvider extends AsyncDataProvider<
-      EmployeeProxy> implements Receiver<List<EmployeeProxy>> {
+      EmployeeProxy> extends Receiver<List<EmployeeProxy>> {
 
     private final String department;
 
diff --git a/user/src/com/google/gwt/app/place/AbstractProxyEditActivity.java b/user/src/com/google/gwt/app/place/AbstractProxyEditActivity.java
index 7f04da3..c130357 100644
--- a/user/src/com/google/gwt/app/place/AbstractProxyEditActivity.java
+++ b/user/src/com/google/gwt/app/place/AbstractProxyEditActivity.java
@@ -18,15 +18,19 @@
 import com.google.gwt.app.place.ProxyPlace.Operation;
 import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.EntityProxyId;
 import com.google.gwt.requestfactory.shared.ProxyRequest;
 import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.RequestObject;
 import com.google.gwt.requestfactory.shared.SyncResult;
 import com.google.gwt.requestfactory.shared.Value;
+import com.google.gwt.requestfactory.shared.Violation;
 import com.google.gwt.user.client.Window;
 import com.google.gwt.user.client.ui.AcceptsOneWidget;
 
+import java.util.HashMap;
+import java.util.Map;
 import java.util.Set;
 
 /**
@@ -39,8 +43,8 @@
  * 
  * @param <P> the type of Proxy being edited
  */
-public abstract class AbstractProxyEditActivity<P extends EntityProxy> implements
-    Activity, ProxyEditView.Delegate {
+public abstract class AbstractProxyEditActivity<P extends EntityProxy>
+    implements Activity, ProxyEditView.Delegate {
 
   private RequestObject<Void> requestObject;
 
@@ -50,9 +54,9 @@
   private final RequestFactory requests;
   private final PlaceController placeController;
 
-  private P record;
-  private Long futureId;
   private AcceptsOneWidget display;
+  private P record;
+  private EntityProxyId stableId;
 
   public AbstractProxyEditActivity(ProxyEditView<P> view, P record,
       Class<P> proxyType, boolean creating, RequestFactory requests,
@@ -93,7 +97,7 @@
 
     return null;
   }
-  
+
   public void onCancel() {
     onStop();
   }
@@ -114,15 +118,15 @@
 
     Receiver<Void> receiver = new Receiver<Void>() {
       public void onSuccess(Void ignore, Set<SyncResult> response) {
+        // TODO(rjrjr): This can be simplified with RequestFactory.refresh()
         if (display == null) {
           return;
         }
-        boolean hasViolations = false;
 
         for (SyncResult syncResult : response) {
           EntityProxy syncRecord = syncResult.getProxy();
           if (creating) {
-            if (futureId == null || !futureId.equals(syncResult.getFutureId())) {
+            if (!stableId.equals(syncRecord.stableId())) {
               continue;
             }
             record = cast(syncRecord);
@@ -131,18 +135,22 @@
               continue;
             }
           }
-          if (syncResult.hasViolations()) {
-            hasViolations = true;
-            view.showErrors(syncResult.getViolations());
+        }
+        exit(true);
+      }
+
+      @Override
+      public void onViolation(Set<Violation> errors) {
+        Map<String, String> toShow = new HashMap<String, String>();
+        for (Violation error : errors) {
+          if (error.getProxyId().equals(stableId)) {
+            toShow.put(error.getPath(), error.getMessage());
           }
         }
-        if (!hasViolations) {
-          exit(true);
-        } else {
-          requestObject = toCommit;
-          requestObject.clearUsed();
-          view.setEnabled(true);
-        }
+        view.showErrors(toShow);
+        requestObject = toCommit;
+        requestObject.clearUsed();
+        view.setEnabled(true);
       }
     };
     toCommit.fire(receiver);
@@ -156,7 +164,7 @@
 
     if (creating) {
       P tempRecord = requests.create(proxyType);
-      futureId = tempRecord.getId();
+      stableId = tempRecord.stableId();
       doStart(display, tempRecord);
     } else {
       ProxyRequest<P> findRequest = getFindRequest(Value.of(getRecord().getId()));
diff --git a/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java b/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
index 3bc29bf..21a7252 100644
--- a/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
+++ b/user/src/com/google/gwt/requestfactory/client/RequestFactoryLogHandler.java
@@ -38,7 +38,7 @@
     LoggingRequest getLoggingRequest();
   }
   
-  private class LoggingReceiver implements Receiver<Long> {
+  private class LoggingReceiver extends Receiver<Long> {
     public void onSuccess(Long response, Set<SyncResult> syncResults) {
       if (response > 0) {
         logger.finest("Remote logging successful");
diff --git a/user/src/com/google/gwt/requestfactory/client/SyncResultImpl.java b/user/src/com/google/gwt/requestfactory/client/SyncResultImpl.java
index e01dc46..7dd6bdd 100644
--- a/user/src/com/google/gwt/requestfactory/client/SyncResultImpl.java
+++ b/user/src/com/google/gwt/requestfactory/client/SyncResultImpl.java
@@ -18,8 +18,6 @@
 import com.google.gwt.requestfactory.shared.EntityProxy;
 import com.google.gwt.requestfactory.shared.SyncResult;
 
-import java.util.Map;
-
 /**
  * <p>
  * <span style="color:red">Experimental API: This class is still under rapid
@@ -31,12 +29,10 @@
 public class SyncResultImpl implements SyncResult {
 
   private final EntityProxy record;
-  private final Map<String, String> violations;
   private final Object futureId;
   
-  public SyncResultImpl(EntityProxy record, Map<String, String> violations, Object futureId) {
+  public SyncResultImpl(EntityProxy record, Object futureId) {
     this.record = record;
-    this.violations = violations;
     this.futureId = futureId;
   }
 
@@ -46,14 +42,5 @@
 
   public EntityProxy getProxy() {
     return record;
-  }
-  
-  public Map<String, String> getViolations() {
-    return violations;
-  }
-
-  public boolean hasViolations() {
-    return violations != null && violations.size() > 0;
-  }
-  
+  }  
 }
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 dfad427..5832b5b 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequest.java
@@ -19,19 +19,24 @@
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.requestfactory.client.impl.DeltaValueStoreJsonImpl.ReturnRecord;
 import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.EntityProxyId;
 import com.google.gwt.requestfactory.shared.Property;
 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.SyncResult;
+import com.google.gwt.requestfactory.shared.Violation;
 
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Map;
 import java.util.Set;
 
 /**
- * <p> <span style="color:red">Experimental API: This class is still under rapid
+ * <p>
+ * <span style="color:red">Experimental API: This class is still under rapid
  * development, and is very likely to be deleted. Use it at your own risk.
  * </span>
  * </p>
@@ -97,15 +102,17 @@
   public void handleResponseText(String responseText) {
     JsonResults results = JsonResults.fromResults(responseText);
     if (results.getException() != null) {
-      throw new RuntimeException(results.getException());
+      receiver.onFailure(new ServerFailure(results.getException()));
+      return;
     }
     processRelated(results.getRelated());
 
     // handle violations
     JsArray<DeltaValueStoreJsonImpl.ReturnRecord> violationsArray = results.getViolations();
-    Set<SyncResult> syncResults = new HashSet<SyncResult>();
     if (violationsArray != null) {
       int length = violationsArray.length();
+      Set<Violation> errors = new HashSet<Violation>(length);
+
       for (int i = 0; i < length; i++) {
         ReturnRecord violationRecord = violationsArray.get(i);
         Long id = null;
@@ -117,20 +124,31 @@
         final EntityProxyIdImpl key = new EntityProxyIdImpl(id,
             requestFactory.getSchema(violationRecord.getSchema()),
             violationRecord.hasFutureId(), null);
-        ProxyJsoImpl copy = ProxyJsoImpl.create(id, 1, key.schema,
-            requestFactory);
         assert violationRecord.hasViolations();
+
         HashMap<String, String> violations = new HashMap<String, String>();
         violationRecord.fillViolations(violations);
-        syncResults.add(DeltaValueStoreJsonImpl.makeSyncResult(copy,
-            violations, id));
+
+        for (Map.Entry<String, String> entry : violations.entrySet()) {
+          final String path = entry.getKey();
+          final String message = entry.getValue();
+          errors.add(new Violation() {
+            public String getMessage() {
+              return message;
+            }
+
+            public String getPath() {
+              return path;
+            }
+
+            public EntityProxyId getProxyId() {
+              return key;
+            }
+          });
+        }
       }
-      /*
-       * TODO (amitmanjhi): call onViolations once the Receiver interface has
-       * been updated. remove null checks from all implementations once Receiver
-       * has the onViolations method.
-       */
-      handleResult(null, syncResults);
+
+      receiver.onViolation(errors);
     } else {
       handleResult(results.getResult(),
           deltaValueStore.commit(results.getSideEffects()));
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImpl.java
index 691cf81..9ca8896 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImpl.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImpl.java
@@ -104,8 +104,8 @@
 
   // TODO: remove this method when SyncResult is removed.
   static SyncResultImpl makeSyncResult(ProxyJsoImpl jso,
-      Map<String, String> violations, Object futureId) {
-    return new SyncResultImpl(jso.getSchema().create(jso), violations, futureId);
+      Object futureId) {
+    return new SyncResultImpl(jso.getSchema().create(jso), futureId);
   }
 
   private boolean used = false;
@@ -169,7 +169,7 @@
         ProxyJsoImpl masterRecord = master.records.get(futureKey);
         assert masterRecord == null;
         requestFactory.postChangeEvent(copy, WriteOperation.CREATE);
-        syncResults.add(makeSyncResult(copy, null, futureKey.id));
+        syncResults.add(makeSyncResult(copy, futureKey.id));
       }
     }
     processToRemove(toRemove, WriteOperation.CREATE);
@@ -188,7 +188,7 @@
             requestFactory);
         requestFactory.postChangeEvent(copy, WriteOperation.DELETE);
         master.records.remove(key);
-        syncResults.add(makeSyncResult(copy, null, null));
+        syncResults.add(makeSyncResult(copy, null));
       }
     }
 
@@ -216,7 +216,7 @@
           copy.merge(value);
           toRemove.add(key);
         }
-        syncResults.add(makeSyncResult(copy, null, null));
+        syncResults.add(makeSyncResult(copy, null));
       }
     }
     processToRemove(toRemove, WriteOperation.UPDATE);
diff --git a/user/src/com/google/gwt/requestfactory/shared/Receiver.java b/user/src/com/google/gwt/requestfactory/shared/Receiver.java
index 3ea20dd..3f1ad48 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Receiver.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Receiver.java
@@ -1,12 +1,12 @@
 /*
  * 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
@@ -24,9 +24,32 @@
  * </span>
  * </p>
  * Implemented by objects that display values.
- *
+ * 
  * @param <V> value type
  */
-public interface Receiver<V> {
-  void onSuccess(V response, Set<SyncResult> syncResults);
+public abstract class Receiver<V> {
+  /**
+   * Receives general failure notifications. The default implementation throws a
+   * RuntimeException with the provided error message.
+   */
+  public void onFailure(ServerFailure error) {
+    throw new RuntimeException(error.getMessage());
+  }
+
+  /**
+   * Called when a RequestObject has been successfully executed on the server.
+   */
+  public abstract void onSuccess(V response, Set<SyncResult> syncResults);
+
+  /**
+   * 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.
+   */
+  public void onViolation(Set<Violation> errors) {
+    if (!errors.isEmpty()) {
+      onFailure(new ServerFailure(
+          "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
new file mode 100644
index 0000000..67eef63
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/ServerFailure.java
@@ -0,0 +1,38 @@
+/*
+ * 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.shared;
+
+/**
+ * Describes a request failure on the server.
+ */
+public class ServerFailure {
+  private final String message;
+
+  /**
+   * Constructs a ServerFailure with a null message.
+   */
+  public ServerFailure() {
+    this(null);
+  }
+
+  public ServerFailure(String message) {
+    this.message = message;
+  }
+
+  public String getMessage() {
+    return message;
+  }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/SyncResult.java b/user/src/com/google/gwt/requestfactory/shared/SyncResult.java
index bf40616..35c7031 100644
--- a/user/src/com/google/gwt/requestfactory/shared/SyncResult.java
+++ b/user/src/com/google/gwt/requestfactory/shared/SyncResult.java
@@ -15,8 +15,6 @@
  */
 package com.google.gwt.requestfactory.shared;
 
-import java.util.Map;
-
 /**
  * <p>
  * <span style="color:red">Experimental API: This class is still under rapid
@@ -26,13 +24,8 @@
  * Result per record of a SyncRequest.
  */
 public interface SyncResult {
-  // TODO: move violations out of the SyncResult...
-  boolean hasViolations();
-  
   // TODO: futureId isn't working out so well, leaving soon
   Object getFutureId();
 
   EntityProxy getProxy();
-  
-  Map<String, String> getViolations();
 }
diff --git a/user/src/com/google/gwt/requestfactory/shared/Violation.java b/user/src/com/google/gwt/requestfactory/shared/Violation.java
new file mode 100644
index 0000000..58f94eb
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/Violation.java
@@ -0,0 +1,32 @@
+/*
+ * 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.shared;
+
+/**
+ * <p>
+ * <span style="color:red">Experimental API: This class is still under rapid
+ * development, and is very likely to be deleted. Use it at your own risk.
+ * </span>
+ * </p>
+ * A lightweight representation of a ConstraintViolation.
+ */
+public interface Violation {
+  String getMessage();
+
+  String getPath();
+
+  EntityProxyId getProxyId();
+}
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
index 75829c0..74640ca 100644
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
@@ -20,14 +20,15 @@
 import com.google.gwt.junit.client.GWTTestCase;
 import com.google.gwt.requestfactory.client.impl.ProxyImpl;
 import com.google.gwt.requestfactory.shared.EntityProxy;
+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.SimpleBarProxy;
 import com.google.gwt.requestfactory.shared.SimpleFooProxy;
 import com.google.gwt.requestfactory.shared.SimpleRequestFactory;
 import com.google.gwt.requestfactory.shared.SyncResult;
+import com.google.gwt.requestfactory.shared.Violation;
 
-import java.util.Map;
 import java.util.Set;
 
 /**
@@ -35,9 +36,48 @@
  */
 public class RequestFactoryTest extends GWTTestCase {
 
+  private class ShouldNotSuccedReceiver<T> extends Receiver<T> {
+
+    private final EntityProxyId expectedId;
+
+    public ShouldNotSuccedReceiver(EntityProxyId expectedId) {
+      this.expectedId = expectedId;
+    }
+
+    @Override
+    public void onSuccess(T response, Set<SyncResult> syncResults) {
+      /*
+       * Make sure your class path includes:
+       * 
+       * tools/apache/log4j/log4j-1.2.16.jar
+       * tools/hibernate/validator/hibernate-validator-4.1.0.Final.jar
+       * tools/slf4j/slf4j-api/slf4j-api-1.6.1.jar
+       * tools/slf4j/slf4j-log4j12/slf4j-log4j12-1.6.1.jar
+       */
+      fail("Violations expected (you might be missing some jars, "
+          + "see the comment above this line)");
+    }
+
+    @Override
+    public void onViolation(Set<Violation> errors) {
+      assertEquals(1, errors.size());
+      Violation error = errors.iterator().next();
+      assertEquals("userName", error.getPath());
+      assertEquals("size must be between 3 and 30", error.getMessage());
+      assertEquals("Did not receive expeceted id", expectedId,
+          error.getProxyId());
+      finishTest();
+    }
+  }
+
   private SimpleRequestFactory req;
 
   @Override
+  public String getModuleName() {
+    return "com.google.gwt.requestfactory.RequestFactorySuite";
+  }
+
+  @Override
   public void gwtSetUp() {
     req = GWT.create(SimpleRequestFactory.class);
     req.init(new SimpleEventBus());
@@ -78,200 +118,79 @@
     });
   }
 
-  public void testStableId() {
+  public void testFetchEntity() {
+    delayTestFinish(5000);
+    req.simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
+          public void onSuccess(SimpleFooProxy response,
+              Set<SyncResult> syncResult) {
+            assertEquals(42, (int) response.getIntId());
+            assertEquals("GWT", response.getUserName());
+            assertEquals(8L, (long) response.getLongField());
+            assertEquals(com.google.gwt.requestfactory.shared.SimpleEnum.FOO,
+                response.getEnumField());
+            assertEquals(null, response.getBarField());
+            finishTest();
+          }
+        });
+  }
+
+  public void testFetchEntityWithRelation() {
+    delayTestFinish(5000);
+    req.simpleFooRequest().findSimpleFooById(999L).with("barField").fire(
+        new Receiver<SimpleFooProxy>() {
+          public void onSuccess(SimpleFooProxy response,
+              Set<SyncResult> syncResult) {
+            assertEquals(42, (int) response.getIntId());
+            assertEquals("GWT", response.getUserName());
+            assertEquals(8L, (long) response.getLongField());
+            assertEquals(com.google.gwt.requestfactory.shared.SimpleEnum.FOO,
+                response.getEnumField());
+            assertNotNull(response.getBarField());
+            finishTest();
+          }
+        });
+  }
+
+  /*
+   * tests that (a) any method can have a side effect that is handled correctly.
+   * (b) instance methods are handled correctly.
+   */
+  public void testMethodWithSideEffects() {
     delayTestFinish(5000);
 
-    final SimpleFooProxy foo = req.create(SimpleFooProxy.class);
-    final Object futureId = foo.getId();
-    assertTrue(((ProxyImpl) foo).isFuture());
-    RequestObject<SimpleFooProxy> fooReq = req.simpleFooRequest().persistAndReturnSelf(
-        foo);
+    req.simpleFooRequest().findSimpleFooById(999L).fire(
+        new Receiver<SimpleFooProxy>() {
 
-    final SimpleFooProxy newFoo = fooReq.edit(foo);
-    assertEquals(futureId, foo.getId());
-    assertTrue(((ProxyImpl) foo).isFuture());
-    assertEquals(futureId, newFoo.getId());
-    assertTrue(((ProxyImpl) newFoo).isFuture());
-
-    newFoo.setUserName("GWT basic user");
-    fooReq.fire(new Receiver<SimpleFooProxy>() {
-
-      public void onSuccess(final SimpleFooProxy returned,
-          Set<SyncResult> syncResults) {
-        assertEquals(futureId, foo.getId());
-        assertTrue(((ProxyImpl) foo).isFuture());
-        assertEquals(futureId, newFoo.getId());
-        assertTrue(((ProxyImpl) newFoo).isFuture());
-
-        assertFalse(((ProxyImpl) returned).isFuture());
-
-        checkStableIdEquals(foo, returned);
-        checkStableIdEquals(newFoo, returned);
-
-        RequestObject<SimpleFooProxy> editRequest = req.simpleFooRequest().persistAndReturnSelf(
-            returned);
-        final SimpleFooProxy editableFoo = editRequest.edit(returned);
-        editableFoo.setUserName("GWT power user");
-        editRequest.fire(new Receiver<SimpleFooProxy>() {
-
-          public void onSuccess(SimpleFooProxy returnedAfterEdit,
+          public void onSuccess(SimpleFooProxy newFoo,
               Set<SyncResult> syncResults) {
-            checkStableIdEquals(editableFoo, returnedAfterEdit);
-            assertEquals(returnedAfterEdit.getId(), returned.getId());
-            finishTest();
+            final RequestObject<Long> fooReq = req.simpleFooRequest().countSimpleFooWithUserNameSideEffect(
+                newFoo);
+            newFoo = fooReq.edit(newFoo);
+            newFoo.setUserName("Ray");
+            fooReq.fire(new Receiver<Long>() {
+              public void onSuccess(Long response, Set<SyncResult> syncResults) {
+                assertEquals(new Long(1L), response);
+                // confirm that there was a sideEffect.
+                assertEquals(1, syncResults.size());
+                SyncResult syncResultArray[] = syncResults.toArray(new SyncResult[0]);
+                assertNull(syncResultArray[0].getFutureId());
+                EntityProxy proxy = syncResultArray[0].getProxy();
+                assertEquals(new Long(999L), proxy.getId());
+                // confirm that the instance method did have the desired
+                // sideEffect.
+                req.simpleFooRequest().findSimpleFooById(999L).fire(
+                    new Receiver<SimpleFooProxy>() {
+                      public void onSuccess(SimpleFooProxy finalFoo,
+                          Set<SyncResult> syncResults) {
+                        assertEquals("Ray", finalFoo.getUserName());
+                        finishTest();
+                      }
+                    });
+              }
+            });
           }
         });
-      }
-    });
-  }
-
-  private void checkStableIdEquals(SimpleFooProxy expected, SimpleFooProxy actual) {
-    assertNotSame(expected.stableId(), actual.stableId());
-    assertEquals(expected.stableId(), actual.stableId());
-    assertEquals(expected.stableId().hashCode(), actual.stableId().hashCode());
-
-    // No assumptions about the proxy objects (being proxies and all)
-    assertNotSame(expected, actual);
-    assertFalse(expected.equals(actual));
-  }
-
-  public void testViolationsOnCreate() {
-    delayTestFinish(5000);
-
-    SimpleFooProxy newFoo = req.create(SimpleFooProxy.class);
-    final RequestObject<Void> fooReq = req.simpleFooRequest().persist(newFoo);
-
-    newFoo = fooReq.edit(newFoo);
-    newFoo.setUserName("A"); // will cause constraint violation
-
-    fooReq.fire(new Receiver<Void>() {
-      public void onSuccess(Void ignore, Set<SyncResult> syncResults) {
-        assertEquals(1, syncResults.size());
-        SyncResult syncResult = syncResults.iterator().next();
-        /*
-         * Make sure your class path includes:
-         *
-         * tools/apache/log4j/log4j-1.2.16.jar
-         * tools/hibernate/validator/hibernate-validator-4.1.0.Final.jar
-         * tools/slf4j/slf4j-api/slf4j-api-1.6.1.jar
-         * tools/slf4j/slf4j-log4j12/slf4j-log4j12-1.6.1.jar
-         */
-        assertTrue("Violations expected (you might be missing some jars, "
-            + "see the comment above this line)", syncResult.hasViolations());
-        Map<String, String> violations = syncResult.getViolations();
-        assertEquals(1, violations.size());
-        assertEquals("size must be between 3 and 30",
-            violations.get("userName"));
-        finishTest();
-      }
-    });
-  }
-
-  public void testViolationsOnEdit() {
-    delayTestFinish(5000);
-
-    SimpleFooProxy newFoo = req.create(SimpleFooProxy.class);
-    final RequestObject<SimpleFooProxy> fooReq = req.simpleFooRequest().persistAndReturnSelf(newFoo);
-
-    newFoo = fooReq.edit(newFoo);
-    newFoo.setUserName("GWT User");
-
-    fooReq.fire(new Receiver<SimpleFooProxy>() {
-      public void onSuccess(SimpleFooProxy returned, Set<SyncResult> syncResults) {
-        assertEquals(1, syncResults.size());
-        SyncResult syncResult = syncResults.iterator().next();
-        assertFalse(syncResult.hasViolations());
-
-        RequestObject<Void> editRequest = req.simpleFooRequest().persist(returned);
-        SimpleFooProxy editableFoo = editRequest.edit(returned);
-        editableFoo.setUserName("A");
-
-        editRequest.fire(new Receiver<Void>() {
-          public void onSuccess(Void returned, Set<SyncResult> syncResults) {
-            assertEquals(1, syncResults.size());
-            SyncResult syncResult = syncResults.iterator().next();
-            /*
-             * Make sure your class path includes:
-             *
-             * tools/apache/log4j/log4j-1.2.16.jar
-             * tools/hibernate/validator/hibernate-validator-4.1.0.Final.jar
-             * tools/slf4j/slf4j-api/slf4j-api-1.6.1.jar
-             * tools/slf4j/slf4j-log4j12/slf4j-log4j12-1.6.1.jar
-             */
-            assertTrue("Violations expected (you might be missing some jars, "
-                + "see the comment above this line)", syncResult.hasViolations());
-            Map<String, String> violations = syncResult.getViolations();
-            assertEquals(1, violations.size());
-            assertEquals("size must be between 3 and 30",
-                violations.get("userName"));
-            finishTest();
-          }
-        });
-      }
-    });
-  }
-
-  public void testViolationsOnEdit_withReturnValue() {
-    delayTestFinish(5000);
-
-    SimpleFooProxy newFoo = req.create(SimpleFooProxy.class);
-    final RequestObject<SimpleFooProxy> fooReq = req.simpleFooRequest().persistAndReturnSelf(newFoo);
-
-    newFoo = fooReq.edit(newFoo);
-    newFoo.setUserName("GWT User");
-
-    fooReq.fire(new Receiver<SimpleFooProxy>() {
-      public void onSuccess(SimpleFooProxy returned, Set<SyncResult> syncResults) {
-        assertEquals(1, syncResults.size());
-        SyncResult syncResult = syncResults.iterator().next();
-        assertFalse(syncResult.hasViolations());
-
-        RequestObject<SimpleFooProxy> editRequest = req.simpleFooRequest().persistAndReturnSelf(returned);
-        SimpleFooProxy editableFoo = editRequest.edit(returned);
-        editableFoo.setUserName("A");
-
-        editRequest.fire(new Receiver<SimpleFooProxy>() {
-          public void onSuccess(SimpleFooProxy returned, Set<SyncResult> syncResults) {
-            assertEquals(1, syncResults.size());
-            SyncResult syncResult = syncResults.iterator().next();
-            /*
-             * Make sure your class path includes:
-             *
-             * tools/apache/log4j/log4j-1.2.16.jar
-             * tools/hibernate/validator/hibernate-validator-4.1.0.Final.jar
-             * tools/slf4j/slf4j-api/slf4j-api-1.6.1.jar
-             * tools/slf4j/slf4j-log4j12/slf4j-log4j12-1.6.1.jar
-             */
-            assertTrue("Violations expected (you might be missing some jars, "
-                + "see the comment above this line)", syncResult.hasViolations());
-            Map<String, String> violations = syncResult.getViolations();
-            assertEquals(1, violations.size());
-            assertEquals("size must be between 3 and 30",
-                violations.get("userName"));
-            finishTest();
-          }
-        });
-      }
-    });
-  }
-
-  public void testViolationAbsent() {
-    delayTestFinish(5000);
-
-    SimpleFooProxy newFoo = req.create(SimpleFooProxy.class);
-    final RequestObject<Void> fooReq = req.simpleFooRequest().persist(newFoo);
-
-    newFoo = fooReq.edit(newFoo);
-    newFoo.setUserName("Amit"); // will not cause violation.
-
-    fooReq.fire(new Receiver<Void>() {
-      public void onSuccess(Void ignore, Set<SyncResult> syncResults) {
-        assertEquals(1, syncResults.size());
-        SyncResult syncResult = syncResults.iterator().next();
-        assertFalse(syncResult.hasViolations());
-        finishTest();
-      }
-    });
   }
 
   /*
@@ -492,45 +411,6 @@
     });
   }
 
-  @Override
-  public String getModuleName() {
-    return "com.google.gwt.requestfactory.RequestFactorySuite";
-  }
-
-  public void testFetchEntity() {
-    delayTestFinish(5000);
-    req.simpleFooRequest().findSimpleFooById(999L).fire(
-        new Receiver<SimpleFooProxy>() {
-          public void onSuccess(SimpleFooProxy response,
-              Set<SyncResult> syncResult) {
-            assertEquals(42, (int) response.getIntId());
-            assertEquals("GWT", response.getUserName());
-            assertEquals(8L, (long) response.getLongField());
-            assertEquals(com.google.gwt.requestfactory.shared.SimpleEnum.FOO,
-                response.getEnumField());
-            assertEquals(null, response.getBarField());
-            finishTest();
-          }
-        });
-  }
-
-  public void testFetchEntityWithRelation() {
-    delayTestFinish(5000);
-    req.simpleFooRequest().findSimpleFooById(999L).with("barField").fire(
-        new Receiver<SimpleFooProxy>() {
-          public void onSuccess(SimpleFooProxy response,
-              Set<SyncResult> syncResult) {
-            assertEquals(42, (int) response.getIntId());
-            assertEquals("GWT", response.getUserName());
-            assertEquals(8L, (long) response.getLongField());
-            assertEquals(com.google.gwt.requestfactory.shared.SimpleEnum.FOO,
-                response.getEnumField());
-            assertNotNull(response.getBarField());
-            finishTest();
-          }
-        });
-  }
-
   public void testProxysAsInstanceMethodParams() {
     delayTestFinish(5000);
     req.simpleFooRequest().findSimpleFooById(999L).fire(
@@ -552,45 +432,139 @@
         });
   }
 
-  /*
-   * tests that (a) any method can have a side effect that is handled correctly.
-   * (b) instance methods are handled correctly.
-   */
-  public void testMethodWithSideEffects() {
+  public void testStableId() {
     delayTestFinish(5000);
 
-    req.simpleFooRequest().findSimpleFooById(999L).fire(
-        new Receiver<SimpleFooProxy>() {
+    final SimpleFooProxy foo = req.create(SimpleFooProxy.class);
+    final Object futureId = foo.getId();
+    assertTrue(((ProxyImpl) foo).isFuture());
+    RequestObject<SimpleFooProxy> fooReq = req.simpleFooRequest().persistAndReturnSelf(
+        foo);
 
-          public void onSuccess(SimpleFooProxy newFoo,
+    final SimpleFooProxy newFoo = fooReq.edit(foo);
+    assertEquals(futureId, foo.getId());
+    assertTrue(((ProxyImpl) foo).isFuture());
+    assertEquals(futureId, newFoo.getId());
+    assertTrue(((ProxyImpl) newFoo).isFuture());
+
+    newFoo.setUserName("GWT basic user");
+    fooReq.fire(new Receiver<SimpleFooProxy>() {
+
+      public void onSuccess(final SimpleFooProxy returned,
+          Set<SyncResult> syncResults) {
+        assertEquals(futureId, foo.getId());
+        assertTrue(((ProxyImpl) foo).isFuture());
+        assertEquals(futureId, newFoo.getId());
+        assertTrue(((ProxyImpl) newFoo).isFuture());
+
+        assertFalse(((ProxyImpl) returned).isFuture());
+
+        checkStableIdEquals(foo, returned);
+        checkStableIdEquals(newFoo, returned);
+
+        RequestObject<SimpleFooProxy> editRequest = req.simpleFooRequest().persistAndReturnSelf(
+            returned);
+        final SimpleFooProxy editableFoo = editRequest.edit(returned);
+        editableFoo.setUserName("GWT power user");
+        editRequest.fire(new Receiver<SimpleFooProxy>() {
+
+          public void onSuccess(SimpleFooProxy returnedAfterEdit,
               Set<SyncResult> syncResults) {
-            final RequestObject<Long> fooReq = req.simpleFooRequest().countSimpleFooWithUserNameSideEffect(
-                newFoo);
-            newFoo = fooReq.edit(newFoo);
-            newFoo.setUserName("Ray");
-            fooReq.fire(new Receiver<Long>() {
-              public void onSuccess(Long response, Set<SyncResult> syncResults) {
-                assertEquals(new Long(1L), response);
-                // confirm that there was a sideEffect.
-                assertEquals(1, syncResults.size());
-                SyncResult syncResultArray[] = syncResults.toArray(new SyncResult[0]);
-                assertFalse(syncResultArray[0].hasViolations());
-                assertNull(syncResultArray[0].getFutureId());
-                EntityProxy proxy = syncResultArray[0].getProxy();
-                assertEquals(new Long(999L), proxy.getId());
-                // confirm that the instance method did have the desired
-                // sideEffect.
-                req.simpleFooRequest().findSimpleFooById(999L).fire(
-                    new Receiver<SimpleFooProxy>() {
-                      public void onSuccess(SimpleFooProxy finalFoo,
-                          Set<SyncResult> syncResults) {
-                        assertEquals("Ray", finalFoo.getUserName());
-                        finishTest();
-                      }
-                    });
-              }
-            });
+            checkStableIdEquals(editableFoo, returnedAfterEdit);
+            assertEquals(returnedAfterEdit.getId(), returned.getId());
+            finishTest();
           }
         });
+      }
+    });
+  }
+
+  public void testViolationAbsent() {
+    delayTestFinish(5000);
+
+    SimpleFooProxy newFoo = req.create(SimpleFooProxy.class);
+    final RequestObject<Void> fooReq = req.simpleFooRequest().persist(newFoo);
+
+    newFoo = fooReq.edit(newFoo);
+    newFoo.setUserName("Amit"); // will not cause violation.
+
+    fooReq.fire(new Receiver<Void>() {
+      public void onSuccess(Void ignore, Set<SyncResult> syncResults) {
+        assertEquals(1, syncResults.size());
+        finishTest();
+      }
+    });
+  }
+
+  public void testViolationsOnCreate() {
+    delayTestFinish(5000);
+
+    SimpleFooProxy newFoo = req.create(SimpleFooProxy.class);
+    final RequestObject<Void> fooReq = req.simpleFooRequest().persist(newFoo);
+
+    newFoo = fooReq.edit(newFoo);
+    newFoo.setUserName("A"); // will cause constraint violation
+
+    fooReq.fire(new ShouldNotSuccedReceiver<Void>(newFoo.stableId()));
+  }
+
+  public void testViolationsOnEdit() {
+    delayTestFinish(5000);
+
+    SimpleFooProxy newFoo = req.create(SimpleFooProxy.class);
+    final RequestObject<SimpleFooProxy> fooReq = req.simpleFooRequest().persistAndReturnSelf(
+        newFoo);
+
+    newFoo = fooReq.edit(newFoo);
+    newFoo.setUserName("GWT User");
+
+    fooReq.fire(new Receiver<SimpleFooProxy>() {
+      public void onSuccess(SimpleFooProxy returned, Set<SyncResult> syncResults) {
+        assertEquals(1, syncResults.size());
+
+        RequestObject<Void> editRequest = req.simpleFooRequest().persist(
+            returned);
+        SimpleFooProxy editableFoo = editRequest.edit(returned);
+        editableFoo.setUserName("A");
+
+        editRequest.fire(new ShouldNotSuccedReceiver<Void>(returned.stableId()));
+      }
+    });
+  }
+
+  public void testViolationsOnEdit_withReturnValue() {
+    delayTestFinish(5000);
+
+    SimpleFooProxy newFoo = req.create(SimpleFooProxy.class);
+    final RequestObject<SimpleFooProxy> fooReq = req.simpleFooRequest().persistAndReturnSelf(
+        newFoo);
+
+    newFoo = fooReq.edit(newFoo);
+    newFoo.setUserName("GWT User");
+
+    fooReq.fire(new Receiver<SimpleFooProxy>() {
+      public void onSuccess(SimpleFooProxy returned, Set<SyncResult> syncResults) {
+        assertEquals(1, syncResults.size());
+
+        RequestObject<SimpleFooProxy> editRequest = req.simpleFooRequest().persistAndReturnSelf(
+            returned);
+        SimpleFooProxy editableFoo = editRequest.edit(returned);
+        editableFoo.setUserName("A");
+
+        editRequest.fire(new ShouldNotSuccedReceiver<SimpleFooProxy>(
+            returned.stableId()));
+      }
+    });
+  }
+
+  private void checkStableIdEquals(SimpleFooProxy expected,
+      SimpleFooProxy actual) {
+    assertNotSame(expected.stableId(), actual.stableId());
+    assertEquals(expected.stableId(), actual.stableId());
+    assertEquals(expected.stableId().hashCode(), actual.stableId().hashCode());
+
+    // No assumptions about the proxy objects (being proxies and all)
+    assertNotSame(expected, actual);
+    assertFalse(expected.equals(actual));
   }
 }