Fix the one spot where we were accidentally relying on a cached record,
via an update event, and ensure that can't happen again.

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

Review by: amitmanjhi@google.com

git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8569 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/app/place/AbstractRecordListActivity.java b/user/src/com/google/gwt/app/place/AbstractRecordListActivity.java
index 99ebb72..d13b57f 100644
--- a/user/src/com/google/gwt/app/place/AbstractRecordListActivity.java
+++ b/user/src/com/google/gwt/app/place/AbstractRecordListActivity.java
@@ -163,8 +163,7 @@
       }
     };
 
-    createRangeRequest(range).forProperties(getView().getProperties()).fire(
-        callback);
+    fireRangeRequest(range, callback);
   }
 
   public void onStop() {
@@ -233,6 +232,12 @@
     placeController.goTo(new ProxyPlace(record, Operation.DETAILS));
   }
 
+  private void fireRangeRequest(final Range range,
+      final Receiver<List<R>> callback) {
+    createRangeRequest(range).forProperties(getView().getProperties()).fire(
+        callback);
+  }
+
   private void getLastPage() {
     fireCountRequest(new Receiver<Long>() {
       public void onSuccess(Long response, Set<SyncResult> syncResults) {
@@ -266,8 +271,16 @@
   }
 
   private void update(R record) {
-    Integer row = recordToRow.get(record.getId());
-    getView().asHasData().setRowData(row, Collections.singletonList(record));
+    final Integer row = recordToRow.get(record.getId());
+    if (row == null) {
+      return;
+    }
+    fireRangeRequest(new Range(row, 1), new Receiver<List<R>>() {
+      public void onSuccess(List<R> response, Set<SyncResult> syncResults) {
+        getView().asHasData().setRowData(row,
+            Collections.singletonList(response.get(0)));
+      }
+    });
   }
 
   private void updateSelection(Place newPlace) {
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 150aca5..f7cc91c 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImpl.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImpl.java
@@ -184,8 +184,7 @@
           master.records.put(key, value);
           masterRecord = value;
           toRemove.add(new RecordKey(datastoreId, futureKey.schema, RequestFactoryJsonImpl.IS_FUTURE));
-          master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
-              masterRecord, WriteOperation.CREATE));
+          requestFactory.postChangeEvent(masterRecord, WriteOperation.CREATE);
           syncResults.add(makeSyncResult(masterRecord, null, futureKey.id));
         } else {
           // do not change the masterRecord or fire event
@@ -213,8 +212,7 @@
         if (masterRecord != null) {
           if (violations == NULL_VIOLATIONS) {
             master.records.remove(key);
-            master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
-                masterRecord, WriteOperation.DELETE));
+            requestFactory.postChangeEvent(masterRecord, WriteOperation.DELETE);
             syncResults.add(makeSyncResult(masterRecord, null, null));
           } else {
             // do not change the masterRecord or fire event
@@ -237,8 +235,7 @@
           assert masterRecord != null;
           masterRecord.merge(entry.getValue());
           toRemove.add(key);
-          master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
-              masterRecord, WriteOperation.UPDATE));
+          requestFactory.postChangeEvent(masterRecord, WriteOperation.UPDATE);
           syncResults.add(makeSyncResult(masterRecord, null, null));
         } else {
           // do not change the masterRecord or fire event
@@ -466,7 +463,7 @@
   }
 
   private RecordJsoImpl newChangeRecord(RecordImpl fromRecord) {
-    return RecordJsoImpl.emptyCopy(fromRecord);
+    return RecordJsoImpl.emptyCopy(fromRecord.asJso());
   }
 
   private void processToRemove(Set<RecordKey> toRemove,
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RecordJsoImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/RecordJsoImpl.java
index de31d43..4cf57e4 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/RecordJsoImpl.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/RecordJsoImpl.java
@@ -73,10 +73,10 @@
     return copy;
   }
 
-  public static RecordJsoImpl emptyCopy(RecordImpl from) {
-    Long id = from.get(Record.id);
-    Integer version = from.get(Record.version);
-    final RecordSchema<?> schema = from.getSchema();
+  public static RecordJsoImpl emptyCopy(RecordJsoImpl jso) {
+    Long id = jso.get(Record.id);
+    Integer version = jso.get(Record.version);
+    final RecordSchema<?> schema = jso.getSchema();
 
     return create(id, version, schema);
   }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RecordSchema.java b/user/src/com/google/gwt/requestfactory/client/impl/RecordSchema.java
index 1991941..fd2b05e 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/RecordSchema.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/RecordSchema.java
@@ -60,12 +60,6 @@
   public abstract RecordChangedEvent<?, ?> createChangeEvent(Record record,
       WriteOperation writeOperation);
 
-  public RecordChangedEvent<?, ?> createChangeEvent(RecordJsoImpl jsoRecord,
-      WriteOperation writeOperation) {
-    R record = create(jsoRecord);
-    return createChangeEvent(record, writeOperation);
-  }
-
   // TODO(rjrjr) rename getProxyClass
   public abstract Class<? extends Record> getToken();
 }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java
