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