Revision: 8030
Author: [email protected]
Date: Mon May  3 13:28:30 2010
Log: Added server side validation using JSR 303. On the server side, one can now do
validations before persisting an entity. Also, added a callback for sync
request so that any validation errors can be shown on the client.

Patch by: amitmanjhi
Review by: rjrjr (desk review)

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

http://code.google.com/p/google-web-toolkit/source/detail?r=8030

Added:
/branches/2.1/bikeshed/src/com/google/gwt/requestfactory/shared/SyncResult.java /branches/2.1/bikeshed/src/com/google/gwt/valuestore/client/SyncResultImpl.java
 /branches/2.1/bikeshed/war/temp-libs
/branches/2.1/bikeshed/war/temp-libs/com.springsource.org.apache.log4j-1.2.15.jar
 /branches/2.1/bikeshed/war/temp-libs/hibernate-validator-4.0.2.GA.jar
 /branches/2.1/bikeshed/war/temp-libs/slf4j-api-1.5.11.jar
 /branches/2.1/bikeshed/war/temp-libs/slf4j-log4j12-1.5.11.jar
 /branches/2.1/bikeshed/war/temp-libs/validation-api-1.0.0.GA-sources.jar
 /branches/2.1/bikeshed/war/temp-libs/validation-api-1.0.0.GA.jar
Modified:
 /branches/2.1/bikeshed/eclipse.README
/branches/2.1/bikeshed/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java /branches/2.1/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java /branches/2.1/bikeshed/src/com/google/gwt/requestfactory/shared/SyncRequest.java /branches/2.1/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java /branches/2.1/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.java /branches/2.1/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java /branches/2.1/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java /branches/2.1/bikeshed/src/com/google/gwt/valuestore/client/DeltaValueStoreJsonImpl.java /branches/2.1/bikeshed/src/com/google/gwt/valuestore/shared/DeltaValueStore.java

=======================================
--- /dev/null
+++ /branches/2.1/bikeshed/src/com/google/gwt/requestfactory/shared/SyncResult.java Mon May 3 13:28:30 2010
@@ -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.shared;
+
+import com.google.gwt.valuestore.shared.Record;
+
+import java.util.Map;
+
+/**
+ * Result per record of a SyncRequest.
+ */
+public interface SyncResult {
+  boolean hasViolations();
+
+  Record getRecord();
+
+  Map<String, String> getViolations();
+}
=======================================
--- /dev/null
+++ /branches/2.1/bikeshed/src/com/google/gwt/valuestore/client/SyncResultImpl.java Mon May 3 13:28:30 2010
@@ -0,0 +1,48 @@
+/*
+ * 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.valuestore.client;
+
+import com.google.gwt.requestfactory.shared.SyncResult;
+import com.google.gwt.valuestore.shared.Record;
+
+import java.util.Map;
+
+/**
+ * Concrete implementation of SyncResult.
+ */
+public class SyncResultImpl implements SyncResult {
+
+  private final Record record;
+  private final Map<String, String> violations;
+
+  public SyncResultImpl(Record record, Map<String, String> violations) {
+    this.record = record;
+    this.violations = violations;
+  }
+
+  public Record getRecord() {
+    return record;
+  }
+
+  public Map<String, String> getViolations() {
+    return violations;
+  }
+
+  public boolean hasViolations() {
+    return violations != null && violations.size() > 0;
+  }
+
+}
=======================================
--- /dev/null   
+++ /branches/2.1/bikeshed/war/temp-libs/com.springsource.org.apache.log4j-1.2.15.jar Mon May 3 13:28:30 2010
Binary file, no diff available.
=======================================
--- /dev/null   
+++ /branches/2.1/bikeshed/war/temp-libs/hibernate-validator-4.0.2.GA.jar Mon May 3 13:28:30 2010
Binary file, no diff available.
=======================================
--- /dev/null   
+++ /branches/2.1/bikeshed/war/temp-libs/slf4j-api-1.5.11.jar Mon May 3 13:28:30 2010
Binary file, no diff available.
=======================================
--- /dev/null   
+++ /branches/2.1/bikeshed/war/temp-libs/slf4j-log4j12-1.5.11.jar Mon May 3 13:28:30 2010
Binary file, no diff available.
=======================================
--- /dev/null   
+++ /branches/2.1/bikeshed/war/temp-libs/validation-api-1.0.0.GA-sources.jar Mon May 3 13:28:30 2010
Binary file, no diff available.
=======================================
--- /dev/null   
+++ /branches/2.1/bikeshed/war/temp-libs/validation-api-1.0.0.GA.jar Mon May 3 13:28:30 2010
Binary file, no diff available.
=======================================
--- /branches/2.1/bikeshed/eclipse.README       Wed Apr 28 13:19:55 2010
+++ /branches/2.1/bikeshed/eclipse.README       Mon May  3 13:28:30 2010
@@ -33,5 +33,8 @@
       * src/com/google/gwt/sample/bikeshed/stocks/shared
       * src/com/google/gwt/sample/expenses/server/domain