index 65ba21f..2a4b169 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java
@@ -29,6 +29,7 @@
 import com.google.gwt.requestfactory.shared.RequestEvent.State;
 import com.google.gwt.user.client.Window.Location;
 import com.google.gwt.valuestore.shared.Record;
+import com.google.gwt.valuestore.shared.WriteOperation;
 
 import java.util.HashMap;
 import java.util.Map;
@@ -65,7 +66,7 @@
 
   private ValueStoreJsonImpl valueStore;
 
-  private EventBus handlerManager;
+  private EventBus eventBus;
 
   public com.google.gwt.valuestore.shared.Record create(
       Class<? extends Record> token, RecordToTypeMap recordToTypeMap) {
@@ -84,7 +85,7 @@
     builder.setHeader("Content-Type", RequestFactory.JSON_CONTENT_TYPE_UTF8);
     builder.setHeader("pageurl", Location.getHref());
     builder.setRequestData(ClientRequestHelper.getRequestString(requestObject.getRequestData().getRequestMap(
-        ((AbstractRequest<?,?>) requestObject).deltaValueStore.toJson())));
+        ((AbstractRequest<?, ?>) requestObject).deltaValueStore.toJson())));
     builder.setCallback(new RequestCallback() {
 
       public void onError(Request request, Throwable exception) {
@@ -120,7 +121,7 @@
           e);
     }
   }
-  
+
   public Class<? extends Record> getClass(Record proxy) {
     return ((RecordImpl) proxy).getSchema().getToken();
   }
@@ -138,11 +139,11 @@
   }
 
   /**
-   * @param handlerManager
+   * @param eventBus
    */
