Added a way for EntityProxy to have a stable equality using getStableId(). The getStableId() is stable across creates and updates on the client. Patch by: amitmanjhi Suggested by: robertvawter Review by: rjrjr (tbr) git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8733 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/requestfactory/client/SyncResultImpl.java b/user/src/com/google/gwt/requestfactory/client/SyncResultImpl.java index 7fd3136..e01dc46 100644 --- a/user/src/com/google/gwt/requestfactory/client/SyncResultImpl.java +++ b/user/src/com/google/gwt/requestfactory/client/SyncResultImpl.java
@@ -32,15 +32,15 @@ private final EntityProxy record; private final Map<String, String> violations; - private final Long futureId; + private final Object futureId; - public SyncResultImpl(EntityProxy record, Map<String, String> violations, Long futureId) { + public SyncResultImpl(EntityProxy record, Map<String, String> violations, Object futureId) { this.record = record; this.violations = violations; this.futureId = futureId; } - public Long getFutureId() { + public Object getFutureId() { return futureId; }
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 b47b399..ea086b2 100644 --- a/user/src/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImpl.java +++ b/user/src/com/google/gwt/requestfactory/client/impl/DeltaValueStoreJsonImpl.java
@@ -18,8 +18,9 @@ import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray; import com.google.gwt.requestfactory.client.SyncResultImpl; -import com.google.gwt.requestfactory.shared.Property; 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.SyncResult; import com.google.gwt.requestfactory.shared.WriteOperation; @@ -162,23 +163,31 @@ } for (Map.Entry<EntityProxyId, ProxyJsoImpl> entry : creates.entrySet()) { - final EntityProxyId futureKey = entry.getKey(); + final EntityProxyIdImpl futureKey = (EntityProxyIdImpl) entry.getKey(); // TODO change violationsMap to <Long, String> Map<String, String> violations = violationsMap.get(futureKey.id); assert violations != null; if (violations == NULL_VIOLATIONS) { Long datastoreId = futureToDatastoreId.get(futureKey.id); assert datastoreId != null; - final EntityProxyId key = new EntityProxyId(datastoreId, futureKey.schema, - RequestFactoryJsonImpl.NOT_FUTURE); + + // populate the two maps first. + requestFactory.datastoreToFutureMap.put(datastoreId, futureKey.schema, futureKey.id); + requestFactory.futureToDatastoreMap.put(futureKey.id, datastoreId); + final EntityProxyIdImpl key = getPersistedProxyId(datastoreId, futureKey.schema); ProxyJsoImpl value = entry.getValue(); - value.set(EntityProxy.id, datastoreId); + + // make a copy of value and set the id there. + // TODO (amitmanjhi): get all the data from the server. + ProxyJsoImpl copy = ProxyJsoImpl.emptyCopy(value); + copy.merge(value); + copy.set(EntityProxy.id, datastoreId); + ProxyJsoImpl masterRecord = master.records.get(key); assert masterRecord == null; - master.records.put(key, value); - masterRecord = value; - toRemove.add(new EntityProxyId(datastoreId, futureKey.schema, - RequestFactoryJsonImpl.IS_FUTURE)); + + masterRecord = copy; + toRemove.add(key); requestFactory.postChangeEvent(masterRecord, WriteOperation.CREATE); syncResults.add(makeSyncResult(masterRecord, null, futureKey.id)); } else { @@ -198,9 +207,8 @@ int length = deletedRecords.length(); for (int i = 0; i < length; i++) { - final EntityProxyId key = new EntityProxyId(deletedRecords.get(i).getId(), - requestFactory.getSchema(deletedRecords.get(i).getSchema()), - RequestFactoryJsonImpl.NOT_FUTURE); + final EntityProxyIdImpl key = getPersistedProxyId(deletedRecords.get(i).getId(), + requestFactory.getSchema(deletedRecords.get(i).getSchema())); Map<String, String> violations = violationsMap.get(key.id); assert violations != null; /* @@ -210,7 +218,7 @@ * with update events. */ if (violations == NULL_VIOLATIONS) { - requestFactory.postChangeEvent(ProxyJsoImpl.create(key.id, 1, + requestFactory.postChangeEvent(ProxyJsoImpl.create((Long) key.id, 1, key.schema, requestFactory), WriteOperation.DELETE); } ProxyJsoImpl masterRecord = master.records.get(key); @@ -230,14 +238,13 @@ int length = updatedRecords.length(); for (int i = 0; i < length; i++) { - final EntityProxyId key = new EntityProxyId(updatedRecords.get(i).getId(), - requestFactory.getSchema(updatedRecords.get(i).getSchema()), - RequestFactoryJsonImpl.NOT_FUTURE); + final EntityProxyIdImpl key = getPersistedProxyId(updatedRecords.get(i).getId(), + requestFactory.getSchema(updatedRecords.get(i).getSchema())); Map<String, String> violations = violationsMap.get(key.id); assert violations != null; // post change events if no violations. if (violations == NULL_VIOLATIONS) { - requestFactory.postChangeEvent(ProxyJsoImpl.create(key.id, 1, + requestFactory.postChangeEvent(ProxyJsoImpl.create((Long) key.id, 1, key.schema, requestFactory), WriteOperation.UPDATE); } @@ -268,7 +275,7 @@ public <V> void set(Property<V> property, EntityProxy record, V value) { checkArgumentsAndState(record, "set"); ProxyImpl recordImpl = (ProxyImpl) record; - EntityProxyId recordKey = new EntityProxyId(recordImpl); + EntityProxyId recordKey = recordImpl.getStableId(); ProxyJsoImpl rawMasterRecord = master.records.get(recordKey); WriteOperation priorOperation = operations.get(recordKey); @@ -387,6 +394,13 @@ return requestData.toString(); } + private EntityProxyIdImpl getPersistedProxyId(Long datastoreId, + ProxySchema<?> schema) { + return new EntityProxyIdImpl(datastoreId, schema, + RequestFactoryJsonImpl.NOT_FUTURE, + requestFactory.datastoreToFutureMap.get(datastoreId, schema)); + } + private Map<EntityProxyId, ProxyJsoImpl> getRecordsMap( WriteOperation writeOperation) { switch (writeOperation) { @@ -446,7 +460,7 @@ } private SyncResultImpl makeSyncResult(ProxyJsoImpl jso, - Map<String, String> violations, Long futureId) { + Map<String, String> violations, Object futureId) { return new SyncResultImpl(jso.getSchema().create(jso), violations, futureId); }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/EntityProxyId.java b/user/src/com/google/gwt/requestfactory/client/impl/EntityProxyId.java deleted file mode 100644 index b01f613..0000000 --- a/user/src/com/google/gwt/requestfactory/client/impl/EntityProxyId.java +++ /dev/null
@@ -1,86 +0,0 @@ -/* - * Copyright 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.google.gwt.requestfactory.client.impl; - -/** - * <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> - * The key used to store {@link com.google.gwt.requestfactory.shared.EntityProxy Proxy}. - */ -class EntityProxyId { - final ProxySchema<?> schema; - final Long id; - final boolean isFuture; - - EntityProxyId(ProxyImpl record) { - this(record.getId(), record.getSchema(), record.isFuture()); - } - - EntityProxyId(ProxyJsoImpl record, boolean isFuture) { - this(record.getId(), record.getSchema(), isFuture); - } - - protected EntityProxyId(Long id, ProxySchema<?> schema, boolean isFuture) { - assert id != null; - assert schema != null; - - this.id = id; - this.schema = schema; - this.isFuture = isFuture; - } - - @Override - public boolean equals(Object obj) { - if (this == obj) { - return true; - } - if (obj == null) { - return false; - } - if (getClass() != obj.getClass()) { - return false; - } - EntityProxyId other = (EntityProxyId) obj; - if (isFuture != other.isFuture) { - return false; - } - if (!id.equals(other.id)) { - return false; - } - if (!schema.equals(other.schema)) { - return false; - } - return true; - } - - @Override - public int hashCode() { - final int prime = 31; - int result = (isFuture ? 0 : 1); - result = prime * result + ((id == null) ? 0 : id.hashCode()); - result = prime * result + ((schema == null) ? 0 : schema.hashCode()); - return result; - } - - @Override - public String toString() { - return "[RecordKey schema: " + schema.getClass().getName() + " id: " + id - + " isFuture: " + (isFuture ? "true" : "false") + "]"; - } -}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/EntityProxyIdImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/EntityProxyIdImpl.java new file mode 100644 index 0000000..17d8b7f --- /dev/null +++ b/user/src/com/google/gwt/requestfactory/client/impl/EntityProxyIdImpl.java
@@ -0,0 +1,121 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.requestfactory.client.impl; + +import com.google.gwt.requestfactory.shared.EntityProxyId; + +/** + * <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> + * The key used to store + * {@link com.google.gwt.requestfactory.shared.EntityProxy Proxy}. This is + * stable across creates and updates on the client. + * <p> + * At all times, the id is not null. If the isFuture flag is true, the + * corresponding proxy has not been persisted and the id field is a futureId. If + * the isFuture flag is false, the corresponding proxy has been persisted and + * the id is the data-store id. The futureId is non-null if the entity was + * created on this client. + */ +final class EntityProxyIdImpl implements EntityProxyId { + static final String SEPARATOR = "---"; + + private static int hashCode(ProxySchema<?> proxySchema, boolean hasFutureId, Object finalId) { + final int prime = 31; + int result = hasFutureId ? 0 : 1; + result = prime * result + finalId.hashCode(); + result = prime * result + proxySchema.hashCode(); + return result; + } + + final ProxySchema<?> schema; + final Object id; + final Object futureId; + + final boolean isFuture; + + protected EntityProxyIdImpl(Object id, ProxySchema<?> schema, + boolean isFuture, Object futureId) { + assert id != null; + assert schema != null; + if (isFuture) { + assert futureId == null; + } + + this.id = id; + this.schema = schema; + this.isFuture = isFuture; + this.futureId = futureId; + } + + public String asString() { + if (isFuture) { + throw new IllegalStateException("Need to persist this proxy first"); + } + return id + SEPARATOR + schema.getToken(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + EntityProxyIdImpl other = (EntityProxyIdImpl) obj; + if (!schema.equals(other.schema)) { + return false; + } + if (isFuture == other.isFuture && id.equals(other.id)) { + return true; + } + // one of the isFuture is false. check its futureId + if (!isFuture && other.id.equals(futureId)) { + return true; + } + if (!other.isFuture && id.equals(other.futureId)) { + return true; + } + return false; + } + + /* + * This hashcode is complicated. + */ + @Override + public int hashCode() { + if (futureId == null && !isFuture) { + // does not have a futureId. + return hashCode(schema, false, id); + } + // has futureId + return hashCode(schema, true, isFuture ? id : futureId); + } + + @Override + public String toString() { + return "[RecordKey schema: " + schema.getClass().getName() + " id: " + id + + " isFuture: " + (isFuture ? "true" : "false") + + (futureId != null ? ("futureId : " + futureId) : "") + "]"; + } +}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/ProxyImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/ProxyImpl.java index 041b5e6..a6255ff 100644 --- a/user/src/com/google/gwt/requestfactory/client/impl/ProxyImpl.java +++ b/user/src/com/google/gwt/requestfactory/client/impl/ProxyImpl.java
@@ -15,6 +15,7 @@ */ package com.google.gwt.requestfactory.client.impl; +import com.google.gwt.requestfactory.shared.EntityProxyId; import com.google.gwt.requestfactory.shared.Property; import com.google.gwt.requestfactory.shared.PropertyReference; import com.google.gwt.requestfactory.shared.EntityProxy; @@ -34,7 +35,7 @@ */ public class ProxyImpl implements EntityProxy { - protected static String getUniqueId(Long id, boolean isFuture, + protected static String getWireFormatId(Long id, boolean isFuture, ProxySchema<?> schema) { return id + "-" + (isFuture ? "IS" : "NO") + "-" + schema.getToken(); } @@ -82,14 +83,25 @@ return jso.getSchema(); } - public String getUniqueId() { - return getUniqueId(jso.getId(), isFuture, jso.getSchema()); + public EntityProxyId getStableId() { + if (!isFuture) { + return new EntityProxyIdImpl( + getId(), + getSchema(), + false, + jso.getRequestFactory().datastoreToFutureMap.get(getId(), getSchema())); + } + return new EntityProxyIdImpl(getId(), getSchema(), isFuture, null); } public Integer getVersion() { return jso.getVersion(); } + public String getWireFormatId() { + return getWireFormatId(jso.getId(), isFuture, jso.getSchema()); + } + public boolean isChanged() { if (deltaValueStore == null) { return false; @@ -109,16 +121,11 @@ deltaValueStore.set(property, record, value); } - /* - * TODO: this method is public for the time being. Will become - * package-protected once {@link RecordImpl} moves to the same package as - * {@link AbstractRequest}. - */ - public void setDeltaValueStore(DeltaValueStoreJsonImpl deltaValueStore) { - this.deltaValueStore = deltaValueStore; - } - protected ValueStoreJsonImpl getValueStore() { return jso.getRequestFactory().getValueStore(); } + + void setDeltaValueStore(DeltaValueStoreJsonImpl deltaValueStore) { + this.deltaValueStore = deltaValueStore; + } }
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/ProxyJsoImpl.java b/user/src/com/google/gwt/requestfactory/client/impl/ProxyJsoImpl.java index b76410d..d69c1aa 100644 --- a/user/src/com/google/gwt/requestfactory/client/impl/ProxyJsoImpl.java +++ b/user/src/com/google/gwt/requestfactory/client/impl/ProxyJsoImpl.java
@@ -19,6 +19,7 @@ import com.google.gwt.core.client.JavaScriptObject; import com.google.gwt.core.client.JsArray; import com.google.gwt.requestfactory.shared.EntityProxy; +import com.google.gwt.requestfactory.shared.EntityProxyId; import com.google.gwt.requestfactory.shared.EnumProperty; import com.google.gwt.requestfactory.shared.Property; import com.google.gwt.requestfactory.shared.PropertyReference; @@ -178,7 +179,7 @@ assert schemaAndId.length == 2; ProxySchema<?> schema = getRequestFactory().getSchema(schemaAndId[0]); return (V) getRequestFactory().getValueStore().getRecordBySchemaAndId(schema, - Long.valueOf(schemaAndId[1])); + Long.valueOf(schemaAndId[1]), getRequestFactory()); } } @@ -201,6 +202,10 @@ public final native ProxySchema<?> getSchema() /*-{ return this['__key']; }-*/; + + public final EntityProxyId getStableId() { + throw new IllegalArgumentException("Can't call stableId on the jso"); + } public final Integer getVersion() { return this.get(version); @@ -291,7 +296,7 @@ } if (value instanceof ProxyImpl) { - setString(property.getName(), ((ProxyImpl) value).getUniqueId()); + setString(property.getName(), ((ProxyImpl) value).getWireFormatId()); return; }
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 c8eeb81..f8802d8 100644 --- a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java +++ b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryJsonImpl.java
@@ -24,13 +24,16 @@ import com.google.gwt.http.client.Response; import com.google.gwt.requestfactory.client.RequestFactoryLogHandler; import com.google.gwt.requestfactory.shared.EntityProxy; +import com.google.gwt.requestfactory.shared.EntityProxyId; import com.google.gwt.requestfactory.shared.RequestEvent; +import com.google.gwt.requestfactory.shared.RequestEvent.State; import com.google.gwt.requestfactory.shared.RequestFactory; import com.google.gwt.requestfactory.shared.RequestObject; import com.google.gwt.requestfactory.shared.WriteOperation; -import com.google.gwt.requestfactory.shared.RequestEvent.State; import com.google.gwt.user.client.Window.Location; +import java.util.HashMap; +import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; @@ -56,7 +59,39 @@ private static String SERVER_ERROR = "Server Error"; - private static final Integer INITIAL_VERSION = 1; + private final Integer initialVersion = 1; + + /* + * Keeping these maps forever is not a desirable solution because of the + * memory overhead but need these if want to provide stable {@EntityProxyId}. + * + * futureToDatastoreMap is currently not used, will be useful in find requests. + */ + final Map<Object, Object> futureToDatastoreMap = new HashMap<Object, Object>(); + final DataStoreToFutureMap datastoreToFutureMap = new DataStoreToFutureMap(); + + final class DataStoreToFutureMap { + + Map<ProxySchema<? extends ProxyImpl>, Map<Object, Object>> internalMap = new HashMap<ProxySchema<? extends ProxyImpl>, Map<Object, Object>>(); + + /* returns the previous futureId, if any*/ + Object put(Object datastoreId, ProxySchema<? extends ProxyImpl> schema, Object futureId) { + Map<Object, Object> perSchemaMap = internalMap.get(schema); + if (perSchemaMap == null) { + perSchemaMap = new HashMap<Object, Object>(); + internalMap.put(schema, perSchemaMap); + } + return perSchemaMap.put(datastoreId, futureId); + } + + Object get(Object datastoreId, ProxySchema<? extends ProxyImpl> schema) { + Map<Object, Object> perSchemaMap = internalMap.get(schema); + if (perSchemaMap == null) { + return null; + } + return perSchemaMap.get(datastoreId); + } + } private long currentFutureId = 0; @@ -144,7 +179,10 @@ } return schema.getProxyClass(); } - + + /** + * TODO(amitmanjhi): remove this method, use getProxyId instead. + */ protected EntityProxy getProxy(String token, ProxyToTypeMap recordToTypeMap) { String[] bits = token.split("-"); if (bits.length < 2 || bits.length > 3) { @@ -170,6 +208,31 @@ return schema.create(ProxyJsoImpl.create(id, -1, schema, this)); } + protected EntityProxyId getProxyId(String token, ProxyToTypeMap recordToTypeMap) { + String[] bits = token.split(EntityProxyIdImpl.SEPARATOR); + if (bits.length != 2) { + return null; + } + + ProxySchema<? extends EntityProxy> schema = recordToTypeMap.getType(bits[1]); + if (schema == null) { + return null; + } + + Long id = null; + try { + id = Long.valueOf(bits[0]); + } catch (NumberFormatException e) { + return null; + } + + Object futureId = datastoreToFutureMap.get(id, schema); + return new EntityProxyIdImpl(id, schema, false, futureId); + } + + /* + * can this method use the EntityProxyIdImpl.asString() method? + */ protected String getToken(EntityProxy record, ProxyToTypeMap recordToTypeMap) { Class<? extends EntityProxy> proxyClass = ((ProxyImpl) record).getSchema().getProxyClass(); String rtn = recordToTypeMap.getClassToken(proxyClass) + "-"; @@ -185,6 +248,9 @@ return valueStore; } + /* + * use ProxyId instead here. + */ void postChangeEvent(ProxyJsoImpl newJsoRecord, WriteOperation op) { /* * Ensure event receivers aren't accidentally using cached info by making an @@ -198,7 +264,7 @@ private <R extends ProxyImpl> R createFuture(ProxySchema<R> schema) { Long futureId = ++currentFutureId; - ProxyJsoImpl newRecord = ProxyJsoImpl.create(futureId, INITIAL_VERSION, + ProxyJsoImpl newRecord = ProxyJsoImpl.create(futureId, initialVersion, schema, this); return schema.create(newRecord, IS_FUTURE); }
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 7582a7e..96d2609 100644 --- a/user/src/com/google/gwt/requestfactory/client/impl/ValueStoreJsonImpl.java +++ b/user/src/com/google/gwt/requestfactory/client/impl/ValueStoreJsonImpl.java
@@ -29,26 +29,27 @@ * </span> * </p> */ -public class ValueStoreJsonImpl { +class ValueStoreJsonImpl { // package protected fields for use by DeltaValueStoreJsonImpl - final Map<EntityProxyId, ProxyJsoImpl> records = new HashMap<EntityProxyId, ProxyJsoImpl>(); + final Map<EntityProxyIdImpl, ProxyJsoImpl> records = new HashMap<EntityProxyIdImpl, ProxyJsoImpl>(); - public EntityProxy getRecordBySchemaAndId(ProxySchema<?> schema, - Long id) { + EntityProxy getRecordBySchemaAndId(ProxySchema<?> schema, Long id, + RequestFactoryJsonImpl requestFactory) { if (id == null) { return null; } // TODO: pass isFuture to this method from decoding ID string - EntityProxyId key = new EntityProxyId(id, schema, false); + EntityProxyIdImpl key = new EntityProxyIdImpl(id, schema, false, + requestFactory.datastoreToFutureMap.get(id, schema)); return schema.create(records.get(key)); } - public void setProxy(ProxyJsoImpl newRecord) { + void setProxy(ProxyJsoImpl newRecord) { setRecordInList(newRecord, 0, null); } - public void setRecords(JsArray<ProxyJsoImpl> newRecords) { + void setRecords(JsArray<ProxyJsoImpl> newRecords) { for (int i = 0, l = newRecords.length(); i < l; i++) { ProxyJsoImpl newRecord = newRecords.get(i); setRecordInList(newRecord, i, newRecords); @@ -57,8 +58,11 @@ private void setRecordInList(ProxyJsoImpl newJsoRecord, int i, JsArray<ProxyJsoImpl> array) { - EntityProxyId recordKey = new EntityProxyId(newJsoRecord, RequestFactoryJsonImpl.NOT_FUTURE); - + EntityProxyIdImpl recordKey = new EntityProxyIdImpl(newJsoRecord.getId(), + newJsoRecord.getSchema(), RequestFactoryJsonImpl.NOT_FUTURE, + newJsoRecord.getRequestFactory().datastoreToFutureMap.get( + newJsoRecord.getId(), newJsoRecord.getSchema())); + ProxyJsoImpl oldRecord = records.get(recordKey); if (oldRecord == null) { records.put(recordKey, newJsoRecord); @@ -73,7 +77,8 @@ array.set(i, newJsoRecord); } if (changed) { - newJsoRecord.getRequestFactory().postChangeEvent(newJsoRecord, WriteOperation.UPDATE); + newJsoRecord.getRequestFactory().postChangeEvent(newJsoRecord, + WriteOperation.UPDATE); } } }
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java index 2302bb8..4477645 100644 --- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java +++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
@@ -52,6 +52,7 @@ import com.google.gwt.requestfactory.server.ReflectionBasedOperationRegistry; import com.google.gwt.requestfactory.shared.EntityProxy; import com.google.gwt.requestfactory.shared.EntityProxyChangedEvent; +import com.google.gwt.requestfactory.shared.EntityProxyId; import com.google.gwt.requestfactory.shared.Property; import com.google.gwt.requestfactory.shared.PropertyReference; import com.google.gwt.requestfactory.shared.ProxyListRequest; @@ -361,6 +362,14 @@ sw.println("}"); sw.println(); + // write getProxyId(String) + sw.println("public " + EntityProxyId.class.getName() + " getProxyId(String token) {"); + sw.indent(); + sw.println("return getProxyId(token, new " + proxyToTypeMapName + "());"); + sw.outdent(); + sw.println("}"); + sw.println(); + // write getToken(Proxy) sw.println("public String getToken(EntityProxy proxy) {"); sw.indent(); @@ -493,7 +502,6 @@ sw.outdent(); sw.println("}"); - sw.outdent(); sw.println("}"); sw.println(); @@ -670,7 +678,7 @@ } if (classType != null && classType.isAssignableTo(typeOracle.findType(EntityProxy.class.getName()))) { - sb.append(").getUniqueId()"); + sb.append(").getWireFormatId()"); } } return "new Object[] {" + sb.toString() + "}";
diff --git a/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java b/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java index 131b411..d51ec44 100644 --- a/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java +++ b/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
@@ -56,6 +56,11 @@ <V> PropertyReference<V> getRef(Property<V> property); /** + * @return {@link EntityProxyId} that is stable across changes. + */ + EntityProxyId getStableId(); + + /** * @return the version of this Proxy. */ Integer getVersion();
diff --git a/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java b/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java new file mode 100644 index 0000000..77336a3 --- /dev/null +++ b/user/src/com/google/gwt/requestfactory/shared/EntityProxyId.java
@@ -0,0 +1,35 @@ +/* + * 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; + +/** + * A stable, opaque id of an {@link EntityProxy} that remains stable across + * updates, creates, deletes on the client. + * <p> + * In particular, an {@link EntityProxy} foo that is yet to be persisted and a + * copy of foo after being persisted have equal {@link EntityProxyId}. + */ +public interface EntityProxyId { + /** + * Returns a stable string that remains the same across updates and creates on + * the client. Note that calling this method on an instance that has not been + * persisted throws IllegalStateException. + * + * @return a stable string that remains the same across updates and creates on + * the client. + */ + String asString(); +}
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java b/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java index cf58c18..9d40015 100644 --- a/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java +++ b/user/src/com/google/gwt/requestfactory/shared/RequestFactory.java
@@ -52,10 +52,17 @@ /** * Return the appropriate proxy, which may have only its id attribute set. + * + * @deprecated, use the getProxyId method below. */ EntityProxy getProxy(String token); /** + * Return the appropriate {@link EntityProxyId}, a stable id for the Proxy. + */ + EntityProxyId getProxyId(String token); + + /** * Get a {@link com.google.gwt.user.client.History} compatible token that * represents the given class. It can be processed by * {@link #getClass(String)}
diff --git a/user/src/com/google/gwt/requestfactory/shared/SyncResult.java b/user/src/com/google/gwt/requestfactory/shared/SyncResult.java index 858d6a8..bf40616 100644 --- a/user/src/com/google/gwt/requestfactory/shared/SyncResult.java +++ b/user/src/com/google/gwt/requestfactory/shared/SyncResult.java
@@ -30,7 +30,7 @@ boolean hasViolations(); // TODO: futureId isn't working out so well, leaving soon - Long getFutureId(); + Object getFutureId(); EntityProxy getProxy();
diff --git a/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java b/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java index f2685e8..bd124d9 100644 --- a/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java +++ b/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
@@ -15,7 +15,7 @@ */ package com.google.gwt.requestfactory; -import com.google.gwt.requestfactory.client.impl.EntityProxyIdTest; +import com.google.gwt.requestfactory.client.impl.EntityProxyIdImplTest; import com.google.gwt.requestfactory.server.JsonRequestProcessorTest; import com.google.gwt.requestfactory.server.ReflectionBasedOperationRegistryTest; import com.google.gwt.requestfactory.server.RequestPropertyTest; @@ -30,7 +30,7 @@ public static Test suite() { TestSuite suite = new TestSuite( "requestfactory package tests that require the JRE"); - suite.addTestSuite(EntityProxyIdTest.class); + suite.addTestSuite(EntityProxyIdImplTest.class); suite.addTestSuite(JsonRequestProcessorTest.class); suite.addTestSuite(ReflectionBasedOperationRegistryTest.class); suite.addTestSuite(RequestPropertyTest.class);
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java index 2b6d77c..f9d4d72 100644 --- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java +++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
@@ -18,6 +18,7 @@ import com.google.gwt.core.client.GWT; import com.google.gwt.event.shared.HandlerManager; 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.Receiver; import com.google.gwt.requestfactory.shared.RequestObject; @@ -34,10 +35,108 @@ */ public class RequestFactoryTest extends GWTTestCase { + private SimpleRequestFactory req; + private HandlerManager eventBus; + + @Override + public void gwtSetUp() { + req = GWT.create(SimpleRequestFactory.class); + eventBus = new HandlerManager(null); + req.init(eventBus); + } + + @Override + public void gwtTearDown() { + req.simpleFooRequest().reset().fire(new Receiver<Void>() { + public void onSuccess(Void response, Set<SyncResult> syncResults) { + } + }); + req.simpleBarRequest().reset().fire(new Receiver<Void>() { + public void onSuccess(Void response, Set<SyncResult> syncResults) { + } + }); + } + + /* + * TODO(amitmanjhi) : new test that needs to be re-enabled. + */ + public void disabled_testDummyCreate() { + delayTestFinish(5000); + + final SimpleFooProxy foo = (SimpleFooProxy) req.create(SimpleFooProxy.class); + RequestObject<SimpleFooProxy> fooReq = req.simpleFooRequest().persistAndReturnSelf( + foo); + fooReq.fire(new Receiver<SimpleFooProxy>() { + + public void onSuccess(final SimpleFooProxy returned, + Set<SyncResult> syncResults) { + assertNotSame(foo.getStableId(), returned.getStableId()); + assertEquals(foo.getStableId(), returned.getStableId()); + finishTest(); + } + }); + } + + public void testStableId() { + delayTestFinish(5000); + + final SimpleFooProxy foo = (SimpleFooProxy) req.create(SimpleFooProxy.class); + final Object futureId = foo.getId(); + assertTrue(((ProxyImpl) foo).isFuture()); + RequestObject<SimpleFooProxy> fooReq = req.simpleFooRequest().persistAndReturnSelf( + foo); + + 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) { + assertNotSame(returnedAfterEdit, editableFoo); // Mutation above. + assertEquals(returnedAfterEdit.getStableId(), + returned.getStableId()); + assertEquals(returnedAfterEdit.getId(), returned.getId()); + finishTest(); + } + }); + } + }); + } + + private void checkStableIdEquals(SimpleFooProxy expected, SimpleFooProxy actual) { + assertNotSame(expected.getStableId(), actual.getStableId()); + assertEquals(expected.getStableId(), actual.getStableId()); + assertEquals(expected.getStableId().hashCode(), actual.getStableId().hashCode()); + + // No assumptions about the proxy objects (being proxies and all) + assertNotSame(expected, actual); + assertFalse(expected.equals(actual)); + } + public void testViolationPresent() { - final SimpleRequestFactory req = GWT.create(SimpleRequestFactory.class); - HandlerManager hm = new HandlerManager(null); - req.init(hm); delayTestFinish(5000); SimpleFooProxy newFoo = req.create(SimpleFooProxy.class); @@ -70,9 +169,6 @@ } public void testViolationAbsent() { - final SimpleRequestFactory req = GWT.create(SimpleRequestFactory.class); - HandlerManager hm = new HandlerManager(null); - req.init(hm); delayTestFinish(5000); SimpleFooProxy newFoo = req.create(SimpleFooProxy.class); @@ -96,9 +192,6 @@ * we have better persistence than the singleton pattern. */ public void testPersistExistingEntityExistingRelation() { - final SimpleRequestFactory req = GWT.create(SimpleRequestFactory.class); - HandlerManager hm = new HandlerManager(null); - req.init(hm); delayTestFinish(5000); req.simpleBarRequest().findSimpleBarById(999L).fire( @@ -129,9 +222,6 @@ * Find Entity Create Entity2 Relate Entity2 to Entity Persist Entity */ public void testPersistExistingEntityNewRelation() { - final SimpleRequestFactory req = GWT.create(SimpleRequestFactory.class); - HandlerManager hm = new HandlerManager(null); - req.init(hm); delayTestFinish(5000); SimpleBarProxy newBar = req.create(SimpleBarProxy.class); @@ -173,9 +263,6 @@ * Entity */ public void testPersistNewEntityExistingRelation() { - final SimpleRequestFactory req = GWT.create(SimpleRequestFactory.class); - HandlerManager hm = new HandlerManager(null); - req.init(hm); delayTestFinish(5000); SimpleFooProxy newFoo = req.create(SimpleFooProxy.class); @@ -214,9 +301,6 @@ * to Entity Persist */ public void testPersistNewEntityNewRelation() { - final SimpleRequestFactory req = GWT.create(SimpleRequestFactory.class); - HandlerManager hm = new HandlerManager(null); - req.init(hm); delayTestFinish(5000); SimpleFooProxy newFoo = req.create(SimpleFooProxy.class); SimpleBarProxy newBar = req.create(SimpleBarProxy.class); @@ -264,9 +348,6 @@ } public void testPersistRecursiveRelation() { - final SimpleRequestFactory req = GWT.create(SimpleRequestFactory.class); - HandlerManager hm = new HandlerManager(null); - req.init(hm); delayTestFinish(5000); SimpleFooProxy rayFoo = req.create(SimpleFooProxy.class); @@ -284,9 +365,6 @@ } public void testPersistRelation() { - final SimpleRequestFactory req = GWT.create(SimpleRequestFactory.class); - HandlerManager hm = new HandlerManager(null); - req.init(hm); delayTestFinish(5000); SimpleFooProxy rayFoo = req.create(SimpleFooProxy.class); @@ -333,9 +411,6 @@ } public void testFetchEntity() { - SimpleRequestFactory req = GWT.create(SimpleRequestFactory.class); - HandlerManager hm = new HandlerManager(null); - req.init(hm); delayTestFinish(5000); req.simpleFooRequest().findSimpleFooById(999L).fire( new Receiver<SimpleFooProxy>() { @@ -353,9 +428,6 @@ } public void testFetchEntityWithRelation() { - SimpleRequestFactory req = GWT.create(SimpleRequestFactory.class); - HandlerManager hm = new HandlerManager(null); - req.init(hm); delayTestFinish(5000); req.simpleFooRequest().findSimpleFooById(999L).with("barField").fire( new Receiver<SimpleFooProxy>() { @@ -373,10 +445,6 @@ } public void testProxysAsInstanceMethodParams() { - - final SimpleRequestFactory req = GWT.create(SimpleRequestFactory.class); - HandlerManager hm = new HandlerManager(null); - req.init(hm); delayTestFinish(5000); req.simpleFooRequest().findSimpleFooById(999L).fire( new Receiver<SimpleFooProxy>() { @@ -402,9 +470,6 @@ * (b) instance methods are handled correctly. */ public void testMethodWithSideEffects() { - final SimpleRequestFactory req = GWT.create(SimpleRequestFactory.class); - HandlerManager hm = new HandlerManager(null); - req.init(hm); delayTestFinish(5000); req.simpleFooRequest().findSimpleFooById(999L).fire(
diff --git a/user/test/com/google/gwt/requestfactory/client/impl/EntityProxyIdImplTest.java b/user/test/com/google/gwt/requestfactory/client/impl/EntityProxyIdImplTest.java new file mode 100644 index 0000000..246a782 --- /dev/null +++ b/user/test/com/google/gwt/requestfactory/client/impl/EntityProxyIdImplTest.java
@@ -0,0 +1,98 @@ +/* + * Copyright 2010 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +package com.google.gwt.requestfactory.client.impl; + +import com.google.gwt.requestfactory.shared.EntityProxy; +import com.google.gwt.requestfactory.shared.EntityProxyChangedEvent; +import com.google.gwt.requestfactory.shared.WriteOperation; + +import junit.framework.TestCase; + +/** + * Eponymous unit test. + */ +public class EntityProxyIdImplTest extends TestCase { + + static class Schema1 extends ProxySchema<ProxyImpl> { + public Schema1() { + super("schemey"); + } + + @Override + public ProxyImpl create(ProxyJsoImpl jso, boolean isFuture) { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + @Override + public EntityProxyChangedEvent<?, ?> createChangeEvent(EntityProxy proxy, + WriteOperation writeOperation) { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + + @Override + public Class<? extends EntityProxy> getProxyClass() { + throw new UnsupportedOperationException("Auto-generated method stub"); + } + } + + static class Schema2 extends Schema1 { + } + + public void testEquals() { + EntityProxyIdImpl newKey1 = new EntityProxyIdImpl(1L, new Schema1(), + RequestFactoryJsonImpl.IS_FUTURE, null); + + EntityProxyIdImpl anotherNewKey1 = new EntityProxyIdImpl(newKey1.id, newKey1.schema, + newKey1.isFuture, null); + assertTrue(newKey1.equals(anotherNewKey1)); + assertTrue(newKey1.hashCode() == anotherNewKey1.hashCode()); + + EntityProxyIdImpl newKey2 = new EntityProxyIdImpl((Long) newKey1.id + 1, newKey1.schema, + newKey1.isFuture, null); + assertFalse(newKey1.equals(newKey2)); + assertFalse(newKey1.hashCode() == newKey2.hashCode()); + + EntityProxyIdImpl newKey1NoSchema = new EntityProxyIdImpl(newKey1.id, + new Schema2(), newKey1.isFuture, null); + assertFalse(newKey1.equals(newKey1NoSchema)); + assertFalse(newKey1.hashCode() == newKey1NoSchema.hashCode()); + + EntityProxyIdImpl oldKey1 = new EntityProxyIdImpl(newKey1.id, newKey1.schema, + !newKey1.isFuture, null); + assertFalse(newKey1.equals(oldKey1)); + assertFalse(newKey1.hashCode() == oldKey1.hashCode()); + } + + public void testEqualsWithFuture() { + EntityProxyIdImpl newKey1 = new EntityProxyIdImpl(1L, new Schema1(), + RequestFactoryJsonImpl.IS_FUTURE, null); + + EntityProxyIdImpl persistedNewKey1 = new EntityProxyIdImpl(1000L, + newKey1.schema, RequestFactoryJsonImpl.NOT_FUTURE, newKey1.id); + assertTrue(persistedNewKey1.equals(persistedNewKey1)); + assertTrue(newKey1.equals(persistedNewKey1)); + assertTrue(persistedNewKey1.equals(newKey1)); + assertTrue(newKey1.hashCode() == persistedNewKey1.hashCode()); + + EntityProxyIdImpl anotherPersistedNewKey1 = new EntityProxyIdImpl(1024L, + newKey1.schema, RequestFactoryJsonImpl.NOT_FUTURE, + (Long) newKey1.id + 1); + assertTrue(anotherPersistedNewKey1.equals(anotherPersistedNewKey1)); + assertFalse(persistedNewKey1.equals(anotherPersistedNewKey1)); + assertFalse(anotherPersistedNewKey1.equals(persistedNewKey1)); + assertFalse(newKey1.hashCode() == anotherPersistedNewKey1.hashCode()); + } +}
diff --git a/user/test/com/google/gwt/requestfactory/client/impl/EntityProxyIdTest.java b/user/test/com/google/gwt/requestfactory/client/impl/EntityProxyIdTest.java deleted file mode 100644 index 47c97a2..0000000 --- a/user/test/com/google/gwt/requestfactory/client/impl/EntityProxyIdTest.java +++ /dev/null
@@ -1,78 +0,0 @@ -/* - * Copyright 2010 Google Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not - * use this file except in compliance with the License. You may obtain a copy of - * the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations under - * the License. - */ -package com.google.gwt.requestfactory.client.impl; - -import com.google.gwt.requestfactory.shared.EntityProxy; -import com.google.gwt.requestfactory.shared.EntityProxyChangedEvent; -import com.google.gwt.requestfactory.shared.WriteOperation; - -import junit.framework.TestCase; - -/** - * Eponymous unit test. - */ -public class EntityProxyIdTest extends TestCase { - - static class Schema1 extends ProxySchema<ProxyImpl> { - public Schema1() { - super("schemey"); - } - - @Override - public ProxyImpl create(ProxyJsoImpl jso, boolean isFuture) { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - @Override - public EntityProxyChangedEvent<?, ?> createChangeEvent(EntityProxy proxy, - WriteOperation writeOperation) { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - - @Override - public Class<? extends EntityProxy> getProxyClass() { - throw new UnsupportedOperationException("Auto-generated method stub"); - } - } - - static class Schema2 extends Schema1 { - } - - public void testEquals() { - EntityProxyId newKey1 = new EntityProxyId(1L, new Schema1(), - RequestFactoryJsonImpl.IS_FUTURE); - - EntityProxyId anotherNewKey1 = new EntityProxyId(newKey1.id, newKey1.schema, - newKey1.isFuture); - assertTrue(newKey1.equals(anotherNewKey1)); - assertTrue(newKey1.hashCode() == anotherNewKey1.hashCode()); - - EntityProxyId newKey2 = new EntityProxyId(newKey1.id + 1, newKey1.schema, - newKey1.isFuture); - assertFalse(newKey1.equals(newKey2)); - assertFalse(newKey1.hashCode() == newKey2.hashCode()); - - EntityProxyId newKey1NoSchema = new EntityProxyId(newKey1.id, - new Schema2(), newKey1.isFuture); - assertFalse(newKey1.equals(newKey1NoSchema)); - assertFalse(newKey1.hashCode() == newKey1NoSchema.hashCode()); - - EntityProxyId oldKey1 = new EntityProxyId(newKey1.id, newKey1.schema, - !newKey1.isFuture); - assertFalse(newKey1.equals(oldKey1)); - assertFalse(newKey1.hashCode() == oldKey1.hashCode()); - } -}