* Java Build Path > Libraries > Add Variable > GWT_TOOLS, Extend > redist/json/r2_20080312/json.jar + * Java Build Path > Extenal Library > bikeshed/war/temp-lib/validation-api-1.0.0.GA.jar
 * Copy tools/redist/json/r2_20080312/json.jar to bikeshed/war/WEB_INF/lib
+* Copy all jars from  bikeshed/war/temp-lib to bikeshed/war/WEB-INF/lib
* Right click on the bikeshed project and choose Run as > Web Application. Choose from the various .html files
+*
=======================================
--- /branches/2.1/bikeshed/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java Fri Apr 30 09:10:49 2010 +++ /branches/2.1/bikeshed/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java Mon May 3 13:28:30 2010
@@ -21,13 +21,17 @@
 import com.google.gwt.http.client.RequestCallback;
 import com.google.gwt.http.client.RequestException;
 import com.google.gwt.http.client.Response;
+import com.google.gwt.requestfactory.shared.Receiver;
 import com.google.gwt.requestfactory.shared.RequestFactory;
 import com.google.gwt.requestfactory.shared.SyncRequest;
+import com.google.gwt.requestfactory.shared.SyncResult;
 import com.google.gwt.requestfactory.shared.impl.RequestDataManager;
 import com.google.gwt.valuestore.client.DeltaValueStoreJsonImpl;
 import com.google.gwt.valuestore.client.ValueStoreJsonImpl;
 import com.google.gwt.valuestore.shared.DeltaValueStore;

+import java.util.Set;
+
 /**
  * Base implementation of RequestFactory.
  */
@@ -82,6 +86,7 @@

     return new SyncRequest() {

+      Receiver<Set<SyncResult>> receiver = null;
       public void fire() {

         RequestBuilder builder = new RequestBuilder(RequestBuilder.POST,
@@ -98,8 +103,7 @@
public void onResponseReceived(Request request, Response response) {
             if (200 == response.getStatusCode()) {
               // parse the return value.
-
-              jsonDeltas.commit(response.getText());
+              receiver.onSuccess(jsonDeltas.commit(response.getText()));
             } else {
               // shell.error.setInnerText(SERVER_ERROR + " ("
               // + response.getStatusText() + ")");
@@ -114,6 +118,11 @@
           // ")");
         }
       }
+
+      public SyncRequest to(Receiver<Set<SyncResult>> receiver) {
+        this.receiver = receiver;
+        return this;
+      }
     };
   }
 }
=======================================
--- /branches/2.1/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java Sat May 1 07:18:49 2010 +++ /branches/2.1/bikeshed/src/com/google/gwt/requestfactory/server/RequestFactoryServlet.java Mon May 3 13:28:30 2010
@@ -48,6 +48,10 @@
 import javax.servlet.http.HttpServlet;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import javax.validation.ConstraintViolation;