-  public void init(EventBus handlerManager) {
-    this.valueStore = new ValueStoreJsonImpl(handlerManager);
-    this.handlerManager = handlerManager;
+  public void init(EventBus eventBus) {
+    this.valueStore = new ValueStoreJsonImpl();
+    this.eventBus = eventBus;
     Logger.getLogger("").addHandler(
         new RequestFactoryLogHandler(this, Level.WARNING, wireLogger.getName()));
     logger.fine("Successfully initialized RequestFactory");
@@ -192,8 +193,18 @@
     return valueStore;
   }
 
-  private Record createFuture(
-      RecordSchema<? extends Record> schema) {
+  void postChangeEvent(RecordJsoImpl newJsoRecord, WriteOperation op) {
+    /*
+     * Ensure event receivers aren't accidentally using cached info by making an
+     * unpopulated copy of the record.
+     */
+    newJsoRecord = RecordJsoImpl.emptyCopy(newJsoRecord);
+    Record javaRecord = newJsoRecord.getSchema().create(newJsoRecord);    
+    eventBus.fireEvent(newJsoRecord.getSchema().createChangeEvent(javaRecord,
+        op));
+  }
+
+  private Record createFuture(RecordSchema<? extends Record> schema) {
     Long futureId = ++currentFutureId;
     RecordJsoImpl newRecord = RecordJsoImpl.create(futureId, INITIAL_VERSION,
         schema);
@@ -203,6 +214,6 @@
   }
 
   private void postRequestEvent(State received, Response response) {
-    handlerManager.fireEvent(new RequestEvent(received, response));
+    eventBus.fireEvent(new RequestEvent(received, response));
   }
 }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/ValueStoreJsonImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/ValueStoreJsonImpl.java
index 765f296..4555970 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/ValueStoreJsonImpl.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/ValueStoreJsonImpl.java
@@ -16,7 +16,6 @@
 package com.google.gwt.requestfactory.client.impl;
 
 import com.google.gwt.core.client.JsArray;
-import com.google.gwt.event.shared.EventBus;
 import com.google.gwt.valuestore.shared.Record;
 import com.google.gwt.valuestore.shared.WriteOperation;
 
@@ -33,14 +32,8 @@
 public class ValueStoreJsonImpl {
   // package protected fields for use by DeltaValueStoreJsonImpl
 
-  final EventBus eventBus;
-
   final Map<RecordKey, RecordJsoImpl> records = new HashMap<RecordKey, RecordJsoImpl>();
 
-  ValueStoreJsonImpl(EventBus eventBus) {
-    this.eventBus = eventBus;
-  }
-
   public Record getRecordBySchemaAndId(RecordSchema<?> schema, 
       Long id) {
     if (id == null) {
@@ -64,34 +57,27 @@
     }
   }
 
-  /**
-   * @param newRecord
-   * @param i
-   * @param array
-   * @param requestFactory
-   */
-  private void setRecordInList(RecordJsoImpl newRecord, int i,
+  private void setRecordInList(RecordJsoImpl newJsoRecord, int i,
       JsArray<RecordJsoImpl> array, RequestFactoryJsonImpl requestFactory) {
-    RecordKey recordKey = new RecordKey(newRecord, RequestFactoryJsonImpl.NOT_FUTURE);
-    newRecord.setValueStore(this);
-    newRecord.setRequestFactory(requestFactory);
+    RecordKey recordKey = new RecordKey(newJsoRecord, RequestFactoryJsonImpl.NOT_FUTURE);
+    newJsoRecord.setValueStore(this);
+    newJsoRecord.setRequestFactory(requestFactory);
     
     RecordJsoImpl oldRecord = records.get(recordKey);
     if (oldRecord == null) {
-      records.put(recordKey, newRecord);
+      records.put(recordKey, newJsoRecord);
       // TODO: need to fire a create event.
     } else {
       // TODO: Merging is not the correct thing to do but it works as long as we
       // don't have filtering by properties. Need to revisit this once response
       // only has a subset of all properties.
-      boolean changed = oldRecord.merge(newRecord);
-      newRecord = oldRecord.cast();
+      boolean changed = oldRecord.merge(newJsoRecord);
+      newJsoRecord = oldRecord.cast();
       if (array != null) {
-        array.set(i, newRecord);
+        array.set(i, newJsoRecord);
       }
       if (changed) {
-        eventBus.fireEvent(newRecord.getSchema().createChangeEvent(newRecord,
-            WriteOperation.UPDATE));
+        requestFactory.postChangeEvent(newJsoRecord, WriteOperation.UPDATE);
       }
     }
   }
diff --git a/user/src/com/google/gwt/valuestore/shared/RecordChangedEvent.java b/user/src/com/google/gwt/valuestore/shared/RecordChangedEvent.java
index a71f1ef..7f58120 100644
--- a/user/src/com/google/gwt/valuestore/shared/RecordChangedEvent.java
+++ b/user/src/com/google/gwt/valuestore/shared/RecordChangedEvent.java
@@ -25,6 +25,13 @@
  * </span>
  * </p>
  * Abstract base class for an event announcing changes to a {@link Record}.
+ * <p>
+ * Note that this event includes an unpopulated copy of the changed record
+ * &mdash; all properties are undefined except it's id. That is, the event
+ * includes only enough information for receivers to issue requests to get
+ * themselves fresh copies of the record.
+ * <p>
+ * TODO: rather than an empty record, consider using a string token 
  * 
  * @param <R> the type of the record
  * @param <H> the type of event handler
@@ -41,6 +48,10 @@
     this.writeOperation = writeOperation;
   }
 
+  /**
+   * @return an unpopulated copy of the changed record &mdash; all properties
+   *         are undefined except it's id
+   */
   public R getRecord() {
     return record;
   }
diff --git a/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java b/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java
index 287b474..7753b41 100644
--- a/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImplTest.java
@@ -72,7 +72,7 @@
 
   @Override
   public void gwtSetUp() {
-    valueStore = new ValueStoreJsonImpl(null);
+    valueStore = new ValueStoreJsonImpl();
     requestFactory = new RequestFactoryJsonImpl() {
 
       public Record create(Class token) {
diff --git a/user/test/com/google/gwt/requestfactory/client/impl/RecordJsoImplTest.java b/user/test/com/google/gwt/requestfactory/client/impl/RecordJsoImplTest.java
index 413899c..6b5f637 100644
--- a/user/test/com/google/gwt/requestfactory/client/impl/RecordJsoImplTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/impl/RecordJsoImplTest.java
@@ -40,8 +40,7 @@
   }
 
   public void testEmptyCopy() {
-    RecordJsoImpl emptyCopy = RecordJsoImpl.emptyCopy(new RecordImpl(
-        getPopulatedJso(), false));
+    RecordJsoImpl emptyCopy = RecordJsoImpl.emptyCopy(getPopulatedJso());
     testMinimalJso(emptyCopy, SCHEMA_PRESENT);
   }