+import javax.validation.Validation;
+import javax.validation.Validator;
+import javax.validation.ValidatorFactory;

 /**
  * Handles GWT RequestFactory JSON requests. Configured via servlet context
@@ -206,6 +210,7 @@
         recordObject.get("id"), propertiesInRecord.get("id"));

     // persist
+    Set<ConstraintViolation<Object>> violations = null;
     if (writeOperation == WriteOperation.DELETE) {
       entity.getMethod("remove").invoke(entityInstance);
     } else {
@@ -223,11 +228,20 @@
               propertyType).invoke(entityInstance, propertyValue);
         }
       }
-      entity.getMethod("persist").invoke(entityInstance);
+
+      // validations check..
+ ValidatorFactory validatorFactory = Validation.buildDefaultValidatorFactory();
+      Validator validator = validatorFactory.getValidator();
+
+      violations = validator.validate(entityInstance);
+      if (violations.isEmpty()) {
+        entity.getMethod("persist").invoke(entityInstance);
+      }
     }

     // return data back.
-    return getReturnRecord(writeOperation, entityInstance, recordObject);
+    return getReturnRecord(writeOperation, entityInstance, recordObject,
+        violations);
   }

private Collection<Property<?>> allProperties(Class<? extends Record> clazz) {
@@ -476,15 +490,24 @@
   }

   private JSONObject getReturnRecord(WriteOperation writeOperation,
- Object entityInstance, JSONObject recordObject) throws SecurityException,
+      Object entityInstance, JSONObject recordObject,
+ Set<ConstraintViolation<Object>> violations) throws SecurityException,
       JSONException, IllegalAccessException, InvocationTargetException,
       NoSuchMethodException {

     JSONObject returnObject = new JSONObject();
-    // currently sending back only two properties.
-    for (String propertyName : new String[] {"id", "version"}) {
-      returnObject.put(propertyName, getPropertyValueFromDataStore(
-          entityInstance, propertyName));
+    if (writeOperation != WriteOperation.CREATE || violations == null) {
+      // currently sending back only two properties.
+      for (String propertyName : new String[] {"id", "version"}) {
+        if ("version".equals(propertyName) && violations != null) {
+          continue;
+        }
+        returnObject.put(propertyName, getPropertyValueFromDataStore(
+            entityInstance, propertyName));
+      }
+    }
+    if (violations != null) {
+      returnObject.put("violations", getViolationsAsJson(violations));
     }
     if (writeOperation == WriteOperation.CREATE) {
       returnObject.put("futureId", recordObject.getString("id"));
@@ -507,6 +530,16 @@
throw new IllegalArgumentException("id is of type: " + idValue.getClass()
         + ",  expected type: " + idType);
   }
+
+  private JSONObject getViolationsAsJson(
+      Set<ConstraintViolation<Object>> violations) throws JSONException {
+    JSONObject violationsAsJson = new JSONObject();
+    for (ConstraintViolation<Object> violation : violations) {
+      violationsAsJson.put(violation.getPropertyPath().toString(),
+          violation.getMessage());
+    }
+    return violationsAsJson;
+  }

   /**
* returns true if the property has been requested. TODO: use the properties
=======================================
--- /branches/2.1/bikeshed/src/com/google/gwt/requestfactory/shared/SyncRequest.java Fri Mar 12 17:03:36 2010 +++ /branches/2.1/bikeshed/src/com/google/gwt/requestfactory/shared/SyncRequest.java Mon May 3 13:28:30 2010
@@ -15,9 +15,13 @@
  */
 package com.google.gwt.requestfactory.shared;

+import java.util.Set;
+
 /**
  * Request to commit CRUD operations accumulated in a DeltaValueStore.
  */
 public interface SyncRequest {
   void fire();
-}
+
+  SyncRequest to(Receiver<Set<SyncResult>> receiver);
+}
=======================================
--- /branches/2.1/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java Thu Apr 29 07:36:37 2010 +++ /branches/2.1/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditActivity.java Mon May 3 13:28:30 2010
@@ -18,6 +18,7 @@
 import com.google.gwt.app.place.AbstractActivity;
 import com.google.gwt.app.place.PlaceController;
 import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.requestfactory.shared.SyncResult;
 import com.google.gwt.sample.expenses.gwt.client.place.ListScaffoldPlace;
 import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
 import com.google.gwt.sample.expenses.gwt.request.EmployeeRecord;
@@ -27,9 +28,11 @@
 import com.google.gwt.valuestore.shared.Value;
 import com.google.gwt.valuestore.ui.RecordEditView;

+import java.util.Set;
+
 /**
- * An activity that requests all info on an employee, allows the user to edit it,
- * and persists the results.
+ * An activity that requests all info on an employee, allows the user to edit
+ * it, and persists the results.
  */
 public class EmployeeEditActivity extends AbstractActivity implements
     RecordEditView.Delegate {
@@ -75,10 +78,29 @@
   public void saveClicked() {
     if (deltas.isChanged()) {
       view.setEnabled(false);
-      DeltaValueStore toCommit = deltas;
+      final DeltaValueStore toCommit = deltas;
       deltas = null;
-      requests.syncRequest(toCommit).fire(); // TODO Need callback, idiot
-      placeController.goTo(new ListScaffoldPlace(EmployeeRecord.class));
+ Receiver<Set<SyncResult>> receiver = new Receiver<Set<SyncResult>>() {
+        public void onSuccess(Set<SyncResult> response) {
+          boolean hasViolations = false;
+          for (SyncResult syncResult : response) {
+            if (syncResult.getRecord().getId().equals(id)) {
+              if (syncResult.hasViolations()) {
+                hasViolations = true;
+                view.showErrors(syncResult.getViolations());
+              }
+            }
+          }
+          if (!hasViolations) {
+ placeController.goTo(new ListScaffoldPlace(EmployeeRecord.class));
+          } else {
+            view.setEnabled(true);
+            deltas = toCommit;
+            deltas.clearUsed();
+          }
+        }
+      };
+      requests.syncRequest(toCommit).to(receiver).fire();
     }
   }

@@ -87,6 +109,7 @@
       public void onSuccess(EmployeeRecord record) {
         view.setEnabled(true);
         view.setValue(record);
+        view.showErrors(null);
         display.showActivityWidget(view);
       }
     };
=======================================
--- /branches/2.1/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.java Mon May 3 09:01:39 2010 +++ /branches/2.1/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/employee/EmployeeEditView.java Mon May 3 13:28:30 2010
@@ -112,17 +112,10 @@
     this.record = value;
     DATA_BINDER.setValue(this, value);
   }
-
-  @UiHandler("save")
-  void onSave(@SuppressWarnings("unused") ClickEvent event) {
-    delegate.saveClicked();
-  }

   public void showErrors(Map<String, String> errorMap) {
     // TODO Make EditorSupport do this
-
     errors.setInnerText("");
-
     if (errorMap == null || errorMap.isEmpty()) {
       return;
     }
@@ -146,4 +139,9 @@
       errors.appendChild(div);
     }
   }
-}
+
+  @UiHandler("save")
+  void onSave(@SuppressWarnings("unused") ClickEvent event) {
+    delegate.saveClicked();
+  }
+}
=======================================
--- /branches/2.1/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java Thu Apr 29 07:36:37 2010 +++ /branches/2.1/bikeshed/src/com/google/gwt/sample/expenses/gwt/ui/report/ReportEditActivity.java Mon May 3 13:28:30 2010
@@ -18,6 +18,7 @@
 import com.google.gwt.app.place.AbstractActivity;
 import com.google.gwt.app.place.PlaceController;
 import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.requestfactory.shared.SyncResult;
 import com.google.gwt.sample.expenses.gwt.client.place.ListScaffoldPlace;
 import com.google.gwt.sample.expenses.gwt.client.place.ScaffoldPlace;
 import com.google.gwt.sample.expenses.gwt.request.ExpensesRequestFactory;
@@ -27,6 +28,8 @@
 import com.google.gwt.valuestore.shared.Value;
 import com.google.gwt.valuestore.ui.RecordEditView;

+import java.util.Set;
+
 /**
* An activity that requests all info on a report, allows the user to edit it,
  * and persists the results.
@@ -75,10 +78,29 @@
   public void saveClicked() {
     if (deltas.isChanged()) {
       view.setEnabled(false);
-      DeltaValueStore toCommit = deltas;
+      final DeltaValueStore toCommit = deltas;
       deltas = null;
-      requests.syncRequest(toCommit).fire(); // TODO Need callback, idiot
-      placeController.goTo(new ListScaffoldPlace(ReportRecord.class));
+ Receiver<Set<SyncResult>> receiver = new Receiver<Set<SyncResult>>() {
+        public void onSuccess(Set<SyncResult> response) {
+          boolean hasViolations = false;
+          for (SyncResult syncResult : response) {
+            if (syncResult.getRecord().getId().equals(id)) {
+              if (syncResult.hasViolations()) {
+                hasViolations = true;
+                view.showErrors(syncResult.getViolations());
+              }
+            }
+          }
+          if (!hasViolations) {
+ placeController.goTo(new ListScaffoldPlace(ReportRecord.class));
+          } else {
+            view.setEnabled(true);
+            deltas = toCommit;
+            deltas.clearUsed();
+          }
+        }
+      };
+      requests.syncRequest(toCommit).to(receiver).fire();
     }
   }

@@ -87,6 +109,7 @@
       public void onSuccess(ReportRecord record) {
         view.setEnabled(true);
         view.setValue(record);
+        view.showErrors(null);
         display.showActivityWidget(view);
       }
     };
=======================================
--- /branches/2.1/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java Sat May 1 07:18:49 2010 +++ /branches/2.1/bikeshed/src/com/google/gwt/sample/expenses/server/domain/Employee.java Mon May 3 13:28:30 2010
@@ -24,6 +24,8 @@
 import javax.persistence.GenerationType;
 import javax.persistence.Id;
 import javax.persistence.Version;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;

 /**
  * The Employee domain object.
@@ -84,8 +86,10 @@
     }
   }

+  @Size(min = 3, max = 30)
   private String userName;

+  @NotNull
   private String displayName;

   private String password;
=======================================
--- /branches/2.1/bikeshed/src/com/google/gwt/valuestore/client/DeltaValueStoreJsonImpl.java Wed Apr 28 06:43:26 2010 +++ /branches/2.1/bikeshed/src/com/google/gwt/valuestore/client/DeltaValueStoreJsonImpl.java Mon May 3 13:28:30 2010
@@ -17,6 +17,7 @@

 import com.google.gwt.core.client.JavaScriptObject;
 import com.google.gwt.core.client.JsArray;
+import com.google.gwt.requestfactory.shared.SyncResult;
 import com.google.gwt.requestfactory.shared.RequestFactory.WriteOperation;
 import com.google.gwt.valuestore.shared.DeltaValueStore;
 import com.google.gwt.valuestore.shared.Property;
@@ -58,6 +59,14 @@
     protected ReturnRecord() {
     }

+    public final native void fillViolations(HashMap<String, String> s) /*-{
+      for (key in this.violations) {
+        if (this.violations.hasOwnProperty(key)) {
+ [email protected]::put(Ljava/lang/Object;Ljava/lang/Object;)(key, this.violations[key]);
+        }
+      }
+    }-*/;
+
     public final native String getFutureId()/*-{
       return this.futureId;
     }-*/;
@@ -73,6 +82,14 @@
     public final native boolean hasFutureId()/*-{
       return 'futureId' in this;
     }-*/;
+
+    public final native boolean hasId()/*-{
+      return 'id' in this;
+    }-*/;
+
+    public final native boolean hasViolations()/*-{
+      return 'violations' in this;
+    }-*/;
   }

   private static class FutureIdGenerator {
@@ -92,6 +109,8 @@
       return new String(futureId + "");
     }
   }
+
+ private static final HashMap<String, String> NULL_VIOLATIONS = new HashMap<String, String>();

   private static final Integer INITIAL_VERSION = 1;

@@ -115,69 +134,118 @@
     throw new UnsupportedOperationException("Auto-generated method stub");
   }

-  public void commit(String response) {
+  public void clearUsed() {
+    used = false;
+  }
+
+  public Set<SyncResult> commit(String response) {
+    Set<SyncResult> syncResults = new HashSet<SyncResult>();
     JavaScriptObject returnedJso = ReturnRecord.getJsoResponse(response);
     HashSet<String> keys = new HashSet<String>();
     ReturnRecord.fillKeys(returnedJso, keys);

+    Set<RecordKey> toRemove = new HashSet<RecordKey>();
     if (keys.contains(WriteOperation.CREATE.name())) {
JsArray<ReturnRecord> newRecords = ReturnRecord.getRecords(returnedJso,
           WriteOperation.CREATE.name());
-      // construct a map from futureId to the datastore Id
+      /*
+ * construct 2 maps: (i) futureId to the datastore Id, (ii) futureId to
+       * violationsMap
+       */
Map<Object, Object> futureToDatastoreId = new HashMap<Object, Object>(); + Map<String, Map<String, String>> violationsMap = new HashMap<String, Map<String, String>>();
       int length = newRecords.length();
       for (int i = 0; i < length; i++) {
         ReturnRecord sync = newRecords.get(i);
-        futureToDatastoreId.put(sync.getFutureId(), sync.getId());
+        if (sync.hasViolations()) {
+          // does not have an id.
+          assert !sync.hasId();
+ HashMap<String, String> violations = new HashMap<String, String>();
+          sync.fillViolations(violations);
+          violationsMap.put(sync.getFutureId(), violations);
+        } else {
+          violationsMap.put(sync.getFutureId(), NULL_VIOLATIONS);
+          futureToDatastoreId.put(sync.getFutureId(), sync.getId());
+        }
       }

for (Map.Entry<RecordKey, RecordJsoImpl> entry : creates.entrySet()) {
         final RecordKey futureKey = entry.getKey();
-        Object datastoreId = futureToDatastoreId.get(futureKey.id);
-        assert datastoreId != null;
-        futureIdGenerator.delete(futureKey.id.toString());
-
-        final RecordKey key = new RecordKey(datastoreId, futureKey.schema);
-        RecordJsoImpl value = entry.getValue();
-        value.set(Record.id, datastoreId.toString());
-        RecordJsoImpl masterRecord = master.records.get(key);
-        assert masterRecord == null;
-        master.records.put(key, value);
-        masterRecord = value;
- master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
-            masterRecord, WriteOperation.CREATE));
+        Map<String, String> violations = violationsMap.get(futureKey.id);
+        assert violations != null;
+        if (violations == NULL_VIOLATIONS) {
+          Object datastoreId = futureToDatastoreId.get(futureKey.id);
+          assert datastoreId != null;
+          futureIdGenerator.delete(futureKey.id.toString());
+ final RecordKey key = new RecordKey(datastoreId, futureKey.schema);
+          RecordJsoImpl value = entry.getValue();
+          value.set(Record.id, datastoreId.toString());
+          RecordJsoImpl masterRecord = master.records.get(key);
+          assert masterRecord == null;
+          master.records.put(key, value);
+          masterRecord = value;
+          toRemove.add(key);
+ master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
+              masterRecord, WriteOperation.CREATE));
+          syncResults.add(new SyncResultImpl(masterRecord, null));
+        } else {
+          // do not change the masterRecord or fire event
+ syncResults.add(new SyncResultImpl(entry.getValue(), violations));
+        }
       }
     }
-
+    processToRemove(toRemove, WriteOperation.CREATE);
+
+    toRemove.clear();
     if (keys.contains(WriteOperation.DELETE.name())) {
       JsArray<ReturnRecord> deletedRecords = ReturnRecord.getRecords(
           returnedJso, WriteOperation.DELETE.name());
-      Set<String> returnedKeys = getKeySet(deletedRecords);
+ Map<String, Map<String, String>> violationsMap = getViolationsMap(deletedRecords); for (Map.Entry<RecordKey, RecordJsoImpl> entry : deletes.entrySet()) {
         final RecordKey key = entry.getKey();
-        assert returnedKeys.contains(key.id);
-        RecordJsoImpl masterRecord = master.records.get(key);
-        assert masterRecord != null;
-        master.records.remove(key);
- master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
-            masterRecord, WriteOperation.DELETE));
+        Map<String, String> violations = violationsMap.get(key.id);
+        assert violations != null;
+        if (violations == NULL_VIOLATIONS) {
+          RecordJsoImpl masterRecord = master.records.get(key);
+          assert masterRecord != null;
+          master.records.remove(key);
+          toRemove.add(key);
+ master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
+              masterRecord, WriteOperation.DELETE));
+          syncResults.add(new SyncResultImpl(masterRecord, null));
+        } else {
+          // do not change the masterRecord or fire event
+ syncResults.add(new SyncResultImpl(entry.getValue(), violations));
+        }
       }
     }
-
+    processToRemove(toRemove, WriteOperation.DELETE);
+
+    toRemove.clear();
     if (keys.contains(WriteOperation.UPDATE.name())) {
       JsArray<ReturnRecord> updatedRecords = ReturnRecord.getRecords(
           returnedJso, WriteOperation.UPDATE.name());
-      Set<String> returnedKeys = getKeySet(updatedRecords);
+ Map<String, Map<String, String>> violationsMap = getViolationsMap(updatedRecords); for (Map.Entry<RecordKey, RecordJsoImpl> entry : updates.entrySet()) {
         final RecordKey key = entry.getKey();
-        assert returnedKeys.contains(key.id.toString());
-        RecordJsoImpl masterRecord = master.records.get(key);
-        assert masterRecord != null;
-        masterRecord.merge(entry.getValue());
- master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
-            masterRecord, WriteOperation.UPDATE));
+        Map<String, String> violations = violationsMap.get(key.id);
+        assert violations != null;
+        if (violations == NULL_VIOLATIONS) {
+          RecordJsoImpl masterRecord = master.records.get(key);
+          assert masterRecord != null;
+          masterRecord.merge(entry.getValue());
+          toRemove.add(key);
+ master.eventBus.fireEvent(masterRecord.getSchema().createChangeEvent(
+              masterRecord, WriteOperation.UPDATE));
+          syncResults.add(new SyncResultImpl(masterRecord, null));
+        } else {
+          // do not change the masterRecord or fire event
+ syncResults.add(new SyncResultImpl(entry.getValue(), violations));
+        }
       }
     }
+    processToRemove(toRemove, WriteOperation.UPDATE);
+    return syncResults;
   }

   // TODO: don't use RecordSchema
@@ -299,6 +367,22 @@

   public String toJson() {
     used = true;
+    if (operations.size() > 1) {
+      throw new UnsupportedOperationException(
+          "Currently, only one entity can be saved/persisted at a time");
+      /*
+ * TODO: Short-term todo is to allow multiple entities belonging to the + * same class to be persisted at the same time. The client side support + * for this operation is already in place. On the server side, this will
+       * entail persisting all entities as part of a single transaction. In
+ * particular, the transaction should fail if the validation check on any
+       * of the entities fail.
+       *
+ * Multiple entities belonging to different records can not be persisted + * at present due to the appEngine limitation of a transaction not being
+       * allowed to span multiple entity groups.
+       */
+    }
     StringBuffer jsonData = new StringBuffer("{");
     for (WriteOperation writeOperation : WriteOperation.values()) {
       String jsonDataForOperation = getJsonForOperation(writeOperation);
@@ -361,15 +445,6 @@
     requestData.append("]");
     return requestData.toString();
   }
-
-  private Set<String> getKeySet(JsArray<ReturnRecord> records) {
-    Set<String> returnSet = new HashSet<String>();
-    int length = records.length();
-    for (int i = 0; i < length; i++) {
-      returnSet.add(records.get(i).getId());
-    }
-    return returnSet;
-  }

   private Map<RecordKey, RecordJsoImpl> getRecordsMap(
       WriteOperation writeOperation) {
@@ -385,6 +460,24 @@
             + writeOperation.name());
     }
   }
+
+  private Map<String, Map<String, String>> getViolationsMap(
+      JsArray<ReturnRecord> records) {
+ Map<String, Map<String, String>> violationsMap = new HashMap<String, Map<String, String>>();
+    int length = records.length();
+    for (int i = 0; i < length; i++) {
+      ReturnRecord record = records.get(i);
+      HashMap<String, String> violations = null;
+      if (record.hasViolations()) {
+        violations = new HashMap<String, String>();
+        record.fillViolations(violations);
+      } else {
+        violations = NULL_VIOLATIONS;
+      }
+      violationsMap.put(record.getId(), violations);
+    }
+    return violationsMap;
+  }

   private <V> boolean isRealChange(Property<V> property, V value,
       RecordJsoImpl rawMasterRecord) {
@@ -416,4 +509,22 @@
   private RecordJsoImpl newChangeRecord(RecordImpl fromRecord) {
     return RecordJsoImpl.emptyCopy(fromRecord);
   }
-}
+
+  private void processToRemove(Set<RecordKey> toRemove,
+      WriteOperation writeOperation) {
+    for (RecordKey recordKey : toRemove) {
+      operations.remove(recordKey);
+      switch (writeOperation) {
+        case CREATE:
+          creates.remove(recordKey);
+          break;
+        case DELETE:
+          deletes.remove(recordKey);
+          break;
+        case UPDATE:
+          updates.remove(recordKey);
+          break;
+      }
+    }
+  }
+}
=======================================
--- /branches/2.1/bikeshed/src/com/google/gwt/valuestore/shared/DeltaValueStore.java Thu Apr 22 16:39:32 2010 +++ /branches/2.1/bikeshed/src/com/google/gwt/valuestore/shared/DeltaValueStore.java Mon May 3 13:28:30 2010
@@ -19,6 +19,13 @@
  * Set of changes to a ValueStore.
  */
 public interface DeltaValueStore extends ValueStore {
+
+  /**
+ * Enable a DeltaValueStore to be reused again. For example, when the edit
+   * fails on the server.
+   */
+  void clearUsed();
+
   Record create(Record existingRecord);

   void delete(Record record);

--
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to