First pass at implementing ValueProxy support.
Resolve issue 5522, issue 5373, issue 5559.
http://code.google.com/p/google-web-toolkit/wiki/RequestFactory_2_1_1
Patch by: bobv
Review by: rchandia,rjrjr
Review at http://gwt-code-reviews.appspot.com/1108802
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9252 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/console/HttpClientTransport.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/console/HttpClientTransport.java
index 4325979..8b5b0c5 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/console/HttpClientTransport.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/console/HttpClientTransport.java
@@ -41,7 +41,6 @@
this.uri = uri;
}
- @Override
public void send(String payload, TransportReceiver receiver) {
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost();
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java
index c7059bd..815f349 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java
@@ -15,8 +15,6 @@
*/
package com.google.gwt.sample.dynatablerf.domain;
-import com.google.gwt.sample.dynatablerf.server.SchoolCalendarService;
-
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
@@ -26,14 +24,6 @@
* Represents an address.
*/
public class Address {
- /**
- * The RequestFactory requires a static finder method for each proxied type.
- * Soon it should allow you to customize how instances are found.
- */
- public static Address findAddress(String id) {
- return SchoolCalendarService.findPerson(id).getAddress();
- }
-
@NotNull
@Size(min = 1)
private String city;
@@ -77,10 +67,6 @@
return city;
}
- public String getId() {
- return id;
- }
-
public String getState() {
return state;
}
@@ -89,10 +75,6 @@
return street;
}
- public Integer getVersion() {
- return version;
- }
-
public String getZip() {
return zip;
}
@@ -101,23 +83,10 @@
return new Address(this);
}
- /**
- * When this was written the RequestFactory required a persist method per
- * type. That requirement should be relaxed very soon (and may well have been
- * already if we forget to update this comment).
- */
- public void persist() {
- SchoolCalendarService.persist(this);
- }
-
public void setCity(String city) {
this.city = city;
}
- public void setId(String id) {
- this.id = id;
- }
-
public void setState(String state) {
this.state = state;
}
@@ -126,10 +95,6 @@
this.street = street;
}
- public void setVersion(Integer version) {
- this.version = version;
- }
-
public void setZip(String zip) {
this.zip = zip;
}
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
index 3c10fd8..6f65c67 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
@@ -50,7 +50,7 @@
}
@NotNull
- private final Address address = new Address();
+ private Address address = new Address();
@NotNull
private Schedule classSchedule = new Schedule();
@@ -158,7 +158,7 @@
}
public void setAddress(Address address) {
- this.address.copyFrom(address);
+ this.address = address;
}
public void setDaysFilter(List<Boolean> daysFilter) {
@@ -172,7 +172,6 @@
public void setId(String id) {
this.id = id;
- address.setId(id);
}
public void setMentor(Person mentor) {
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java
index 19b06e5..1279342 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java
@@ -18,7 +18,6 @@
import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.ALL_DAYS;
import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.NO_DAYS;
-import com.google.gwt.sample.dynatablerf.domain.Address;
import com.google.gwt.sample.dynatablerf.domain.Person;
import java.util.ArrayList;
@@ -90,12 +89,6 @@
}
@Override
- public void persist(Address address) {
- address.setVersion(address.getVersion() + 1);
- findPerson(address.getId()).getAddress().copyFrom(address);
- }
-
- @Override
public void persist(Person person) {
if (person.getId() == null) {
person.setId(Long.toString(++serial));
@@ -149,11 +142,6 @@
}
@Override
- public void persist(Address address) {
- backingStore.persist(address);
- }
-
- @Override
public void persist(Person person) {
backingStore.persist(person);
}
@@ -187,7 +175,5 @@
public abstract List<Person> getPeople(int startIndex, int maxCount,
List<Boolean> daysFilter);
- public abstract void persist(Address address);
-
public abstract void persist(Person person);
}
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
index 5493cd7..b4097cf 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
@@ -17,7 +17,6 @@
import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.ALL_DAYS;
-import com.google.gwt.sample.dynatablerf.domain.Address;
import com.google.gwt.sample.dynatablerf.domain.Person;
import java.io.IOException;
@@ -62,11 +61,6 @@
ALL_DAYS).get(0);
}
- public static void persist(Address address) {
- checkPersonSource();
- PERSON_SOURCE.get().persist(address);
- }
-
public static void persist(Person person) {
checkPersonSource();
PERSON_SOURCE.get().persist(person);
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java
index f96e5c8..8e87ef6 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java
@@ -15,16 +15,15 @@
*/
package com.google.gwt.sample.dynatablerf.shared;
-import com.google.gwt.requestfactory.shared.EntityProxy;
-import com.google.gwt.requestfactory.shared.EntityProxyId;
import com.google.gwt.requestfactory.shared.ProxyFor;
+import com.google.gwt.requestfactory.shared.ValueProxy;
import com.google.gwt.sample.dynatablerf.domain.Address;
/**
* Represents an Address in the client code.
*/
-@ProxyFor(Address.class)
-public interface AddressProxy extends EntityProxy {
+@ProxyFor(value = Address.class)
+public interface AddressProxy extends ValueProxy {
String getCity();
String getState();
@@ -40,6 +39,4 @@
void setStreet(String street);
void setZip(String zip);
-
- EntityProxyId<AddressProxy> stableId();
}
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
index 0a0e38a..ce18e88 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
@@ -21,7 +21,6 @@
import com.google.gwt.requestfactory.shared.RequestContext;
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.Service;
-import com.google.gwt.sample.dynatablerf.domain.Address;
import com.google.gwt.sample.dynatablerf.domain.Person;
import com.google.gwt.sample.dynatablerf.server.SchoolCalendarService;
@@ -34,15 +33,6 @@
* {@link com.google.gwt.core.client.GWT#create}.
*/
public interface DynaTableRequestFactory extends RequestFactory {
-
- /**
- * Source of request objects for the Address class.
- */
- @Service(Address.class)
- interface AddressRequest extends RequestContext {
- InstanceRequest<AddressProxy, Void> persist();
- }
-
/**
* Source of request objects for the Person class.
*/
@@ -67,8 +57,6 @@
Request<PersonProxy> getRandomPerson();
}
- AddressRequest addressRequest();
-
LoggingRequest loggingRequest();
PersonRequest personRequest();
diff --git a/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java b/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
index f3a0bb2..e8aa4bb 100644
--- a/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
+++ b/user/src/com/google/gwt/autobean/client/impl/JsoSplittable.java
@@ -24,11 +24,11 @@
import java.util.List;
/**
- * Implements the EntityCodex.Splittable interface
+ * Implements the EntityCodex.Splittable interface using a raw JavaScriptObject.
*/
public final class JsoSplittable extends JavaScriptObject implements Splittable {
/**
- * This type is only used in DevMode because we can't treat Strings as JSOs.
+ * This type is used because we can't treat Strings as JSOs.
*/
public static class StringSplittable implements Splittable {
private final String value;
diff --git a/user/src/com/google/gwt/autobean/server/BeanMethod.java b/user/src/com/google/gwt/autobean/server/BeanMethod.java
index 117bf54..24c2009 100644
--- a/user/src/com/google/gwt/autobean/server/BeanMethod.java
+++ b/user/src/com/google/gwt/autobean/server/BeanMethod.java
@@ -138,13 +138,21 @@
Class<?>[] searchParams = new Class<?>[declaredParams.length + 1];
searchParams[0] = AutoBean.class;
System.arraycopy(declaredParams, 0, searchParams, 1, declaredParams.length);
+ Class<?> autoBeanType = handler.getBean().getType();
for (Class<?> clazz : handler.getBean().getConfiguration().getCategories()) {
try {
Method found = clazz.getMethod(method.getName(), searchParams);
- if (Modifier.isStatic(found.getModifiers())) {
- return found;
+ if (!Modifier.isStatic(found.getModifiers())) {
+ continue;
}
+ // Check the AutoBean parameterization of the 0th argument
+ Class<?> foundAutoBean = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
+ AutoBean.class, found.getGenericParameterTypes()[0]));
+ if (!foundAutoBean.isAssignableFrom(autoBeanType)) {
+ continue;
+ }
+ return found;
} catch (NoSuchMethodException expected) {
} catch (IllegalArgumentException e) {
throw new RuntimeException(e);
diff --git a/user/src/com/google/gwt/autobean/server/SimpleBeanHandler.java b/user/src/com/google/gwt/autobean/server/SimpleBeanHandler.java
index a15d27b..b331ad5 100644
--- a/user/src/com/google/gwt/autobean/server/SimpleBeanHandler.java
+++ b/user/src/com/google/gwt/autobean/server/SimpleBeanHandler.java
@@ -15,6 +15,8 @@
*/
package com.google.gwt.autobean.server;
+import com.google.gwt.autobean.shared.AutoBeanCodex;
+
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
@@ -47,4 +49,12 @@
}
throw new RuntimeException("Unhandled invocation " + method.getName());
}
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ return AutoBeanCodex.encode(bean).getPayload();
+ }
}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java b/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
index 6b9f22e..6550f82 100644
--- a/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
+++ b/user/src/com/google/gwt/autobean/server/impl/JsonSplittable.java
@@ -138,6 +138,21 @@
return array.length();
}
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ if (obj != null) {
+ return obj.toString();
+ } else if (array != null) {
+ return array.toString();
+ } else if (string != null) {
+ return string;
+ }
+ return "<Uninitialized>";
+ }
+
private JsonSplittable makeSplittable(Object object) {
if (object instanceof JSONObject) {
return new JsonSplittable((JSONObject) object);
diff --git a/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java b/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
index d4d954b..c7c64ab 100644
--- a/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
+++ b/user/src/com/google/gwt/autobean/server/impl/TypeUtils.java
@@ -60,6 +60,7 @@
static {
Map<Class<?>, Class<?>> autoBoxMap = new HashMap<Class<?>, Class<?>>();
+ autoBoxMap.put(boolean.class, Boolean.class);
autoBoxMap.put(byte.class, Byte.class);
autoBoxMap.put(char.class, Character.class);
autoBoxMap.put(double.class, Double.class);
diff --git a/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java b/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
index b82af0e..d55d45f 100644
--- a/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
+++ b/user/src/com/google/gwt/autobean/shared/impl/EnumMap.java
@@ -20,7 +20,7 @@
* convert enum values to strings.
*/
public interface EnumMap {
- public <E extends Enum<E>> E getEnum(Class<E> clazz, String token);
+ <E extends Enum<E>> E getEnum(Class<E> clazz, String token);
- public String getToken(Enum<?> e);
+ String getToken(Enum<?> e);
}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
index 50fd171..2e22c51 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
@@ -24,6 +24,7 @@
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.RequestContext;
import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.ValueProxy;
import com.google.gwt.requestfactory.shared.Violation;
import java.util.ArrayList;
@@ -38,10 +39,34 @@
public abstract class AbstractRequestFactoryEditorDriver<R, E extends Editor<R>>
implements RequestFactoryEditorDriver<R, E> {
+ /**
+ * Since the ValueProxy is being mutated in-place, we need a way to stabilize
+ * its hashcode for future equality checks.
+ */
+ private static class ValueProxyHolder {
+ private final ValueProxy proxy;
+
+ public ValueProxyHolder(ValueProxy proxy) {
+ this.proxy = proxy;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ return proxy.equals(((ValueProxyHolder) o).proxy);
+ }
+
+ @Override
+ public int hashCode() {
+ return proxy.getClass().hashCode();
+ }
+ }
+
private static final DelegateMap.KeyMethod PROXY_ID_KEY = new DelegateMap.KeyMethod() {
- public Object key(Object object) {
+ public Object key(final Object object) {
if (object instanceof EntityProxy) {
return ((EntityProxy) object).stableId();
+ } else if (object instanceof ValueProxy) {
+ return new ValueProxyHolder((ValueProxy) object);
}
return null;
}
@@ -115,7 +140,11 @@
* Find the delegates that are attached to the object. Use getRaw() here
* since the violation doesn't include an EntityProxy reference
*/
- List<AbstractEditorDelegate<?, ?>> delegateList = delegateMap.getRaw(error.getProxyId());
+ Object key = error.getInvalidProxy();
+ // Object key = error.getOriginalProxy();
+ // if (key == null) {
+ // }
+ List<AbstractEditorDelegate<?, ?>> delegateList = delegateMap.get(key);
if (delegateList != null) {
// For each delegate editing some record...
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
index b7eb331..916eee7 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
@@ -33,19 +33,23 @@
import com.google.gwt.requestfactory.client.impl.AbstractClientRequestFactory;
import com.google.gwt.requestfactory.rebind.model.ContextMethod;
import com.google.gwt.requestfactory.rebind.model.EntityProxyModel;
+import com.google.gwt.requestfactory.rebind.model.EntityProxyModel.Type;
import com.google.gwt.requestfactory.rebind.model.RequestFactoryModel;
import com.google.gwt.requestfactory.rebind.model.RequestMethod;
import com.google.gwt.requestfactory.shared.EntityProxyId;
import com.google.gwt.requestfactory.shared.impl.AbstractRequest;
import com.google.gwt.requestfactory.shared.impl.AbstractRequestContext;
import com.google.gwt.requestfactory.shared.impl.AbstractRequestFactory;
+import com.google.gwt.requestfactory.shared.impl.BaseProxyCategory;
import com.google.gwt.requestfactory.shared.impl.EntityProxyCategory;
import com.google.gwt.requestfactory.shared.impl.RequestData;
+import com.google.gwt.requestfactory.shared.impl.ValueProxyCategory;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
import com.google.gwt.user.rebind.SourceWriter;
import java.io.PrintWriter;
import java.util.HashMap;
+import java.util.HashSet;
/**
* Generates implementations of
@@ -96,8 +100,11 @@
private void writeAutoBeanFactory(SourceWriter sw) {
// Map in static implementations of EntityProxy methods
- sw.println("@%s(%s.class)", Category.class.getCanonicalName(),
- EntityProxyCategory.class.getCanonicalName());
+ sw.println("@%s({%s.class, %s.class, %s.class})",
+ Category.class.getCanonicalName(),
+ EntityProxyCategory.class.getCanonicalName(),
+ ValueProxyCategory.class.getCanonicalName(),
+ BaseProxyCategory.class.getCanonicalName());
// Don't wrap our id type, because it makes code grungy
sw.println("@%s(%s.class)", NoWrap.class.getCanonicalName(),
EntityProxyId.class.getCanonicalName());
@@ -251,6 +258,12 @@
+ " = new %1$s<String, Class<?>>();", HashMap.class.getCanonicalName());
sw.println("private static final %1$s<Class<?>, String> typesToTokens"
+ " = new %1$s<Class<?>, String>();", HashMap.class.getCanonicalName());
+ sw.println(
+ "private static final %1$s<Class<?>> entityProxyTypes = new %1$s<Class<?>>();",
+ HashSet.class.getCanonicalName());
+ sw.println(
+ "private static final %1$s<Class<?>> valueProxyTypes = new %1$s<Class<?>>();",
+ HashSet.class.getCanonicalName());
sw.println("static {");
sw.indent();
for (EntityProxyModel type : model.getAllProxyModels()) {
@@ -260,6 +273,10 @@
// typesToTokens.put(Foo.class, Foo);
sw.println("typesToTokens.put(%1$s.class, \"%1$s\");",
type.getQualifiedSourceName());
+ // fooProxyTypes.add(MyFooProxy.class);
+ sw.println("%s.add(%s.class);", type.getType().equals(Type.ENTITY)
+ ? "entityProxyTypes" : "valueProxyTypes",
+ type.getQualifiedSourceName());
}
sw.outdent();
sw.println("}");
@@ -271,5 +288,11 @@
sw.println("@Override protected String getTypeToken(Class type) {");
sw.indentln("return typesToTokens.get(type);");
sw.println("}");
+ sw.println("@Override public boolean isEntityType(Class<?> type) {");
+ sw.indentln("return entityProxyTypes.contains(type);");
+ sw.println("}");
+ sw.println("@Override public boolean isValueType(Class<?> type) {");
+ sw.indentln("return valueProxyTypes.contains(type);");
+ sw.println("}");
}
}
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java b/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
index 52989d1..7676035 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
@@ -23,6 +23,14 @@
*/
public class EntityProxyModel {
/**
+ * The kind of proxy. This is an enum in case more proxy types are defined in
+ * the future.
+ */
+ public enum Type {
+ ENTITY, VALUE
+ }
+
+ /**
* Builds {@link EntityProxyModel}.
*/
public static class Builder {
@@ -56,11 +64,16 @@
public void setRequestMethods(List<RequestMethod> requestMethods) {
toReturn.requestMethods = requestMethods;
}
+
+ public void setType(Type type) {
+ toReturn.type = type;
+ }
}
private Class<?> proxyFor;
private String qualifiedSourceName;
private List<RequestMethod> requestMethods;
+ private Type type;
private EntityProxyModel() {
}
@@ -77,6 +90,10 @@
return Collections.unmodifiableList(requestMethods);
}
+ public Type getType() {
+ return type;
+ }
+
/**
* For debugging use only.
*/
diff --git a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
index be2eb96..56d859a 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
@@ -24,6 +24,7 @@
import com.google.gwt.core.ext.typeinfo.JType;
import com.google.gwt.core.ext.typeinfo.TypeOracle;
import com.google.gwt.editor.rebind.model.ModelUtils;
+import com.google.gwt.requestfactory.rebind.model.EntityProxyModel.Type;
import com.google.gwt.requestfactory.rebind.model.RequestMethod.CollectionType;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.InstanceRequest;
@@ -34,6 +35,7 @@
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.Service;
import com.google.gwt.requestfactory.shared.ServiceName;
+import com.google.gwt.requestfactory.shared.ValueProxy;
import java.util.ArrayList;
import java.util.Collection;
@@ -61,13 +63,13 @@
return "Unable to create RequestFactoryModel model due to previous errors";
}
- private final TreeLogger logger;
private final JClassType collectionInterface;
private final List<ContextMethod> contextMethods = new ArrayList<ContextMethod>();
private final JClassType entityProxyInterface;
private final JClassType factoryType;
private final JClassType instanceRequestInterface;
private final JClassType listInterface;
+ private final TreeLogger logger;
private final TypeOracle oracle;
/**
* This map prevents cyclic type dependencies from overflowing the stack.
@@ -82,6 +84,7 @@
private final JClassType requestContextInterface;
private final JClassType requestFactoryInterface;
private final JClassType requestInterface;
+ private final JClassType valueProxyInterface;
public RequestFactoryModel(TreeLogger logger, JClassType factoryType)
throws UnableToCompleteException {
@@ -96,6 +99,7 @@
requestContextInterface = oracle.findType(RequestContext.class.getCanonicalName());
requestFactoryInterface = oracle.findType(RequestFactory.class.getCanonicalName());
requestInterface = oracle.findType(Request.class.getCanonicalName());
+ valueProxyInterface = oracle.findType(ValueProxy.class.getCanonicalName());
for (JMethod method : factoryType.getOverridableMethods()) {
if (method.getEnclosingType().equals(requestFactoryInterface)) {
@@ -206,6 +210,17 @@
peerBuilders.put(entityProxyType, builder);
builder.setQualifiedSourceName(ModelUtils.getQualifiedBaseName(entityProxyType));
+ if (entityProxyInterface.isAssignableFrom(entityProxyType)) {
+ builder.setType(Type.ENTITY);
+ } else if (valueProxyInterface.isAssignableFrom(entityProxyType)) {
+ builder.setType(Type.VALUE);
+ } else {
+ poison("The type %s is not assignable to either %s or %s",
+ entityProxyInterface.getQualifiedSourceName(),
+ valueProxyInterface.getQualifiedSourceName());
+ // Cannot continue, since knowing the behavior is crucial
+ die(poisonedMessage());
+ }
// Get the server domain object type
ProxyFor proxyFor = entityProxyType.getAnnotation(ProxyFor.class);
@@ -332,8 +347,9 @@
if (ModelUtils.isValueType(oracle, transportedClass)) {
// Simple values, like Integer and String
methodBuilder.setValueType(true);
- } else if (entityProxyInterface.isAssignableFrom(transportedClass)) {
- // EntityProxy return types
+ } else if (entityProxyInterface.isAssignableFrom(transportedClass)
+ || valueProxyInterface.isAssignableFrom(transportedClass)) {
+ // EntityProxy and ValueProxy return types
methodBuilder.setEntityType(getEntityProxyType(transportedClass));
} else if (collectionInterface.isAssignableFrom(transportedClass)) {
// Only allow certain collections for now
diff --git a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
index c2bfc34..6ffb4c2 100644
--- a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
@@ -18,19 +18,24 @@
import com.google.gwt.autobean.server.impl.TypeUtils;
import com.google.gwt.autobean.shared.ValueCodex;
import com.google.gwt.requestfactory.server.SimpleRequestProcessor.ServiceLayer;
+import com.google.gwt.requestfactory.shared.BaseProxy;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.InstanceRequest;
import com.google.gwt.requestfactory.shared.ProxyFor;
import com.google.gwt.requestfactory.shared.ProxyForName;
+import com.google.gwt.requestfactory.shared.Request;
import com.google.gwt.requestfactory.shared.RequestContext;
import com.google.gwt.requestfactory.shared.Service;
import com.google.gwt.requestfactory.shared.ServiceName;
+import com.google.gwt.requestfactory.shared.ValueProxy;
import com.google.gwt.requestfactory.shared.messages.EntityCodex;
import com.google.gwt.requestfactory.shared.messages.EntityCodex.EntitySource;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
+import java.lang.reflect.Type;
import java.util.Collections;
import java.util.List;
import java.util.Set;
@@ -91,13 +96,14 @@
clazz.getCanonicalName());
}
- public Class<?> getClientType(Class<?> domainClass) {
+ public Class<?> getClientType(Class<?> domainClass, Class<?> clientClass) {
String name;
synchronized (validator) {
- name = validator.getEntityProxyTypeName(domainClass.getName());
+ name = validator.getEntityProxyTypeName(domainClass.getName(),
+ clientClass.getName());
}
if (name != null) {
- return forName(name).asSubclass(EntityProxy.class);
+ return forName(name).asSubclass(BaseProxy.class);
}
if (List.class.isAssignableFrom(domainClass)) {
return List.class;
@@ -117,7 +123,7 @@
return List.class;
} else if (Set.class.equals(clazz)) {
return Set.class;
- } else if (EntityProxy.class.isAssignableFrom(clazz)) {
+ } else if (BaseProxy.class.isAssignableFrom(clazz)) {
ProxyFor pf = clazz.getAnnotation(ProxyFor.class);
if (pf != null) {
return pf.value();
@@ -135,7 +141,7 @@
public String getFlatId(EntitySource source, Object domainObject) {
Object id = getProperty(domainObject, "id");
if (id == null) {
- report("Could not retrieve id property for domain object");
+ return null;
}
if (!isKeyType(id.getClass())) {
die(null, "The type %s is not a valid key type",
@@ -165,6 +171,23 @@
return die(toReport, "Could not retrieve property %s", property);
}
+ public Type getRequestReturnType(Method contextMethod) {
+ Class<?> returnClass = contextMethod.getReturnType();
+ if (InstanceRequest.class.isAssignableFrom(returnClass)) {
+ Type[] params = TypeUtils.getParameterization(InstanceRequest.class,
+ contextMethod.getGenericReturnType());
+ assert params.length == 2;
+ return params[1];
+ } else if (Request.class.isAssignableFrom(returnClass)) {
+ Type param = TypeUtils.getSingleParameterization(Request.class,
+ contextMethod.getGenericReturnType());
+ return param;
+ } else {
+ throw new IllegalArgumentException("Unknown RequestContext return type "
+ + returnClass.getCanonicalName());
+ }
+ }
+
public String getTypeToken(Class<?> clazz) {
return clazz.getName();
}
@@ -173,7 +196,7 @@
// TODO: Make version any value type
Object version = getProperty(domainObject, "version");
if (version == null) {
- report("Could not retrieve version property");
+ return 0;
}
if (!(version instanceof Integer)) {
die(null, "The getVersion() method on type %s did not return"
@@ -244,21 +267,23 @@
return die(ex, "Cauld not load domain object using id", id.toString());
}
- public Class<? extends EntityProxy> resolveClass(String typeToken) {
+ public Class<? extends BaseProxy> resolveClass(String typeToken) {
Class<?> found = forName(typeToken);
- if (!EntityProxy.class.isAssignableFrom(found)) {
- die(null, "The requested type %s is not assignable to %s", typeToken,
- EntityProxy.class.getName());
+ if (!EntityProxy.class.isAssignableFrom(found)
+ && !ValueProxy.class.isAssignableFrom(found)) {
+ die(null, "The requested type %s is not assignable to %s or %s",
+ typeToken, EntityProxy.class.getCanonicalName(),
+ ValueProxy.class.getCanonicalName());
}
synchronized (validator) {
validator.antidote();
- validator.validateEntityProxy(found.getName());
+ validator.validateProxy(found.getName());
if (validator.isPoisoned()) {
die(null, "The type %s did not pass RequestFactory validation",
found.getCanonicalName());
}
}
- return found.asSubclass(EntityProxy.class);
+ return found.asSubclass(BaseProxy.class);
}
public Method resolveDomainMethod(Method requestContextMethod) {
@@ -289,8 +314,8 @@
Class<?>[] parameterTypes = requestContextMethod.getParameterTypes();
Class<?>[] domainArgs = new Class<?>[parameterTypes.length];
for (int i = 0, j = domainArgs.length; i < j; i++) {
- if (EntityProxy.class.isAssignableFrom(parameterTypes[i])) {
- domainArgs[i] = getDomainClass(parameterTypes[i].asSubclass(EntityProxy.class));
+ if (BaseProxy.class.isAssignableFrom(parameterTypes[i])) {
+ domainArgs[i] = getDomainClass(parameterTypes[i].asSubclass(BaseProxy.class));
} else if (EntityProxyId.class.isAssignableFrom(parameterTypes[i])) {
domainArgs[i] = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
EntityProxyId.class,
@@ -346,7 +371,7 @@
report(e);
return;
}
- die(ex, "Could not locate getter for property %s in type %s", property,
+ die(ex, "Could not locate setter for property %s in type %s", property,
domainObject.getClass().getCanonicalName());
}
@@ -381,7 +406,7 @@
private boolean isKeyType(Class<?> clazz) {
return ValueCodex.canDecode(clazz)
- || EntityProxy.class.isAssignableFrom(clazz);
+ || BaseProxy.class.isAssignableFrom(clazz);
}
/**
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
index bbe81e9..2108542 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -28,6 +28,7 @@
import com.google.gwt.dev.util.Name;
import com.google.gwt.dev.util.Name.BinaryName;
import com.google.gwt.dev.util.Name.SourceOrBinaryName;
+import com.google.gwt.requestfactory.shared.BaseProxy;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.InstanceRequest;
import com.google.gwt.requestfactory.shared.ProxyFor;
@@ -37,6 +38,7 @@
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.Service;
import com.google.gwt.requestfactory.shared.ServiceName;
+import com.google.gwt.requestfactory.shared.ValueProxy;
import java.io.IOException;
import java.io.InputStream;
@@ -138,6 +140,7 @@
private class DomainMapper extends EmptyVisitor {
private final ErrorContext logger;
private String domainInternalName;
+ private boolean isValueType;
public DomainMapper(ErrorContext logger) {
this.logger = logger;
@@ -148,6 +151,10 @@
return domainInternalName;
}
+ public boolean isValueType() {
+ return isValueType;
+ }
+
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
@@ -168,9 +175,12 @@
@Override
public void visit(String name, Object value) {
- domainInternalName = ((Type) value).getInternalName();
+ if ("value".equals(name)) {
+ domainInternalName = ((Type) value).getInternalName();
+ } else if ("isValueType".equals(name)) {
+ isValueType = (Boolean) value;
+ }
}
-
};
}
@@ -178,25 +188,30 @@
return new EmptyVisitor() {
@Override
public void visit(String name, Object value) {
- String sourceName = (String) value;
+ if ("value".equals(name)) {
+ String sourceName = (String) value;
- /*
- * The input is a source name, so we need to convert it to an
- * internal name. We'll do this by substituting dollar signs for the
- * last slash in the name until there are no more slashes.
- */
- StringBuffer desc = new StringBuffer(sourceName.replace('.', '/'));
- while (!loader.exists(desc.toString() + ".class")) {
- logger.spam("Did not find " + desc.toString());
- int idx = desc.lastIndexOf("/");
- if (idx == -1) {
- return;
+ /*
+ * The input is a source name, so we need to convert it to an
+ * internal name. We'll do this by substituting dollar signs for
+ * the last slash in the name until there are no more slashes.
+ */
+ StringBuffer desc = new StringBuffer(sourceName.replace('.', '/'));
+ while (!loader.exists(desc.toString() + ".class")) {
+ logger.spam("Did not find " + desc.toString());
+ int idx = desc.lastIndexOf("/");
+ if (idx == -1) {
+ return;
+ }
+ desc.setCharAt(idx, '$');
}
- desc.setCharAt(idx, '$');
- }
- domainInternalName = desc.toString();
- logger.spam(domainInternalName);
+ domainInternalName = desc.toString();
+ logger.spam(domainInternalName);
+ } else if ("isValueType".equals(name)) {
+ isValueType = (Boolean) value;
+ logger.spam("isValueType: %s", isValueType);
+ }
}
};
}
@@ -399,6 +414,58 @@
}
}
+ /**
+ * Return all types referenced by a method signature.
+ */
+ private static class TypesInSignatureCollector extends SignatureAdapter {
+ private final Set<Type> found = new HashSet<Type>();
+
+ public Type[] getFound() {
+ return found.toArray(new Type[found.size()]);
+ }
+
+ public SignatureVisitor visitArrayType() {
+ return this;
+ }
+
+ public SignatureVisitor visitClassBound() {
+ return this;
+ }
+
+ @Override
+ public void visitClassType(String name) {
+ found.add(Type.getObjectType(name));
+ }
+
+ public SignatureVisitor visitExceptionType() {
+ return this;
+ }
+
+ public SignatureVisitor visitInterface() {
+ return this;
+ }
+
+ public SignatureVisitor visitInterfaceBound() {
+ return this;
+ }
+
+ public SignatureVisitor visitParameterType() {
+ return this;
+ }
+
+ public SignatureVisitor visitReturnType() {
+ return this;
+ }
+
+ public SignatureVisitor visitSuperclass() {
+ return this;
+ }
+
+ public SignatureVisitor visitTypeArgument(char wildcard) {
+ return this;
+ }
+ }
+
@SuppressWarnings("unchecked")
static final Set<Class<?>> VALUE_TYPES = Collections.unmodifiableSet(new HashSet<Class<?>>(
Arrays.asList(Boolean.class, Character.class, Class.class, Date.class,
@@ -439,10 +506,19 @@
private final Set<String> badTypes = new HashSet<String>();
/**
+ * The type {@link BaseProxy}.
+ */
+ private final Type baseProxyIntf = Type.getType(BaseProxy.class);
+ /**
* Maps client types (e.g. FooProxy) to server domain types (e.g. Foo).
*/
private final Map<Type, Type> clientToDomainType = new HashMap<Type, Type>();
- private final Map<Type, Type> domainToClientType = new HashMap<Type, Type>();
+ /**
+ * Maps domain types (e.g Foo) to client proxy types (e.g. FooAProxy,
+ * FooBProxy).
+ */
+ private final Map<Type, List<Type>> domainToClientType = new HashMap<Type, List<Type>>();
+
/**
* The type {@link EntityProxy}.
*/
@@ -480,6 +556,10 @@
*/
private final Map<Type, List<Type>> supertypes = new HashMap<Type, List<Type>>();
/**
+ * The type {@link ValueProxy}.
+ */
+ private final Type valueProxyIntf = Type.getType(ValueProxy.class);
+ /**
* A set to prevent re-validation of a type.
*/
private final Set<String> validatedTypes = new HashSet<String>();
@@ -530,53 +610,37 @@
* <li>The domain object has getId() and getVersion() methods</li>
* <li>All property methods in the EntityProxy can be mapped onto an
* equivalent domain method</li>
- * <li>All referenced EntityProxy types are valid</li>
+ * <li>All referenced proxy types are valid</li>
* </ul>
*
* @param binaryName the binary name (e.g. {@link Class#getName()}) of the
* EntityProxy subtype
*/
public void validateEntityProxy(String binaryName) {
+ validateProxy(binaryName, entityProxyIntf, true);
+ }
+
+ /**
+ * Determine if the specified type implements a proxy interface and apply the
+ * appropriate validations. This can be used as a general-purpose entry method
+ * when writing unit tests.
+ *
+ * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+ * EntityProxy or ValueProxy subtype
+ */
+ public void validateProxy(String binaryName) {
if (fastFail(binaryName)) {
return;
}
Type proxyType = Type.getObjectType(BinaryName.toInternalName(binaryName));
- ErrorContext logger = parentLogger.setType(proxyType);
-
- // Quick sanity check for calling code
- if (!isAssignable(logger, entityProxyIntf, proxyType)) {
- parentLogger.poison("%s is not a %s", print(proxyType),
- EntityProxy.class.getSimpleName());
- return;
- }
-
- // Find the domain type
- Type domainType = getDomainType(logger, proxyType);
- if (domainType == errorType) {
- logger.poison(
- "The type %s must be annotated with a @%s or @%s annotation",
- BinaryName.toSourceName(binaryName), ProxyFor.class.getSimpleName(),
- ProxyForName.class.getSimpleName());
- return;
- }
-
- // Check for getId() and getVersion() in domain
- checkIdAndVersion(logger, domainType);
-
- // Collect all methods in the client proxy type
- Set<RFMethod> clientPropertyMethods = getMethodsInHierarchy(logger,
- proxyType);
-
- // Find the equivalent domain getter/setter method
- for (RFMethod clientPropertyMethod : clientPropertyMethods) {
- // Ignore stableId(). Can't use descriptor due to overrides
- if ("stableId".equals(clientPropertyMethod.getName())
- && clientPropertyMethod.getArgumentTypes().length == 0) {
- continue;
- }
- checkPropertyMethod(logger, clientPropertyMethod, domainType);
- maybeCheckReferredProxies(logger, clientPropertyMethod);
+ if (isAssignable(parentLogger, entityProxyIntf, proxyType)) {
+ validateEntityProxy(binaryName);
+ } else if (isAssignable(parentLogger, valueProxyIntf, proxyType)) {
+ validateValueProxy(binaryName);
+ } else {
+ parentLogger.poison("%s is neither an %s nor a %s", print(proxyType),
+ print(entityProxyIntf), print(valueProxyIntf));
}
}
@@ -685,13 +749,78 @@
}
/**
- * Given the binary name of a domain type, return the EntityProxy type that
- * has been seen to map to the domain type.
+ * This method checks a ValueProxy interface against its peer domain object to
+ * determine if the server code would be able to process a request using the
+ * methods defined in the ValueProxy interface. It does not perform any checks
+ * as to whether or not the ValueProxy could actually be generated by the
+ * Generator.
+ * <p>
+ * This method may be called repeatedly on a single instance of the validator.
+ * Doing so will amortize type calculation costs.
+ * <p>
+ * Checks implemented:
+ * <ul>
+ * <li> <code>binaryName</code> implements ValueProxy</li>
+ * <li><code>binaryName</code> has a {@link ProxyFor} or {@link ProxyForName}
+ * annotation</li>
+ * <li>All property methods in the EntityProxy can be mapped onto an
+ * equivalent domain method</li>
+ * <li>All referenced proxy types are valid</li>
+ * </ul>
+ *
+ * @param binaryName the binary name (e.g. {@link Class#getName()}) of the
+ * EntityProxy subtype
*/
- String getEntityProxyTypeName(String domainTypeNameBinaryName) {
- Type key = Type.getObjectType(BinaryName.toInternalName(domainTypeNameBinaryName));
- Type found = domainToClientType.get(key);
- return found == null ? null : found.getClassName();
+ public void validateValueProxy(String binaryName) {
+ validateProxy(binaryName, valueProxyIntf, false);
+ }
+
+ /**
+ * Given the binary name of a domain type, return the BaseProxy type that is
+ * assignable to {@code clientType}. This method allows multiple proxy types
+ * to be assigned to a domain type for use in different contexts (e.g. API
+ * slices). If there are multiple client types mapped to
+ * {@code domainTypeBinaryName} and assignable to {@code clientTypeBinaryName}
+ * , the first matching type will be returned.
+ */
+ String getEntityProxyTypeName(String domainTypeBinaryName,
+ String clientTypeBinaryName) {
+ Type key = Type.getObjectType(BinaryName.toInternalName(domainTypeBinaryName));
+ List<Type> found = domainToClientType.get(key);
+ if (found == null || found.isEmpty()) {
+ return null;
+ }
+ // Common case
+ if (found.size() == 1) {
+ return found.get(0).getClassName();
+ }
+
+ // Search for the first assignable type
+ Type assignableTo = Type.getObjectType(BinaryName.toInternalName(clientTypeBinaryName));
+ for (Type t : found) {
+ if (isAssignable(parentLogger, assignableTo, t)) {
+ return t.getClassName();
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Record the mapping of a domain type to a client type. Proxy types will be
+ * added to {@link #domainToClientType}.
+ */
+ private void addToDomainMap(ErrorContext logger, Type domainType,
+ Type clientType) {
+ clientToDomainType.put(clientType, domainType);
+
+ if (isAssignable(logger, baseProxyIntf, clientType)) {
+ List<Type> list = domainToClientType.get(domainType);
+ if (list == null) {
+ list = new ArrayList<Type>();
+ domainToClientType.put(domainType, list);
+ }
+ list.add(clientType);
+ }
}
/**
@@ -918,14 +1047,15 @@
* object.
*/
private Type getDomainType(ErrorContext logger, Type clientType) {
- Type toReturn = clientToDomainType.get(clientType);
- if (toReturn != null) {
- return toReturn;
+ Type domainType = clientToDomainType.get(clientType);
+ if (domainType != null) {
+ return domainType;
}
if (isValueType(logger, clientType) || isCollectionType(logger, clientType)) {
- toReturn = clientType;
- } else if (entityProxyIntf.equals(clientType)) {
- toReturn = objectType;
+ domainType = clientType;
+ } else if (entityProxyIntf.equals(clientType)
+ || valueProxyIntf.equals(clientType)) {
+ domainType = objectType;
} else {
logger = logger.setType(clientType);
DomainMapper pv = new DomainMapper(logger);
@@ -934,22 +1064,16 @@
logger.poison("%s has no mapping to a domain type (e.g. @%s or @%s)",
print(clientType), ProxyFor.class.getSimpleName(),
Service.class.getSimpleName());
- toReturn = errorType;
+ domainType = errorType;
} else {
- toReturn = Type.getObjectType(pv.getDomainInternalName());
+ domainType = Type.getObjectType(pv.getDomainInternalName());
+ if (pv.isValueType()) {
+ valueTypes.add(clientType);
+ }
}
}
- clientToDomainType.put(clientType, toReturn);
- if (isAssignable(logger, entityProxyIntf, clientType)) {
- Type previousProxyType = domainToClientType.put(toReturn, clientType);
- if (previousProxyType != null) {
- logger.poison(
- "The domain type %s has more than one proxy type: %s and %s",
- toReturn.getClassName(), previousProxyType.getClassName(),
- clientType.getClassName());
- }
- }
- return toReturn;
+ addToDomainMap(logger, domainType, clientType);
+ return domainType;
}
/**
@@ -1094,28 +1218,88 @@
}
/**
- * Examine an array of Types and call {@link #validateEntityProxy(String)} if
- * the type is an EntityProxy.
+ * Examine an array of Types and call {@link #validateEntityProxy(String)} or
+ * {@link #validateValueProxy(String)} if the type is a proxy.
*/
- private void maybeCheckEntityProxyType(ErrorContext logger, Type... types) {
+ private void maybeCheckProxyType(ErrorContext logger, Type... types) {
for (Type type : types) {
if (isAssignable(logger, entityProxyIntf, type)) {
validateEntityProxy(type.getClassName());
+ } else if (isAssignable(logger, valueProxyIntf, type)) {
+ validateValueProxy(type.getClassName());
+ } else if (isAssignable(logger, baseProxyIntf, type)) {
+ logger.poison(
+ "Invalid type hierarchy for %s. Only types derived from %s or %s may be used.",
+ print(type), print(entityProxyIntf), print(valueProxyIntf));
}
}
}
/**
- * Examine the arguments ond return value of a method and check any
+ * Examine the arguments and return value of a method and check any
* EntityProxies referred.
*/
private void maybeCheckReferredProxies(ErrorContext logger, RFMethod method) {
- Type[] argTypes = method.getArgumentTypes();
- Type returnType = getReturnType(logger, method);
+ if (method.getSignature() != null) {
+ TypesInSignatureCollector collector = new TypesInSignatureCollector();
+ SignatureReader reader = new SignatureReader(method.getSignature());
+ reader.accept(collector);
+ maybeCheckProxyType(logger, collector.getFound());
+ } else {
+ Type[] argTypes = method.getArgumentTypes();
+ Type returnType = getReturnType(logger, method);
- // Check EntityProxy args ond return types against the domain
- maybeCheckEntityProxyType(logger, argTypes);
- maybeCheckEntityProxyType(logger, returnType);
+ // Check EntityProxy args ond return types against the domain
+ maybeCheckProxyType(logger, argTypes);
+ maybeCheckProxyType(logger, returnType);
+ }
+ }
+
+ private void validateProxy(String binaryName, Type expectedType,
+ boolean requireId) {
+ if (fastFail(binaryName)) {
+ return;
+ }
+
+ Type proxyType = Type.getObjectType(BinaryName.toInternalName(binaryName));
+ ErrorContext logger = parentLogger.setType(proxyType);
+
+ // Quick sanity check for calling code
+ if (!isAssignable(logger, expectedType, proxyType)) {
+ parentLogger.poison("%s is not a %s", print(proxyType),
+ print(expectedType));
+ return;
+ }
+
+ // Find the domain type
+ Type domainType = getDomainType(logger, proxyType);
+ if (domainType == errorType) {
+ logger.poison(
+ "The type %s must be annotated with a @%s or @%s annotation",
+ BinaryName.toSourceName(binaryName), ProxyFor.class.getSimpleName(),
+ ProxyForName.class.getSimpleName());
+ return;
+ }
+
+ // Check for getId() and getVersion() in domain
+ if (requireId) {
+ checkIdAndVersion(logger, domainType);
+ }
+
+ // Collect all methods in the client proxy type
+ Set<RFMethod> clientPropertyMethods = getMethodsInHierarchy(logger,
+ proxyType);
+
+ // Find the equivalent domain getter/setter method
+ for (RFMethod clientPropertyMethod : clientPropertyMethods) {
+ // Ignore stableId(). Can't use descriptor due to overrides
+ if ("stableId".equals(clientPropertyMethod.getName())
+ && clientPropertyMethod.getArgumentTypes().length == 0) {
+ continue;
+ }
+ checkPropertyMethod(logger, clientPropertyMethod, domainType);
+ maybeCheckReferredProxies(logger, clientPropertyMethod);
+ }
}
/**
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestState.java b/user/src/com/google/gwt/requestfactory/server/RequestState.java
new file mode 100644
index 0000000..9dcccdc
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/RequestState.java
@@ -0,0 +1,199 @@
+/*
+ * 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.server;
+
+import com.google.gwt.autobean.server.AutoBeanFactoryMagic;
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.requestfactory.server.SimpleRequestProcessor.IdToEntityMap;
+import com.google.gwt.requestfactory.server.SimpleRequestProcessor.ServiceLayer;
+import com.google.gwt.requestfactory.shared.BaseProxy;
+import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.ValueProxy;
+import com.google.gwt.requestfactory.shared.impl.Constants;
+import com.google.gwt.requestfactory.shared.impl.IdFactory;
+import com.google.gwt.requestfactory.shared.impl.SimpleProxyId;
+import com.google.gwt.requestfactory.shared.messages.EntityCodex;
+import com.google.gwt.requestfactory.shared.messages.IdUtil;
+
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+/**
+ * Encapsulates all state relating to the processing of a single request so that
+ * the SimpleRequestProcessor can be stateless.
+ */
+class RequestState implements EntityCodex.EntitySource {
+ final IdToEntityMap beans = new IdToEntityMap();
+ private final Map<Object, SimpleProxyId<?>> domainObjectsToId;
+ private final IdFactory idFactory;
+ private final ServiceLayer service;
+ private final Resolver resolver;
+
+ public RequestState(RequestState parent) {
+ idFactory = parent.idFactory;
+ domainObjectsToId = parent.domainObjectsToId;
+ service = parent.service;
+ resolver = new Resolver(this);
+ }
+
+ public RequestState(final ServiceLayer service) {
+ this.service = service;
+ idFactory = new IdFactory() {
+ @Override
+ public boolean isEntityType(Class<?> clazz) {
+ return EntityProxy.class.isAssignableFrom(clazz);
+ }
+
+ @Override
+ public boolean isValueType(Class<?> clazz) {
+ return ValueProxy.class.isAssignableFrom(clazz);
+ }
+
+ @Override
+ @SuppressWarnings("unchecked")
+ protected <P extends BaseProxy> Class<P> getTypeFromToken(String typeToken) {
+ return (Class<P>) service.resolveClass(typeToken);
+ }
+
+ @Override
+ protected String getTypeToken(Class<?> clazz) {
+ return service.getTypeToken(clazz);
+ }
+ };
+ domainObjectsToId = new IdentityHashMap<Object, SimpleProxyId<?>>();
+ resolver = new Resolver(this);
+ }
+
+ public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
+ SimpleProxyId<Q> id, Object domainObject) {
+ @SuppressWarnings("unchecked")
+ AutoBean<Q> toReturn = (AutoBean<Q>) beans.get(id);
+ if (toReturn == null) {
+ toReturn = createEntityProxyBean(id, domainObject);
+ }
+ return toReturn;
+ }
+
+ /**
+ * EntityCodex support.
+ */
+ public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
+ String serializedProxyId) {
+ String typeToken = IdUtil.getTypeToken(serializedProxyId);
+ SimpleProxyId<Q> id;
+ if (IdUtil.isEphemeral(serializedProxyId)) {
+ id = idFactory.getId(typeToken, null,
+ IdUtil.getClientId(serializedProxyId));
+ } else {
+ id = idFactory.getId(
+ typeToken,
+ SimpleRequestProcessor.fromBase64(IdUtil.getServerId(serializedProxyId)));
+ }
+ return getBeanForPayload(id);
+ }
+
+ public IdFactory getIdFactory() {
+ return idFactory;
+ }
+
+ public Resolver getResolver() {
+ return resolver;
+ }
+
+ /**
+ * EntityCodex support. This method is identical to
+ * {@link IdFactory#getHistoryToken(SimpleProxyId)} except that it
+ * base64-encodes the server ids.
+ * <p>
+ * XXX: Fix the above
+ */
+ public String getSerializedProxyId(SimpleProxyId<?> stableId) {
+ if (stableId.isEphemeral()) {
+ return IdUtil.ephemeralId(stableId.getClientId(),
+ service.getTypeToken(stableId.getProxyClass()));
+ } else if (stableId.isSynthetic()) {
+ return IdUtil.syntheticId(stableId.getSyntheticId(),
+ service.getTypeToken(stableId.getProxyClass()));
+ } else {
+ return IdUtil.persistedId(
+ SimpleRequestProcessor.toBase64(stableId.getServerId()),
+ service.getTypeToken(stableId.getProxyClass()));
+ }
+ }
+
+ public ServiceLayer getServiceLayer() {
+ return service;
+ }
+
+ /**
+ * If the given domain object has been previously associated with an id,
+ * return it
+ */
+ public SimpleProxyId<?> getStableId(Object domain) {
+ return domainObjectsToId.get(domain);
+ }
+
+ /**
+ * EntityCodex support.
+ */
+ public boolean isEntityType(Class<?> clazz) {
+ return idFactory.isEntityType(clazz);
+ }
+
+ /**
+ * EntityCodex support.
+ */
+ public boolean isValueType(Class<?> clazz) {
+ return idFactory.isValueType(clazz);
+ }
+
+ /**
+ * Creates an AutoBean for the given id, tracking a domain object.
+ */
+ private <Q extends BaseProxy> AutoBean<Q> createEntityProxyBean(
+ SimpleProxyId<Q> id, Object domainObject) {
+ AutoBean<Q> toReturn = AutoBeanFactoryMagic.createBean(id.getProxyClass(),
+ SimpleRequestProcessor.CONFIGURATION);
+ toReturn.setTag(Constants.STABLE_ID, id);
+ toReturn.setTag(Constants.DOMAIN_OBJECT, domainObject);
+ beans.put(id, toReturn);
+ return toReturn;
+ }
+
+ /**
+ * Returns the AutoBean corresponding to the given id, or creates if it does
+ * not yet exist.
+ */
+ private <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
+ SimpleProxyId<Q> id) {
+ @SuppressWarnings("unchecked")
+ AutoBean<Q> toReturn = (AutoBean<Q>) beans.get(id);
+ if (toReturn == null) {
+ // Resolve the domain object
+ Class<?> domainClass = service.getDomainClass(id.getProxyClass());
+ Object domain;
+ if (id.isEphemeral()) {
+ domain = service.createDomainObject(domainClass);
+ } else {
+ String address = id.getServerId();
+ domain = service.loadDomainObject(this, domainClass, address);
+ }
+ domainObjectsToId.put(domain, id);
+ toReturn = createEntityProxyBean(id, domain);
+ }
+ return toReturn;
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/server/Resolver.java b/user/src/com/google/gwt/requestfactory/server/Resolver.java
new file mode 100644
index 0000000..20abe2c
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/server/Resolver.java
@@ -0,0 +1,431 @@
+/*
+ * 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.server;
+
+import com.google.gwt.autobean.server.impl.TypeUtils;
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.gwt.autobean.shared.AutoBeanVisitor;
+import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.gwt.requestfactory.server.SimpleRequestProcessor.ServiceLayer;
+import com.google.gwt.requestfactory.shared.BaseProxy;
+import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.impl.Constants;
+import com.google.gwt.requestfactory.shared.impl.SimpleProxyId;
+
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+/**
+ * Responsible for converting between domain and client entities. This class has
+ * a small amount of temporary state used to handle graph cycles and assignment
+ * of synthetic ids.
+ *
+ * @see RequestState#getResolver()
+ */
+class Resolver {
+ /**
+ * A parameterized type with a single parameter.
+ */
+ private static class CollectionType implements ParameterizedType {
+ private final Class<?> rawType;
+ private final Class<?> elementType;
+
+ private CollectionType(Class<?> rawType, Class<?> elementType) {
+ this.rawType = rawType;
+ this.elementType = elementType;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof CollectionType)) {
+ return false;
+ }
+ CollectionType other = (CollectionType) o;
+ return rawType.equals(other.rawType)
+ && elementType.equals(other.elementType);
+ }
+
+ public Type[] getActualTypeArguments() {
+ return new Type[] {elementType};
+ }
+
+ public Type getOwnerType() {
+ return null;
+ }
+
+ public Type getRawType() {
+ return rawType;
+ }
+
+ @Override
+ public int hashCode() {
+ return rawType.hashCode() * 13 + elementType.hashCode() * 7;
+ }
+ }
+
+ /**
+ * Used to map the objects being resolved and its API slice to the client-side
+ * value. This handles the case where a domain object is returned to the
+ * client mapped to two proxies of differing types.
+ */
+ private static class ResolutionKey {
+ private final Object domainObject;
+ private final int hashCode;
+ private final Type requestedType;
+
+ public ResolutionKey(Object domainObject, Type requestedType) {
+ this.domainObject = domainObject;
+ this.requestedType = requestedType;
+ this.hashCode = System.identityHashCode(domainObject) * 13
+ + requestedType.hashCode() * 7;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (!(o instanceof ResolutionKey)) {
+ return false;
+ }
+ ResolutionKey other = (ResolutionKey) o;
+ // Object identity comparison intentional
+ if (domainObject != other.domainObject) {
+ return false;
+ }
+ if (!requestedType.equals(other.requestedType)) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ return domainObject.toString() + " => " + requestedType.toString();
+ }
+ }
+
+ /**
+ * Returns the trailing {@code [n]} index value from a path.
+ */
+ static int index(String path) {
+ int idx = path.lastIndexOf('[');
+ if (idx == -1) {
+ return -1;
+ }
+ return Integer.parseInt(path.substring(idx + 1, path.lastIndexOf(']')));
+ }
+
+ /**
+ * Returns {@code true} if the given prefix is one of the requested property
+ * references.
+ */
+ static boolean matchesPropertyRef(Set<String> propertyRefs, String newPrefix) {
+ return propertyRefs.contains(newPrefix.replaceAll("\\[\\d+\\]", ""));
+ }
+
+ /**
+ * Removes the trailing {@code [n]} from a path.
+ */
+ static String snipIndex(String path) {
+ int idx = path.lastIndexOf('[');
+ if (idx == -1) {
+ return path;
+ }
+ return path.substring(0, idx);
+ }
+
+ /**
+ * Maps domain values to client values. This map prevents cycles in the object
+ * graph from causing infinite recursion.
+ */
+ private final Map<ResolutionKey, Object> resolved = new HashMap<ResolutionKey, Object>();
+ private final ServiceLayer service;
+ private final RequestState state;
+ private int syntheticId;
+
+ /**
+ * Should only be called from {@link RequestState}.
+ */
+ Resolver(RequestState state) {
+ this.state = state;
+ this.service = state.getServiceLayer();
+ }
+
+ /**
+ * Given a domain object, return a value that can be encoded by the client.
+ *
+ * @param domainValue the domain object to be converted into a client-side
+ * value
+ * @param assignableTo the type in the client to which the resolved value
+ * should be assignable
+ * @param propertyRefs the property references requested by the client
+ */
+ public Object resolveClientValue(Object domainValue, Type assignableTo,
+ Set<String> propertyRefs) {
+ return resolveClientValue(domainValue, assignableTo,
+ getPropertyRefs(propertyRefs), "");
+ }
+
+ /**
+ * Convert a client-side value into a domain value.
+ *
+ * @param maybeEntityProxy the client object to resolve
+ * @param detectDeadEntities if <code>true</code> this method will throw a
+ * ReportableException containing a {@link DeadEntityException} if an
+ * EntityProxy cannot be resolved
+ */
+ public Object resolveDomainValue(Object maybeEntityProxy,
+ boolean detectDeadEntities) {
+ if (maybeEntityProxy instanceof BaseProxy) {
+ AutoBean<BaseProxy> bean = AutoBeanUtils.getAutoBean((BaseProxy) maybeEntityProxy);
+ Object domain = bean.getTag(Constants.DOMAIN_OBJECT);
+ if (domain == null && detectDeadEntities) {
+ throw new ReportableException(new DeadEntityException(
+ "The requested entity is not available on the server"));
+ }
+ return domain;
+ } else if (maybeEntityProxy instanceof Collection<?>) {
+ Collection<Object> accumulator;
+ if (maybeEntityProxy instanceof List) {
+ accumulator = new ArrayList<Object>();
+ } else if (maybeEntityProxy instanceof Set) {
+ accumulator = new HashSet<Object>();
+ } else {
+ throw new ReportableException("Unsupported collection type "
+ + maybeEntityProxy.getClass().getName());
+ }
+ for (Object o : (Collection<?>) maybeEntityProxy) {
+ accumulator.add(resolveDomainValue(o, detectDeadEntities));
+ }
+ return accumulator;
+ }
+ return maybeEntityProxy;
+ }
+
+ /**
+ * Expand the property references in an InvocationMessage into a
+ * fully-expanded list of properties. For example, <code>[foo.bar.baz]</code>
+ * will be converted into <code>[foo, foo.bar, foo.bar.baz]</code>.
+ */
+ private Set<String> getPropertyRefs(Set<String> refs) {
+ if (refs == null) {
+ return Collections.emptySet();
+ }
+
+ Set<String> toReturn = new TreeSet<String>();
+ for (String raw : refs) {
+ for (int idx = raw.length(); idx >= 0; idx = raw.lastIndexOf('.', idx - 1)) {
+ toReturn.add(raw.substring(0, idx));
+ }
+ }
+ return toReturn;
+ }
+
+ /**
+ * Converts a domain entity into an EntityProxy that will be sent to the
+ * client.
+ */
+ private <T extends BaseProxy> T resolveClientProxy(final Object domainEntity,
+ Class<T> proxyType, final Set<String> propertyRefs, ResolutionKey key,
+ final String prefix) {
+ if (domainEntity == null) {
+ return null;
+ }
+
+ SimpleProxyId<? extends BaseProxy> id = state.getStableId(domainEntity);
+
+ boolean isEntityProxy = state.isEntityType(proxyType);
+ final boolean isOwnerValueProxy = state.isValueType(proxyType);
+ int version;
+
+ // Create the id or update an ephemeral id by calculating its address
+ if (id == null || id.isEphemeral()) {
+ // The address is an id or an id plus a path
+ String address;
+ if (isEntityProxy) {
+ // Compute data needed to return id to the client
+ address = service.getFlatId(state, domainEntity);
+ version = service.getVersion(domainEntity);
+ } else {
+ address = null;
+ version = 0;
+ }
+ if (id == null) {
+ if (address == null) {
+ /*
+ * This will happen when server code attempts to return an unpersisted
+ * object to the client. In this case, we'll assign a synthetic id
+ * that is valid for the duration of the response. The client is
+ * expected to assign a client-local id to this object and then it
+ * will behave as though it were an object newly-created by the
+ * client.
+ */
+ id = state.getIdFactory().allocateSyntheticId(proxyType,
+ ++syntheticId);
+ } else {
+ id = state.getIdFactory().getId(proxyType, address, null);
+ }
+ } else {
+ id.setServerId(address);
+ }
+ } else if (isEntityProxy) {
+ // Already have the id, just pull the current version
+ version = service.getVersion(domainEntity);
+ } else {
+ // The version of a value object is always 0
+ version = 0;
+ }
+
+ @SuppressWarnings("unchecked")
+ AutoBean<T> bean = (AutoBean<T>) state.getBeanForPayload(id, domainEntity);
+ resolved.put(key, bean.as());
+ bean.setTag(Constants.IN_RESPONSE, true);
+ bean.setTag(Constants.ENCODED_VERSION_PROPERTY, version);
+ bean.accept(new AutoBeanVisitor() {
+
+ @Override
+ public boolean visitReferenceProperty(String propertyName,
+ AutoBean<?> value, PropertyContext ctx) {
+ // Does the user care about the property?
+ String newPrefix = (prefix.length() > 0 ? (prefix + ".") : "")
+ + propertyName;
+
+ /*
+ * Send the property if the enclosing type is a ValueProxy, if the owner
+ * requested the property, or if the property is a list of values.
+ */
+ Class<?> elementType = ctx instanceof CollectionPropertyContext
+ ? ((CollectionPropertyContext) ctx).getElementType() : null;
+ boolean shouldSend = isOwnerValueProxy
+ || matchesPropertyRef(propertyRefs, newPrefix)
+ || elementType != null && ValueCodex.canDecode(elementType);
+
+ if (!shouldSend) {
+ return false;
+ }
+
+ // Call the getter
+ Object domainValue = service.getProperty(domainEntity, propertyName);
+ if (domainValue == null) {
+ return false;
+ }
+
+ // Turn the domain object into something usable on the client side
+ Type type;
+ if (elementType == null) {
+ type = ctx.getType();
+ } else {
+ type = new CollectionType(ctx.getType(), elementType);
+ }
+ Object clientValue = resolveClientValue(domainValue, type,
+ propertyRefs, newPrefix);
+
+ ctx.set(clientValue);
+ return false;
+ }
+
+ @Override
+ public boolean visitValueProperty(String propertyName, Object value,
+ PropertyContext ctx) {
+ // Limit unrequested value properties?
+ value = service.getProperty(domainEntity, propertyName);
+ ctx.set(value);
+ return false;
+ }
+ });
+
+ return bean.as();
+ }
+
+ /**
+ * Recursive-descent implementation.
+ */
+ private Object resolveClientValue(Object domainValue, Type returnType,
+ Set<String> propertyRefs, String prefix) {
+ if (domainValue == null) {
+ return null;
+ }
+
+ Class<?> assignableTo = TypeUtils.ensureBaseType(returnType);
+ ResolutionKey key = new ResolutionKey(domainValue, returnType);
+
+ Object previous = resolved.get(key);
+ if (previous != null && assignableTo.isInstance(previous)) {
+ return assignableTo.cast(previous);
+ }
+
+ Class<?> returnClass = service.getClientType(domainValue.getClass(),
+ assignableTo);
+
+ // Pass simple values through
+ if (ValueCodex.canDecode(returnClass)) {
+ return assignableTo.cast(domainValue);
+ }
+
+ // Convert entities to EntityProxies or EntityProxyIds
+ boolean isProxy = BaseProxy.class.isAssignableFrom(returnClass);
+ boolean isId = EntityProxyId.class.isAssignableFrom(returnClass);
+ if (isProxy || isId) {
+ BaseProxy entity = resolveClientProxy(domainValue,
+ assignableTo.asSubclass(BaseProxy.class), propertyRefs, key, prefix);
+ if (isId) {
+ return assignableTo.cast(((EntityProxy) entity).stableId());
+ }
+ return assignableTo.cast(entity);
+ }
+
+ // Convert collections
+ if (Collection.class.isAssignableFrom(returnClass)) {
+ Collection<Object> accumulator;
+ if (List.class.isAssignableFrom(returnClass)) {
+ accumulator = new ArrayList<Object>();
+ } else if (Set.class.isAssignableFrom(returnClass)) {
+ accumulator = new HashSet<Object>();
+ } else {
+ throw new ReportableException("Unsupported collection type"
+ + returnClass.getName());
+ }
+ resolved.put(key, accumulator);
+
+ Type elementType = TypeUtils.getSingleParameterization(Collection.class,
+ returnType);
+ for (Object o : (Collection<?>) domainValue) {
+ accumulator.add(resolveClientValue(o, elementType, propertyRefs, prefix));
+ }
+ return assignableTo.cast(accumulator);
+ }
+
+ throw new ReportableException("Unsupported domain type "
+ + returnClass.getCanonicalName());
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
index e8dede4..8637cf6 100644
--- a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
@@ -24,15 +24,17 @@
import com.google.gwt.autobean.shared.AutoBeanVisitor;
import com.google.gwt.autobean.shared.Splittable;
import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.gwt.requestfactory.shared.BaseProxy;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.EntityProxyId;
import com.google.gwt.requestfactory.shared.InstanceRequest;
import com.google.gwt.requestfactory.shared.ServerFailure;
import com.google.gwt.requestfactory.shared.WriteOperation;
+import com.google.gwt.requestfactory.shared.impl.BaseProxyCategory;
import com.google.gwt.requestfactory.shared.impl.Constants;
import com.google.gwt.requestfactory.shared.impl.EntityProxyCategory;
-import com.google.gwt.requestfactory.shared.impl.IdFactory;
-import com.google.gwt.requestfactory.shared.impl.SimpleEntityProxyId;
+import com.google.gwt.requestfactory.shared.impl.SimpleProxyId;
+import com.google.gwt.requestfactory.shared.impl.ValueProxyCategory;
import com.google.gwt.requestfactory.shared.messages.EntityCodex;
import com.google.gwt.requestfactory.shared.messages.EntityCodex.EntitySource;
import com.google.gwt.requestfactory.shared.messages.IdUtil;
@@ -53,13 +55,10 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
-import java.util.IdentityHashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
-import java.util.TreeSet;
import javax.validation.ConstraintViolation;
@@ -77,14 +76,24 @@
/**
* The way to introduce polymorphism.
*/
- Class<?> getClientType(Class<?> domainClass);
+ Class<?> getClientType(Class<?> domainClass, Class<?> clientType);
Class<?> getDomainClass(Class<?> clazz);
+ /**
+ * May return {@code null} to indicate that the domain object has not been
+ * persisted.
+ */
String getFlatId(EntitySource source, Object domainObject);
Object getProperty(Object domainObject, String property);
+ /**
+ * Compute the return type for a method declared in a RequestContext by
+ * analyzing the generic method declaration.
+ */
+ Type getRequestReturnType(Method contextMethod);
+
String getTypeToken(Class<?> domainClass);
int getVersion(Object domainObject);
@@ -93,7 +102,7 @@
Object loadDomainObject(EntitySource source, Class<?> clazz, String flatId);
- Class<? extends EntityProxy> resolveClass(String typeToken);
+ Class<? extends BaseProxy> resolveClass(String typeToken);
Method resolveDomainMethod(Method requestContextMethod);
@@ -111,120 +120,24 @@
* specific type.
*/
@SuppressWarnings("serial")
- private static class IdToEntityMap extends
- HashMap<SimpleEntityProxyId<?>, AutoBean<? extends EntityProxy>> {
+ static class IdToEntityMap extends
+ HashMap<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> {
}
- private class RequestState implements EntityCodex.EntitySource {
- private final IdToEntityMap beans = new IdToEntityMap();
- private final Map<Object, Integer> domainObjectsToClientId;
- private final IdFactory idFactory;
+ /**
+ * Allows the creation of properly-configured AutoBeans without having to
+ * create an AutoBeanFactory with the desired annotations.
+ */
+ static final Configuration CONFIGURATION = new Configuration.Builder().setCategories(
+ EntityProxyCategory.class, ValueProxyCategory.class,
+ BaseProxyCategory.class).setNoWrap(EntityProxyId.class).build();
- public RequestState() {
- idFactory = new IdFactory() {
- @Override
- @SuppressWarnings("unchecked")
- protected <P extends EntityProxy> Class<P> getTypeFromToken(
- String typeToken) {
- return (Class<P>) service.resolveClass(typeToken);
- }
+ /**
+ * Vends message objects.
+ */
+ static final MessageFactory FACTORY = AutoBeanFactoryMagic.create(MessageFactory.class);
- @Override
- protected String getTypeToken(Class<?> clazz) {
- return service.getTypeToken(clazz);
- }
- };
- domainObjectsToClientId = new IdentityHashMap<Object, Integer>();
- }
-
- public RequestState(RequestState parent) {
- idFactory = parent.idFactory;
- domainObjectsToClientId = parent.domainObjectsToClientId;
- }
-
- public <Q extends EntityProxy> AutoBean<Q> getBeanForPayload(
- SimpleEntityProxyId<Q> id) {
- @SuppressWarnings("unchecked")
- AutoBean<Q> toReturn = (AutoBean<Q>) beans.get(id);
- if (toReturn == null) {
- toReturn = AutoBeanFactoryMagic.createBean(id.getProxyClass(),
- CONFIGURATION);
- toReturn.setTag(STABLE_ID, id);
-
- // Resolve the domain object
- Class<?> domainClass = service.getDomainClass(id.getProxyClass());
-
- Object domain;
- if (id.isEphemeral()) {
- domain = service.createDomainObject(domainClass);
- // Don't call getFlatId here, resolve the ids after invocations
- domainObjectsToClientId.put(domain, id.getClientId());
- } else {
- domain = service.loadDomainObject(this, domainClass,
- fromBase64(id.getServerId()));
- }
- toReturn.setTag(DOMAIN_OBJECT, domain);
-
- beans.put(id, toReturn);
- }
- return toReturn;
- }
-
- /**
- * EntityCodex support.
- */
- public <Q extends EntityProxy> AutoBean<Q> getBeanForPayload(
- String serializedProxyId) {
- String typeToken = IdUtil.getTypeToken(serializedProxyId);
- SimpleEntityProxyId<Q> id;
- if (IdUtil.isEphemeral(serializedProxyId)) {
- id = idFactory.getId(typeToken, null,
- IdUtil.getClientId(serializedProxyId));
- } else {
- id = idFactory.getId(typeToken, IdUtil.getServerId(serializedProxyId));
- }
- return getBeanForPayload(id);
- }
-
- /**
- * If the domain object was sent with an ephemeral id, return the client id.
- */
- public Integer getClientId(Object domainEntity) {
- return domainObjectsToClientId.get(domainEntity);
- }
-
- /**
- * EntityCodex support.
- */
- public String getSerializedProxyId(EntityProxyId<?> stableId) {
- SimpleEntityProxyId<?> impl = (SimpleEntityProxyId<?>) stableId;
- if (impl.isEphemeral()) {
- return IdUtil.ephemeralId(impl.getClientId(),
- service.getTypeToken(stableId.getProxyClass()));
- } else {
- return IdUtil.persistedId(impl.getServerId(),
- service.getTypeToken(stableId.getProxyClass()));
- }
- }
-
- /**
- * EntityCodex support.
- */
- public boolean isEntityType(Class<?> clazz) {
- return EntityProxy.class.isAssignableFrom(clazz);
- }
- }
-
- private static final Configuration CONFIGURATION = new Configuration.Builder().setCategories(
- EntityProxyCategory.class).setNoWrap(EntityProxyId.class).build();
- private static final MessageFactory FACTORY = AutoBeanFactoryMagic.create(MessageFactory.class);
-
- // Tag constants
- private static final String DOMAIN_OBJECT = "domainObject";
- private static final String IN_RESPONSE = "inResponse";
- private static final String STABLE_ID = "stableId";
-
- private static String fromBase64(String encoded) {
+ static String fromBase64(String encoded) {
try {
return new String(Base64Utils.fromBase64(encoded), "UTF-8");
} catch (UnsupportedEncodingException e) {
@@ -232,7 +145,7 @@
}
}
- private static String toBase64(String data) {
+ static String toBase64(String data) {
try {
return Base64Utils.toBase64(data.getBytes("UTF-8"));
} catch (UnsupportedEncodingException e) {
@@ -241,6 +154,7 @@
}
private ExceptionHandler exceptionHandler = new DefaultExceptionHandler();
+
private final ServiceLayer service;
public SimpleRequestProcessor(ServiceLayer serviceLayer) {
@@ -271,7 +185,7 @@
* Main processing method.
*/
void process(RequestMessage req, ResponseMessage resp) {
- final RequestState source = new RequestState();
+ final RequestState source = new RequestState(service);
// Apply operations
processOperationMessages(source, req);
@@ -319,41 +233,44 @@
private void createReturnOperations(List<OperationMessage> operations,
RequestState returnState, IdToEntityMap toProcess) {
- for (Map.Entry<SimpleEntityProxyId<?>, AutoBean<? extends EntityProxy>> entry : toProcess.entrySet()) {
- SimpleEntityProxyId<?> id = entry.getKey();
+ for (Map.Entry<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> entry : toProcess.entrySet()) {
+ SimpleProxyId<?> id = entry.getKey();
- AutoBean<? extends EntityProxy> bean = entry.getValue();
- Object domainObject = bean.getTag(DOMAIN_OBJECT);
+ AutoBean<? extends BaseProxy> bean = entry.getValue();
+ Object domainObject = bean.getTag(Constants.DOMAIN_OBJECT);
WriteOperation writeOperation;
if (id.isEphemeral()) {
- resolveClientEntityProxy(returnState, domainObject,
- Collections.<String> emptySet(), "");
- if (id.isEphemeral()) {
- throw new ReportableException("Could not persist entity "
- + service.getFlatId(returnState, domainObject.toString()));
- }
+ // See if the entity has been persisted in the meantime
+ returnState.getResolver().resolveClientValue(domainObject,
+ id.getProxyClass(), Collections.<String> emptySet());
}
- if (service.loadDomainObject(returnState,
- service.getDomainClass(id.getProxyClass()),
- fromBase64(id.getServerId())) == null) {
+ int version;
+ if (id.isEphemeral() || id.isSynthetic()) {
+ // If the object isn't persistent, there's no reason to send an update
+ writeOperation = null;
+ version = 0;
+ } else if (service.loadDomainObject(returnState,
+ service.getDomainClass(id.getProxyClass()), id.getServerId()) == null) {
writeOperation = WriteOperation.DELETE;
+ version = 0;
} else if (id.wasEphemeral()) {
writeOperation = WriteOperation.PERSIST;
+ version = service.getVersion(domainObject);
} else {
writeOperation = WriteOperation.UPDATE;
+ version = service.getVersion(domainObject);
}
- boolean inResponse = bean.getTag(IN_RESPONSE) != null;
- int version = domainObject == null ? 0 : service.getVersion(domainObject);
+ boolean inResponse = bean.getTag(Constants.IN_RESPONSE) != null;
/*
* Don't send any data back to the client for an update on an object that
* isn't part of the response payload when the client's version matches
* the domain version.
*/
- if (writeOperation.equals(WriteOperation.UPDATE) && !inResponse) {
+ if (WriteOperation.UPDATE.equals(writeOperation) && !inResponse) {
if (Integer.valueOf(version).equals(
bean.getTag(Constants.ENCODED_VERSION_PROPERTY))) {
continue;
@@ -361,9 +278,15 @@
}
OperationMessage op = FACTORY.operation().as();
- if (writeOperation == WriteOperation.PERSIST) {
+
+ /*
+ * Send a client id if the id is ephemeral or was previously associated
+ * with a client id.
+ */
+ if (id.wasEphemeral()) {
op.setClientId(id.getClientId());
}
+
op.setOperation(writeOperation);
// Only send properties for entities that are part of the return graph
@@ -380,7 +303,12 @@
op.setPropertyMap(propertyMap);
}
- op.setServerId(id.getServerId());
+ if (!id.isEphemeral() && !id.isSynthetic()) {
+ // Send the server address only for persistent objects
+ op.setServerId(toBase64(id.getServerId()));
+ }
+
+ op.setSyntheticId(id.getSyntheticId());
op.setTypeToken(service.getTypeToken(id.getProxyClass()));
op.setVersion(version);
@@ -411,7 +339,8 @@
split = invocation.getParameters().get(i);
}
Object arg = EntityCodex.decode(source, type, elementType, split);
- arg = resolveDomainValue(arg, !EntityProxyId.class.equals(contextArgs[i]));
+ arg = source.getResolver().resolveDomainValue(arg,
+ !EntityProxyId.class.equals(contextArgs[i]));
args.add(arg);
}
@@ -446,26 +375,6 @@
return args;
}
- /**
- * Expand the property references in an InvocationMessage into a
- * fully-expanded list of properties. For example, <code>[foo.bar.baz]</code>
- * will be converted into <code>[foo, foo.bar, foo.bar.baz]</code>.
- */
- private Set<String> getPropertyRefs(InvocationMessage invocation) {
- Set<String> refs = invocation.getPropertyRefs();
- if (refs == null) {
- return Collections.emptySet();
- }
-
- Set<String> toReturn = new TreeSet<String>();
- for (String raw : refs) {
- for (int idx = raw.length(); idx >= 0; idx = raw.lastIndexOf('.', idx - 1)) {
- toReturn.add(raw.substring(0, idx));
- }
- }
- return toReturn;
- }
-
private void processInvocationMessages(RequestState state,
List<InvocationMessage> invlist, List<Splittable> results,
List<Boolean> success, RequestState returnState) {
@@ -483,8 +392,9 @@
Object returnValue = service.invoke(domainMethod, args.toArray());
// Convert domain object to client object
- returnValue = resolveClientValue(returnState, returnValue,
- getPropertyRefs(invocation), "");
+ Type requestReturnType = service.getRequestReturnType(contextMethod);
+ returnValue = state.getResolver().resolveClientValue(returnValue,
+ requestReturnType, invocation.getPropertyRefs());
// Convert the client object to a string
results.add(EntityCodex.encode(returnState, returnValue));
@@ -511,10 +421,10 @@
operation.getServerId(), operation.getTypeToken());
AutoBean<? extends EntityProxy> bean = state.getBeanForPayload(payloadId);
// Use the version later to know which objects need to be sent back
- bean.setTag(Constants.ENCODED_ID_PROPERTY, operation.getVersion());
+ bean.setTag(Constants.ENCODED_VERSION_PROPERTY, operation.getVersion());
// Load the domain object with properties, if it exists
- final Object domain = bean.getTag(DOMAIN_OBJECT);
+ final Object domain = bean.getTag(Constants.DOMAIN_OBJECT);
if (domain != null) {
// Apply any property updates
final Map<String, Splittable> flatValueMap = operation.getPropertyMap();
@@ -529,7 +439,8 @@
? ((CollectionPropertyContext) ctx).getElementType() : null;
Object newValue = EntityCodex.decode(state, ctx.getType(),
elementType, flatValueMap.get(propertyName));
- Object resolved = resolveDomainValue(newValue, false);
+ Object resolved = state.getResolver().resolveDomainValue(
+ newValue, false);
service.setProperty(domain, propertyName,
service.getDomainClass(ctx.getType()), resolved);
}
@@ -542,7 +453,8 @@
if (flatValueMap.containsKey(propertyName)) {
Splittable split = flatValueMap.get(propertyName);
Object newValue = ValueCodex.decode(ctx.getType(), split);
- Object resolved = resolveDomainValue(newValue, false);
+ Object resolved = state.getResolver().resolveDomainValue(
+ newValue, false);
service.setProperty(domain, propertyName, ctx.getType(),
resolved);
}
@@ -555,175 +467,30 @@
}
/**
- * Converts a domain entity into an EntityProxy that will be sent to the
- * client.
- */
- private EntityProxy resolveClientEntityProxy(final RequestState state,
- final Object domainEntity, final Set<String> propertyRefs,
- final String prefix) {
- if (domainEntity == null) {
- return null;
- }
-
- // Compute data needed to return id to the client
- String flatId = toBase64(service.getFlatId(state, domainEntity));
- Class<? extends EntityProxy> proxyType = service.getClientType(
- domainEntity.getClass()).asSubclass(EntityProxy.class);
-
- // Retrieve the id, possibly setting its persisted value
- SimpleEntityProxyId<? extends EntityProxy> id = state.idFactory.getId(
- proxyType, flatId, state.getClientId(domainEntity));
-
- AutoBean<? extends EntityProxy> bean = state.getBeanForPayload(id);
- bean.setTag(IN_RESPONSE, true);
- bean.accept(new AutoBeanVisitor() {
- @Override
- public boolean visitReferenceProperty(String propertyName,
- AutoBean<?> value, PropertyContext ctx) {
- // Does the user care about the property?
- String newPrefix = (prefix.length() > 0 ? (prefix + ".") : "")
- + propertyName;
-
- /*
- * Send if the user cares about the property or if it's a collection of
- * values.
- */
- Class<?> elementType = ctx instanceof CollectionPropertyContext
- ? ((CollectionPropertyContext) ctx).getElementType() : null;
- boolean shouldSend = propertyRefs.contains(newPrefix)
- || elementType != null && !state.isEntityType(elementType);
-
- if (!shouldSend) {
- return false;
- }
-
- // Call the getter
- Object domainValue = service.getProperty(domainEntity, propertyName);
- if (domainValue == null) {
- return false;
- }
-
- // Turn the domain object into something usable on the client side
- Object resolved = resolveClientValue(state, domainValue, propertyRefs,
- newPrefix);
-
- ctx.set(ctx.getType().cast(resolved));
- return false;
- }
-
- @Override
- public boolean visitValueProperty(String propertyName, Object value,
- PropertyContext ctx) {
- // Limit unrequested value properties?
- value = service.getProperty(domainEntity, propertyName);
- ctx.set(ctx.getType().cast(value));
- return false;
- }
- });
- bean.setTag(Constants.ENCODED_VERSION_PROPERTY,
- service.getVersion(domainEntity));
- return proxyType.cast(bean.as());
- }
-
- /**
- * Given a method a domain object, return a value that can be encoded by the
- * client.
- */
- private Object resolveClientValue(RequestState source, Object domainValue,
- Set<String> propertyRefs, String prefix) {
- if (domainValue == null) {
- return null;
- }
-
- Class<?> returnClass = service.getClientType(domainValue.getClass());
-
- // Pass simple values through
- if (ValueCodex.canDecode(returnClass)) {
- return domainValue;
- }
-
- // Convert entities to EntityProxies
- if (EntityProxy.class.isAssignableFrom(returnClass)) {
- return resolveClientEntityProxy(source, domainValue, propertyRefs, prefix);
- }
-
- // Convert collections
- if (Collection.class.isAssignableFrom(returnClass)) {
- Collection<Object> accumulator;
- if (List.class.isAssignableFrom(returnClass)) {
- accumulator = new ArrayList<Object>();
- } else if (Set.class.isAssignableFrom(returnClass)) {
- accumulator = new HashSet<Object>();
- } else {
- throw new ReportableException("Unsupported collection type"
- + returnClass.getName());
- }
-
- for (Object o : (Collection<?>) domainValue) {
- accumulator.add(resolveClientValue(source, o, propertyRefs, prefix));
- }
- return accumulator;
- }
-
- throw new ReportableException("Unsupported domain type "
- + returnClass.getCanonicalName());
- }
-
- /**
- * Convert a client-side value into a domain value.
- *
- * @param the client object to resolve
- * @param detectDeadEntities if <code>true</code> this method will throw a
- * ReportableException containing a {@link DeadEntityException} if an
- * EntityProxy cannot be resolved
- */
- private Object resolveDomainValue(Object maybeEntityProxy,
- boolean detectDeadEntities) {
- if (maybeEntityProxy instanceof EntityProxy) {
- AutoBean<EntityProxy> bean = AutoBeanUtils.getAutoBean((EntityProxy) maybeEntityProxy);
- Object domain = bean.getTag(DOMAIN_OBJECT);
- if (domain == null && detectDeadEntities) {
- throw new ReportableException(new DeadEntityException(
- "The requested entity is not available on the server"));
- }
- return domain;
- } else if (maybeEntityProxy instanceof Collection<?>) {
- Collection<Object> accumulator;
- if (maybeEntityProxy instanceof List) {
- accumulator = new ArrayList<Object>();
- } else if (maybeEntityProxy instanceof Set) {
- accumulator = new HashSet<Object>();
- } else {
- throw new ReportableException("Unsupported collection type "
- + maybeEntityProxy.getClass().getName());
- }
- for (Object o : (Collection<?>) maybeEntityProxy) {
- accumulator.add(resolveDomainValue(o, detectDeadEntities));
- }
- return accumulator;
- }
- return maybeEntityProxy;
- }
-
- /**
* Validate all of the entities referenced in a RequestState.
*/
private List<ViolationMessage> validateEntities(RequestState source) {
List<ViolationMessage> errorMessages = new ArrayList<ViolationMessage>();
- for (Map.Entry<SimpleEntityProxyId<?>, AutoBean<? extends EntityProxy>> entry : source.beans.entrySet()) {
- Object domainObject = entry.getValue().getTag(DOMAIN_OBJECT);
+ for (Map.Entry<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> entry : source.beans.entrySet()) {
+ AutoBean<? extends BaseProxy> bean = entry.getValue();
+ Object domainObject = bean.getTag(Constants.DOMAIN_OBJECT);
// The object could have been deleted
if (domainObject != null) {
Set<ConstraintViolation<Object>> errors = service.validate(domainObject);
if (errors != null && !errors.isEmpty()) {
- SimpleEntityProxyId<?> id = entry.getKey();
+ SimpleProxyId<?> id = entry.getKey();
for (ConstraintViolation<Object> error : errors) {
ViolationMessage message = FACTORY.violation().as();
message.setClientId(id.getClientId());
message.setMessage(error.getMessage());
message.setPath(error.getPropertyPath().toString());
- message.setServerId(id.getServerId());
+ if (id.isEphemeral()) {
+ message.setClientId(id.getClientId());
+ }
+ if (id.getServerId() != null) {
+ message.setServerId(toBase64(id.getServerId()));
+ }
message.setTypeToken(service.getTypeToken(id.getProxyClass()));
errorMessages.add(message);
}
diff --git a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
index e6c412b..74247af 100644
--- a/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/server/testing/InProcessRequestFactory.java
@@ -21,12 +21,16 @@
import com.google.gwt.autobean.shared.AutoBeanFactory.NoWrap;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.requestfactory.server.testing.InProcessRequestContext.RequestContextHandler;
+import com.google.gwt.requestfactory.shared.BaseProxy;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.EntityProxyId;
import com.google.gwt.requestfactory.shared.RequestContext;
import com.google.gwt.requestfactory.shared.RequestFactory;
+import com.google.gwt.requestfactory.shared.ValueProxy;
import com.google.gwt.requestfactory.shared.impl.AbstractRequestFactory;
+import com.google.gwt.requestfactory.shared.impl.BaseProxyCategory;
import com.google.gwt.requestfactory.shared.impl.EntityProxyCategory;
+import com.google.gwt.requestfactory.shared.impl.ValueProxyCategory;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
@@ -37,7 +41,9 @@
* An JRE-compatible implementation of RequestFactory.
*/
class InProcessRequestFactory extends AbstractRequestFactory {
- @Category(EntityProxyCategory.class)
+ @Category(value = {
+ EntityProxyCategory.class, ValueProxyCategory.class,
+ BaseProxyCategory.class})
@NoWrap(EntityProxyId.class)
interface Factory extends AutoBeanFactory {
}
@@ -60,7 +66,7 @@
InProcessRequestFactory.this).new RequestContextHandler();
return context.cast(Proxy.newProxyInstance(
Thread.currentThread().getContextClassLoader(),
- new Class<?>[]{context}, handler));
+ new Class<?>[] {context}, handler));
}
}
@@ -71,17 +77,27 @@
}
@Override
+ public boolean isEntityType(Class<?> clazz) {
+ return EntityProxy.class.isAssignableFrom(clazz);
+ }
+
+ @Override
+ public boolean isValueType(Class<?> clazz) {
+ return ValueProxy.class.isAssignableFrom(clazz);
+ }
+
+ @Override
protected AutoBeanFactory getAutoBeanFactory() {
return AutoBeanFactoryMagic.create(Factory.class);
}
@Override
@SuppressWarnings("unchecked")
- protected <P extends EntityProxy> Class<P> getTypeFromToken(String typeToken) {
+ protected <P extends BaseProxy> Class<P> getTypeFromToken(String typeToken) {
try {
- Class<? extends EntityProxy> found = Class.forName(typeToken, false,
+ Class<? extends BaseProxy> found = Class.forName(typeToken, false,
Thread.currentThread().getContextClassLoader()).asSubclass(
- EntityProxy.class);
+ BaseProxy.class);
return (Class<P>) found;
} catch (ClassNotFoundException e) {
return null;
@@ -90,6 +106,6 @@
@Override
protected String getTypeToken(Class<?> clazz) {
- return EntityProxy.class.isAssignableFrom(clazz) ? clazz.getName() : null;
+ return isEntityType(clazz) || isValueType(clazz) ? clazz.getName() : null;
}
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/BaseProxy.java b/user/src/com/google/gwt/requestfactory/shared/BaseProxy.java
new file mode 100644
index 0000000..3764fc0
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/BaseProxy.java
@@ -0,0 +1,27 @@
+/*
+ * 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;
+
+/**
+ * The root type from which all client-side proxy objects are derived. Users
+ * should not extend the BaseProxy type directly, but use either
+ * {@link EntityProxy} or {@link ValueProxy}.
+ *
+ * @see EntityProxy
+ * @see ValueProxy
+ */
+public interface BaseProxy {
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java b/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
index 1e5cecb..4bb312d 100644
--- a/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
+++ b/user/src/com/google/gwt/requestfactory/shared/EntityProxy.java
@@ -18,7 +18,7 @@
/**
* A proxy for a server-side domain object.
*/
-public interface EntityProxy {
+public interface EntityProxy extends BaseProxy {
/**
* Returns the {@link EntityProxyId} that identifies a particular instance of
* the type proxied by the receiver.
diff --git a/user/src/com/google/gwt/requestfactory/shared/RequestContext.java b/user/src/com/google/gwt/requestfactory/shared/RequestContext.java
index 495d252..c5ad7af 100644
--- a/user/src/com/google/gwt/requestfactory/shared/RequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/shared/RequestContext.java
@@ -21,13 +21,13 @@
public interface RequestContext {
/**
* Returns a new mutable proxy that this request can carry to the server,
- * perhaps to be persisted. If a persist does happen, a CREATE event will be
- * posted including the EntityProxyId of this proxy.
- *
+ * perhaps to be persisted. If the object is succesfully persisted, a PERSIST
+ * event will be posted including the EntityProxyId of this proxy.
+ *
* @param clazz a Class object of type T
- * @return an {@link EntityProxy} instance of type T
+ * @return an {@link BaseProxy} instance of type T
*/
- <T extends EntityProxy> T create(Class<T> clazz);
+ <T extends BaseProxy> T create(Class<T> clazz);
/**
* Returns a mutable version of the proxy, whose mutations will accumulate in
@@ -35,9 +35,9 @@
* be mutable.
*
* @param object an instance of type T
- * @return an {@link EntityProxy} instance of type T
+ * @return an {@link EntityProxy} or {@link ValueProxy} instance of type T
*/
- <T extends EntityProxy> T edit(T object);
+ <T extends BaseProxy> T edit(T object);
/**
* Send the accumulated changes and method invocations associated with the
@@ -51,7 +51,7 @@
/**
* For receiving errors or validation failures only.
- *
+ *
* @param receiver a {@link Receiver} instance
* @throws IllegalArgumentException if <code>receiver</code> is
* <code>null</code>
diff --git a/user/src/com/google/gwt/requestfactory/shared/ValueProxy.java b/user/src/com/google/gwt/requestfactory/shared/ValueProxy.java
new file mode 100644
index 0000000..2882b3e
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/ValueProxy.java
@@ -0,0 +1,23 @@
+/*
+ * 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;
+
+/**
+ * An analog to EntityProxy for domain types that do not have an identity
+ * concept.
+ */
+public interface ValueProxy extends BaseProxy {
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/Violation.java b/user/src/com/google/gwt/requestfactory/shared/Violation.java
index 3698740..23466b0 100644
--- a/user/src/com/google/gwt/requestfactory/shared/Violation.java
+++ b/user/src/com/google/gwt/requestfactory/shared/Violation.java
@@ -21,6 +21,14 @@
*/
public interface Violation {
/**
+ * If the ConstraintViolation occurred while validating a object, this method
+ * will return a BaseProxy that contains the invalid values.
+ *
+ * @return the BaseProxy that caused the ConstraintViolation
+ */
+ BaseProxy getInvalidProxy();
+
+ /**
* Returns the message associated with this {@link Violation}.
*
* @return a String message
@@ -28,6 +36,16 @@
String getMessage();
/**
+ * If the ConstraintViolation occurred while validating a value object that
+ * originated from the server, this method will return a BaseProxy that
+ * contains the original values.
+ *
+ * @return the BaseProxy originally sent by the server or {@code null} if the
+ * BaseProxy was created on the client.
+ */
+ BaseProxy getOriginalProxy();
+
+ /**
* Returns the path associated with this {@link Violation}.
*
* @return a String path
@@ -35,9 +53,11 @@
String getPath();
/**
- * Returns the proxy id associated with this {@link Violation}.
+ * Returns the proxy id associated with this {@link Violation} if the object
+ * associated with the violation is an {@link EntityProxy}.
*
- * @return an {@link EntityProxyId} instance
+ * @return an {@link EntityProxyId} instance or {@code null} if the object is
+ * a ValueProxy.
*/
EntityProxyId<?> getProxyId();
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
index 680286b..1b3befb 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
@@ -15,12 +15,16 @@
*/
package com.google.gwt.requestfactory.shared.impl;
+import static com.google.gwt.requestfactory.shared.impl.BaseProxyCategory.stableId;
+import static com.google.gwt.requestfactory.shared.impl.Constants.REQUEST_CONTEXT;
+
import com.google.gwt.autobean.shared.AutoBean;
import com.google.gwt.autobean.shared.AutoBeanCodex;
import com.google.gwt.autobean.shared.AutoBeanUtils;
import com.google.gwt.autobean.shared.AutoBeanVisitor;
import com.google.gwt.autobean.shared.Splittable;
import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.gwt.requestfactory.shared.BaseProxy;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.EntityProxyChange;
import com.google.gwt.requestfactory.shared.EntityProxyId;
@@ -28,10 +32,13 @@
import com.google.gwt.requestfactory.shared.RequestContext;
import com.google.gwt.requestfactory.shared.RequestTransport.TransportReceiver;
import com.google.gwt.requestfactory.shared.ServerFailure;
+import com.google.gwt.requestfactory.shared.ValueProxy;
import com.google.gwt.requestfactory.shared.Violation;
import com.google.gwt.requestfactory.shared.WriteOperation;
+import com.google.gwt.requestfactory.shared.impl.posers.DatePoser;
import com.google.gwt.requestfactory.shared.messages.EntityCodex;
import com.google.gwt.requestfactory.shared.messages.IdMessage;
+import com.google.gwt.requestfactory.shared.messages.IdUtil;
import com.google.gwt.requestfactory.shared.messages.InvocationMessage;
import com.google.gwt.requestfactory.shared.messages.MessageFactory;
import com.google.gwt.requestfactory.shared.messages.OperationMessage;
@@ -41,6 +48,9 @@
import com.google.gwt.requestfactory.shared.messages.ViolationMessage;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@@ -55,20 +65,46 @@
EntityCodex.EntitySource {
private class MyViolation implements Violation {
+ private final BaseProxy currentProxy;
private final EntityProxyId<?> id;
- private final String path;
private final String message;
+ private final String path;
+ private final BaseProxy parentProxy;
public MyViolation(ViolationMessage message) {
- id = getId(message);
+ // Support violations for value objects.
+ SimpleProxyId<BaseProxy> baseId = getId(message);
+ if (baseId instanceof EntityProxyId<?>) {
+ id = (EntityProxyId<?>) baseId;
+ } else {
+ id = null;
+ }
+ // The stub is empty, since we don't process any OperationMessages
+ AutoBean<BaseProxy> stub = getProxyForReturnPayloadGraph(baseId);
+
+ // So pick up the instance that we just sent to the server
+ AutoBean<?> edited = editedProxies.get(BaseProxyCategory.stableId(stub));
+ currentProxy = (BaseProxy) edited.as();
+
+ // Try to find the original, immutable version.
+ AutoBean<BaseProxy> parentBean = edited.getTag(PARENT_OBJECT);
+ parentProxy = parentBean == null ? null : parentBean.as();
path = message.getPath();
this.message = message.getMessage();
}
+ public BaseProxy getInvalidProxy() {
+ return currentProxy;
+ }
+
public String getMessage() {
return message;
}
+ public BaseProxy getOriginalProxy() {
+ return parentProxy;
+ }
+
public String getPath() {
return path;
}
@@ -88,12 +124,20 @@
* Objects are placed into this map by being passed into {@link #edit} or as
* an invocation argument.
*/
- private final Map<SimpleEntityProxyId<?>, AutoBean<?>> editedProxies = new LinkedHashMap<SimpleEntityProxyId<?>, AutoBean<?>>();
+ private final Map<SimpleProxyId<?>, AutoBean<?>> editedProxies = new LinkedHashMap<SimpleProxyId<?>, AutoBean<?>>();
/**
* A map that contains the canonical instance of an entity to return in the
* return graph, since this is built from scratch.
*/
- private final Map<SimpleEntityProxyId<?>, AutoBean<?>> returnedProxies = new HashMap<SimpleEntityProxyId<?>, AutoBean<?>>();
+ private final Map<SimpleProxyId<?>, AutoBean<?>> returnedProxies = new HashMap<SimpleProxyId<?>, AutoBean<?>>();
+
+ /**
+ * A map that allows us to handle the case where the server has sent back an
+ * unpersisted entity. Because we assume that the server is stateless, the
+ * client will need to swap out the request-local ids with a regular
+ * client-allocated id.
+ */
+ private final Map<Integer, SimpleProxyId<?>> syntheticIds = new HashMap<Integer, SimpleProxyId<?>>();
protected AbstractRequestContext(AbstractRequestFactory factory) {
this.requestFactory = factory;
@@ -102,21 +146,27 @@
/**
* Create a new object, with an ephemeral id.
*/
- public <T extends EntityProxy> T create(Class<T> clazz) {
+ public <T extends BaseProxy> T create(Class<T> clazz) {
checkLocked();
- AutoBean<T> created = requestFactory.createEntityProxy(clazz,
+ AutoBean<T> created = requestFactory.createProxy(clazz,
requestFactory.allocateId(clazz));
return takeOwnership(created);
}
- public <T extends EntityProxy> T edit(T object) {
- AutoBean<T> bean = checkStreamsNotCrossed(object);
+ public <T extends BaseProxy> T edit(T object) {
+ return editProxy(object);
+ }
+ /**
+ * Take ownership of a proxy instance and make it editable.
+ */
+ public <T extends BaseProxy> T editProxy(T object) {
+ AutoBean<T> bean = checkStreamsNotCrossed(object);
checkLocked();
@SuppressWarnings("unchecked")
- AutoBean<T> previouslySeen = (AutoBean<T>) editedProxies.get(object.stableId());
+ AutoBean<T> previouslySeen = (AutoBean<T>) editedProxies.get(BaseProxyCategory.stableId(bean));
if (previouslySeen != null && !previouslySeen.isFrozen()) {
/*
* If we've seen the object before, it might be because it was passed in
@@ -130,7 +180,7 @@
AutoBean<T> parent = bean;
bean = cloneBeanAndCollections(bean);
bean.setTag(PARENT_OBJECT, parent);
- return takeOwnership(bean);
+ return bean.as();
}
/**
@@ -169,9 +219,15 @@
/**
* EntityCodex support.
*/
- public <Q extends EntityProxy> AutoBean<Q> getBeanForPayload(
+ public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
String serializedProxyId) {
- SimpleEntityProxyId<Q> id = requestFactory.getProxyId(serializedProxyId);
+ SimpleProxyId<Q> id;
+ if (IdUtil.isSynthetic(serializedProxyId)) {
+ id = allocateSyntheticId(IdUtil.getTypeToken(serializedProxyId),
+ IdUtil.getSyntheticId(serializedProxyId));
+ } else {
+ id = requestFactory.getBaseProxyId(serializedProxyId);
+ }
return getProxyForReturnPayloadGraph(id);
}
@@ -182,7 +238,7 @@
/**
* EntityCodex support.
*/
- public String getSerializedProxyId(EntityProxyId<?> stableId) {
+ public String getSerializedProxyId(SimpleProxyId<?> stableId) {
return requestFactory.getHistoryToken(stableId);
}
@@ -212,7 +268,7 @@
* EntityCodex support.
*/
public boolean isEntityType(Class<?> clazz) {
- return requestFactory.getTypeToken(clazz) != null;
+ return requestFactory.isEntityType(clazz);
}
public boolean isLocked() {
@@ -220,6 +276,13 @@
}
/**
+ * EntityCodex support.
+ */
+ public boolean isValueType(Class<?> clazz) {
+ return requestFactory.isValueType(clazz);
+ }
+
+ /**
* Called by generated subclasses to enqueue a method invocation.
*/
protected void addInvocation(AbstractRequest<?> request) {
@@ -229,6 +292,22 @@
}
}
+ /**
+ * Get-or-create method for synthetic ids.
+ *
+ * @see #syntheticIds
+ */
+ private <Q extends BaseProxy> SimpleProxyId<Q> allocateSyntheticId(
+ String typeToken, int syntheticId) {
+ @SuppressWarnings("unchecked")
+ SimpleProxyId<Q> toReturn = (SimpleProxyId<Q>) syntheticIds.get(syntheticId);
+ if (toReturn == null) {
+ toReturn = requestFactory.allocateId(requestFactory.<Q> getTypeFromToken(typeToken));
+ syntheticIds.put(syntheticId, toReturn);
+ }
+ return toReturn;
+ }
+
private void checkLocked() {
if (locked) {
throw new IllegalStateException("A request is already in progress");
@@ -246,7 +325,7 @@
throw new IllegalArgumentException(object.getClass().getName());
}
- RequestContext context = bean.getTag(EntityProxyCategory.REQUEST_CONTEXT);
+ RequestContext context = bean.getTag(REQUEST_CONTEXT);
if (!bean.isFrozen() && context != this) {
/*
* This means something is way off in the weeds. If a bean is editable,
@@ -268,17 +347,64 @@
* Shallow-clones an autobean and makes duplicates of the collection types. A
* regular {@link AutoBean#clone} won't duplicate reference properties.
*/
- private <T> AutoBean<T> cloneBeanAndCollections(AutoBean<T> toClone) {
+ private <T extends BaseProxy> AutoBean<T> cloneBeanAndCollections(
+ AutoBean<T> toClone) {
AutoBean<T> clone = toClone.clone(false);
+ /*
+ * Take ownership here to prevent cycles in value objects from overflowing
+ * the stack.
+ */
+ takeOwnership(clone);
clone.accept(new AutoBeanVisitor() {
+
+ @Override
+ public boolean visitCollectionProperty(String propertyName,
+ AutoBean<Collection<?>> value, CollectionPropertyContext ctx) {
+ if (value != null) {
+ Collection<Object> collection;
+ if (List.class == ctx.getType()) {
+ collection = new ArrayList<Object>();
+ } else if (Set.class == ctx.getType()) {
+ collection = new HashSet<Object>();
+ } else {
+ // Should not get here if the validator works correctly
+ throw new IllegalArgumentException(ctx.getType().getName());
+ }
+
+ if (isValueType(ctx.getElementType())) {
+ /*
+ * Value proxies must be cloned upfront, since the value is replaced
+ * outright.
+ */
+ for (Object o : value.as()) {
+ if (o == null) {
+ collection.add(null);
+ } else {
+ collection.add(editProxy((ValueProxy) o));
+ }
+ }
+ } else {
+ // For entities and simple values, just alias the values
+ collection.addAll(value.as());
+ }
+
+ ctx.set(collection);
+ }
+ return false;
+ }
+
@Override
public boolean visitReferenceProperty(String propertyName,
AutoBean<?> value, PropertyContext ctx) {
if (value != null) {
- if (List.class == ctx.getType()) {
- ctx.set(new ArrayList<Object>((List<?>) value.as()));
- } else if (Set.class == ctx.getType()) {
- ctx.set(new HashSet<Object>((Set<?>) value.as()));
+ if (isValueType(ctx.getType())) {
+ /*
+ * Value proxies must be cloned upfront, since the value is replaced
+ * outright.
+ */
+ @SuppressWarnings("unchecked")
+ AutoBean<ValueProxy> valueBean = (AutoBean<ValueProxy>) value;
+ ctx.set(editProxy(valueBean.as()));
}
}
return false;
@@ -382,32 +508,30 @@
}
/**
- * Resolves an IdMessage into an EntityProxyId.
+ * Resolves an IdMessage into an SimpleProxyId.
*/
- private SimpleEntityProxyId<EntityProxy> getId(IdMessage op) {
- SimpleEntityProxyId<EntityProxy> id;
- if (op.getClientId() > 0) {
- id = requestFactory.getId(op.getTypeToken(), op.getServerId(),
- op.getClientId());
- } else {
- id = requestFactory.getId(op.getTypeToken(), op.getServerId());
+ private SimpleProxyId<BaseProxy> getId(IdMessage op) {
+ if (op.getSyntheticId() > 0) {
+ return allocateSyntheticId(op.getTypeToken(), op.getSyntheticId());
}
- return id;
+ if (op.getClientId() > 0) {
+ return requestFactory.getId(op.getTypeToken(), op.getServerId(),
+ op.getClientId());
+ }
+ return requestFactory.getId(op.getTypeToken(), op.getServerId());
}
/**
* Creates or retrieves a new canonical AutoBean to represent the given id in
* the returned payload.
*/
- private <Q extends EntityProxy> AutoBean<Q> getProxyForReturnPayloadGraph(
- SimpleEntityProxyId<Q> id) {
- assert !id.isEphemeral();
-
+ private <Q extends BaseProxy> AutoBean<Q> getProxyForReturnPayloadGraph(
+ SimpleProxyId<Q> id) {
@SuppressWarnings("unchecked")
AutoBean<Q> bean = (AutoBean<Q>) returnedProxies.get(id);
if (bean == null) {
Class<Q> proxyClass = id.getProxyClass();
- bean = requestFactory.createEntityProxy(proxyClass, id);
+ bean = requestFactory.createProxy(proxyClass, id);
returnedProxies.put(id, bean);
}
@@ -417,11 +541,11 @@
/**
* Make an EntityProxy immutable.
*/
- private void makeImmutable(final AutoBean<? extends EntityProxy> toMutate) {
+ private void makeImmutable(final AutoBean<? extends BaseProxy> toMutate) {
// Always diff'ed against itself, producing a no-op
toMutate.setTag(PARENT_OBJECT, toMutate);
// Act with entity-identity semantics
- toMutate.setTag(EntityProxyCategory.REQUEST_CONTEXT, null);
+ toMutate.setTag(REQUEST_CONTEXT, null);
toMutate.setFrozen(true);
}
@@ -442,37 +566,46 @@
// Compute deltas for each entity seen by the context
for (AutoBean<?> currentView : editedProxies.values()) {
@SuppressWarnings("unchecked")
- SimpleEntityProxyId<EntityProxy> stableId = EntityProxyCategory.stableId((AutoBean<EntityProxy>) currentView);
+ SimpleProxyId<BaseProxy> stableId = stableId((AutoBean<BaseProxy>) currentView);
+ assert !stableId.isSynthetic() : "Should not send synthetic id to server";
// The OperationMessages describes operations on exactly one entity
OperationMessage operation = f.operation().as();
operation.setTypeToken(requestFactory.getTypeToken(stableId.getProxyClass()));
// Find the object to compare against
- AutoBean<?> parent = currentView.getTag(PARENT_OBJECT);
- if (parent == null) {
+ AutoBean<?> parent;
+ if (stableId.isEphemeral()) {
// Newly-created object, use a blank object to compare against
- parent = requestFactory.createEntityProxy(stableId.getProxyClass(),
- stableId);
+ parent = requestFactory.createProxy(stableId.getProxyClass(), stableId);
// Newly-created objects go into the persist operation bucket
operation.setOperation(WriteOperation.PERSIST);
// The ephemeral id is passed to the server
operation.setClientId(stableId.getClientId());
} else {
+ parent = currentView.getTag(PARENT_OBJECT);
// Requests involving existing objects use the persisted id
operation.setServerId(stableId.getServerId());
operation.setOperation(WriteOperation.UPDATE);
}
+ assert parent != null;
- // Send our version number to the server to cut down on payload size
+ // Send our version number to the server to cut down on future payloads
Integer version = currentView.getTag(Constants.ENCODED_VERSION_PROPERTY);
if (version != null) {
operation.setVersion(version);
}
- // Compute what's changed on the client
- Map<String, Object> diff = AutoBeanUtils.diff(parent, currentView);
+ Map<String, Object> diff = Collections.emptyMap();
+ if (isEntityType(stableId.getProxyClass())) {
+ // Compute what's changed on the client
+ diff = AutoBeanUtils.diff(parent, currentView);
+ } else if (isValueType(stableId.getProxyClass())) {
+ // Send everything
+ diff = AutoBeanUtils.getAllProperties(currentView);
+ }
+
if (!diff.isEmpty()) {
Map<String, Splittable> propertyMap = new HashMap<String, Splittable>();
for (Map.Entry<String, Object> entry : diff.entrySet()) {
@@ -530,9 +663,8 @@
* @param returnRecord the JSON map containing property/value pairs
* @param operations the WriteOperation eventns to broadcast over the EventBus
*/
- private <Q extends EntityProxy> Q processReturnOperation(
- SimpleEntityProxyId<Q> id, OperationMessage op,
- WriteOperation... operations) {
+ private <Q extends BaseProxy> Q processReturnOperation(SimpleProxyId<Q> id,
+ OperationMessage op, WriteOperation... operations) {
AutoBean<Q> toMutate = getProxyForReturnPayloadGraph(id);
toMutate.setTag(Constants.ENCODED_VERSION_PROPERTY, op.getVersion());
@@ -564,6 +696,10 @@
if (properties.containsKey(propertyName)) {
Splittable raw = properties.get(propertyName);
Object decoded = ValueCodex.decode(ctx.getType(), raw);
+ // Hack for Date, consider generalizing for "custom serializers"
+ if (Date.class.equals(ctx.getType())) {
+ decoded = new DatePoser((Date) decoded);
+ }
ctx.set(decoded);
}
}
@@ -580,7 +716,7 @@
* Notify subscribers if the object differs from when it first came into the
* RequestContext.
*/
- if (operations != null) {
+ if (operations != null && requestFactory.isEntityType(id.getProxyClass())) {
for (WriteOperation writeOperation : operations) {
if (writeOperation.equals(WriteOperation.UPDATE)
&& !requestFactory.hasVersionChanged(id, op.getVersion())) {
@@ -588,8 +724,8 @@
continue;
}
requestFactory.getEventBus().fireEventFromSource(
- new EntityProxyChange<EntityProxy>(proxy, writeOperation),
- id.getProxyClass());
+ new EntityProxyChange<EntityProxy>((EntityProxy) proxy,
+ writeOperation), id.getProxyClass());
}
}
return proxy;
@@ -607,8 +743,10 @@
}
for (OperationMessage op : records) {
- SimpleEntityProxyId<EntityProxy> id = getId(op);
- if (id.wasEphemeral()) {
+ SimpleProxyId<?> id = getId(op);
+ if (id.isEphemeral()) {
+ processReturnOperation(id, op);
+ } else if (id.wasEphemeral()) {
processReturnOperation(id, op, WriteOperation.PERSIST,
WriteOperation.UPDATE);
} else {
@@ -643,10 +781,9 @@
/**
* Make the EnityProxy bean edited and owned by this RequestContext.
*/
- private <T extends EntityProxy> T takeOwnership(AutoBean<T> bean) {
- editedProxies.put(EntityProxyCategory.stableId(bean), bean);
- bean.setTag(EntityProxyCategory.REQUEST_CONTEXT,
- AbstractRequestContext.this);
+ private <T extends BaseProxy> T takeOwnership(AutoBean<T> bean) {
+ editedProxies.put(stableId(bean), bean);
+ bean.setTag(REQUEST_CONTEXT, AbstractRequestContext.this);
return bean.as();
}
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
index 3689365..48cfab3 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
@@ -15,9 +15,12 @@
*/
package com.google.gwt.requestfactory.shared.impl;
+import static com.google.gwt.requestfactory.shared.impl.Constants.STABLE_ID;
+
import com.google.gwt.autobean.shared.AutoBean;
import com.google.gwt.autobean.shared.AutoBeanFactory;
import com.google.gwt.event.shared.EventBus;
+import com.google.gwt.requestfactory.shared.BaseProxy;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.EntityProxyId;
import com.google.gwt.requestfactory.shared.Request;
@@ -30,7 +33,7 @@
import java.util.Map.Entry;
/**
- *
+ * Base type for generated RF interfaces.
*/
public abstract class AbstractRequestFactory extends IdFactory implements
RequestFactory {
@@ -49,17 +52,16 @@
private RequestTransport transport;
/**
- * Creates a new EntityProxy with an assigned ID.
+ * Creates a new proxy with an assigned ID.
*/
- public <T extends EntityProxy> AutoBean<T> createEntityProxy(Class<T> clazz,
- SimpleEntityProxyId<T> id) {
+ public <T extends BaseProxy> AutoBean<T> createProxy(Class<T> clazz,
+ SimpleProxyId<T> id) {
AutoBean<T> created = getAutoBeanFactory().create(clazz);
if (created == null) {
throw new IllegalArgumentException("Unknown EntityProxy type "
+ clazz.getName());
}
- created.setTag(EntityProxyCategory.REQUEST_FACTORY, this);
- created.setTag(EntityProxyCategory.STABLE_ID, id);
+ created.setTag(STABLE_ID, id);
return created;
}
@@ -79,7 +81,7 @@
protected RequestData makeRequestData() {
return new RequestData(
"com.google.gwt.requestfactory.shared.impl.FindRequest::find",
- new Object[]{proxyId}, propertyRefs, proxyId.getProxyClass(), null);
+ new Object[] {proxyId}, propertyRefs, proxyId.getProxyClass(), null);
}
};
}
@@ -88,6 +90,14 @@
return eventBus;
}
+ public String getHistoryToken(Class<? extends EntityProxy> clazz) {
+ return getTypeToken(clazz);
+ }
+
+ public String getHistoryToken(EntityProxyId<?> proxy) {
+ return getHistoryToken((SimpleProxyId<?>) proxy);
+ }
+
public Class<? extends EntityProxy> getProxyClass(String historyToken) {
String typeToken = IdUtil.getTypeToken(historyToken);
if (typeToken != null) {
@@ -96,6 +106,11 @@
return getTypeFromToken(historyToken);
}
+ @SuppressWarnings("unchecked")
+ public <T extends EntityProxy> EntityProxyId<T> getProxyId(String historyToken) {
+ return (EntityProxyId<T>) getBaseProxyId(historyToken);
+ }
+
public RequestTransport getRequestTransport() {
return transport;
}
@@ -120,8 +135,7 @@
* Used by {@link AbstractRequestContext} to quiesce update events for objects
* that haven't truly changed.
*/
- protected boolean hasVersionChanged(SimpleEntityProxyId<?> id,
- int observedVersion) {
+ protected boolean hasVersionChanged(SimpleProxyId<?> id, int observedVersion) {
String key = getHistoryToken(id);
Integer existingVersion = version.get(key);
// Return true if we haven't seen this before or the versions differ
@@ -132,4 +146,5 @@
}
return toReturn;
}
+
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/BaseProxyCategory.java b/user/src/com/google/gwt/requestfactory/shared/impl/BaseProxyCategory.java
new file mode 100644
index 0000000..9782088
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/BaseProxyCategory.java
@@ -0,0 +1,81 @@
+/*
+ * 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.impl;
+
+import static com.google.gwt.requestfactory.shared.impl.Constants.REQUEST_CONTEXT;
+import static com.google.gwt.requestfactory.shared.impl.Constants.STABLE_ID;
+
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.gwt.requestfactory.shared.BaseProxy;
+
+/**
+ * Contains behaviors common to all proxy instances.
+ */
+public class BaseProxyCategory {
+ /**
+ * Sniff all return values and ensure that if the current bean is a mutable
+ * EntityProxy, that its return values are mutable.
+ */
+ // CHECKSTYLE_OFF
+ public static <T> T __intercept(AutoBean<?> bean, T returnValue) {
+ // CHECKSTYLE_ON
+
+ AbstractRequestContext context = requestContext(bean);
+
+ /*
+ * The context will be null if the bean is immutable. If the context is
+ * locked, don't try to edit.
+ */
+ if (context == null || context.isLocked()) {
+ return returnValue;
+ }
+
+ /*
+ * EntityProxies need to be recorded specially by the RequestContext, so
+ * delegate to the edit() method for wiring up the context.
+ */
+ if (returnValue instanceof BaseProxy) {
+ @SuppressWarnings("unchecked")
+ T toReturn = (T) context.editProxy((BaseProxy) returnValue);
+ return toReturn;
+ }
+
+ if (returnValue instanceof Poser) {
+ ((Poser<?>) returnValue).setFrozen(false);
+ }
+
+ /*
+ * We're returning some object that's not an EntityProxy, most likely a
+ * Collection type. At the very least, propagate the current RequestContext
+ * so that editable chains can be constructed.
+ */
+ AutoBean<T> otherBean = AutoBeanUtils.getAutoBean(returnValue);
+ if (otherBean != null) {
+ otherBean.setTag(REQUEST_CONTEXT, bean.getTag(REQUEST_CONTEXT));
+ }
+ return returnValue;
+ }
+
+ public static AbstractRequestContext requestContext(AutoBean<?> bean) {
+ return bean.getTag(REQUEST_CONTEXT);
+ }
+
+ public static <T extends BaseProxy> SimpleProxyId<T> stableId(
+ AutoBean<? extends T> bean) {
+ return bean.getTag(STABLE_ID);
+ }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/CollectionProperty.java b/user/src/com/google/gwt/requestfactory/shared/impl/CollectionProperty.java
deleted file mode 100644
index 7c2ff23..0000000
--- a/user/src/com/google/gwt/requestfactory/shared/impl/CollectionProperty.java
+++ /dev/null
@@ -1,42 +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.shared.impl;
-
-import java.util.Collection;
-
-/**
- * @param <C> the type of the Container, must be List or Set
- * @param <E> the type of the element the container contains
- */
-public class CollectionProperty<C extends Collection<?>, E> extends Property<C> {
-
- private Class<E> leafType;
-
- public CollectionProperty(String name, String displayName, Class<C> colType,
- Class<E> type) {
- super(name, displayName, colType);
- this.leafType = type;
- }
-
- public CollectionProperty(String name, Class<C> colType, Class<E> type) {
- super(name, colType);
- this.leafType = type;
- }
-
- public Class<E> getLeafType() {
- return leafType;
- }
-}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java b/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
index 9ebc01a..58f3543 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
@@ -16,25 +16,12 @@
package com.google.gwt.requestfactory.shared.impl;
/**
- * Contains a variety of contstants shared between client and server code.
+ * Contains a variety of AutoBean tag constants to prevent typos.
*/
public interface Constants {
-
- /**
- * Property on a proxy JSO that holds its encoded server side data store id.
- */
- String ENCODED_ID_PROPERTY = "!id";
- /**
- * Property on a proxy JSO that holds its server side version data.
- */
- String ENCODED_VERSION_PROPERTY = "!version";
- /**
- * Id property that server entity objects are required to define.
- */
- String ENTITY_ID_PROPERTY = "id";
- /**
- * Version property that server entity objects are required to define.
- */
- Property<Integer> ENTITY_VERSION_PROPERTY = new Property<Integer>("version",
- Integer.class);
+ String DOMAIN_OBJECT = "domainObject";
+ String ENCODED_VERSION_PROPERTY = "version";
+ String IN_RESPONSE = "inResponse";
+ String REQUEST_CONTEXT = "requestContext";
+ String STABLE_ID = "stableId";
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java b/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
index 6fe67c6..26ed7f2 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/EntityProxyCategory.java
@@ -15,19 +15,22 @@
*/
package com.google.gwt.requestfactory.shared.impl;
+import static com.google.gwt.requestfactory.shared.impl.BaseProxyCategory.requestContext;
+import static com.google.gwt.requestfactory.shared.impl.Constants.STABLE_ID;
+
import com.google.gwt.autobean.shared.AutoBean;
import com.google.gwt.autobean.shared.AutoBeanUtils;
import com.google.gwt.requestfactory.shared.EntityProxy;
-import com.google.gwt.requestfactory.shared.RequestFactory;
/**
- * Contains static implementation methods used by the AutoBean generator.
+ * Contains static implementation of EntityProxy-specific methods.
*/
public class EntityProxyCategory {
- static final String REQUEST_CONTEXT = "requestContext";
- static final String REQUEST_FACTORY = "requestFactory";
- static final String STABLE_ID = "stableId";
+ /**
+ * EntityProxies are equal if they are from the same RequestContext and their
+ * stableIds are equal.
+ */
public static boolean equals(AutoBean<? extends EntityProxy> bean, Object o) {
if (!(o instanceof EntityProxy)) {
return false;
@@ -50,59 +53,12 @@
return stableId(bean).hashCode();
}
- public static AbstractRequestContext requestContext(AutoBean<?> bean) {
- return (AbstractRequestContext) bean.getTag(REQUEST_CONTEXT);
- }
-
- public static RequestFactory requestFactory(
- AutoBean<? extends EntityProxy> bean) {
- return (RequestFactory) bean.getTag(REQUEST_FACTORY);
- }
-
- @SuppressWarnings("unchecked")
+ /**
+ * Effectively overrides {@link BaseProxyCategory#stableId(AutoBean)} to
+ * return a narrower bound.
+ */
public static <T extends EntityProxy> SimpleEntityProxyId<T> stableId(
AutoBean<? extends T> bean) {
- return (SimpleEntityProxyId<T>) bean.getTag(STABLE_ID);
- }
-
- /**
- * Sniff all return values and ensure that if the current bean is a mutable
- * EntityProxy, that its return values are mutable.
- */
- // CHECKSTYLE_OFF
- public static <T> T __intercept(AutoBean<?> bean, T returnValue) {
- // CHECKSTYLE_ON
-
- AbstractRequestContext context = requestContext(bean);
-
- /*
- * The context will be null if the bean is immutable. If the context is
- * locked, don't try to edit.
- */
- if (context == null || context.isLocked()) {
- return returnValue;
- }
-
- /*
- * EntityProxies need to be recorded specially by the RequestContext, so
- * delegate to the edit() method for wiring up the context.
- */
- if (returnValue instanceof EntityProxy) {
- @SuppressWarnings("unchecked")
- T toReturn = (T) context.edit((EntityProxy) returnValue);
- return toReturn;
- }
-
- /*
- * We're returning some object that's not an EntityProxy, most likely a
- * Collection type. At the very least, propagate the current RequestContext
- * so that editable chains can be constructed.
- */
- AutoBean<T> otherBean = AutoBeanUtils.getAutoBean(returnValue);
- if (otherBean != null) {
- otherBean.setTag(EntityProxyCategory.REQUEST_CONTEXT,
- bean.getTag(EntityProxyCategory.REQUEST_CONTEXT));
- }
- return returnValue;
+ return bean.getTag(STABLE_ID);
}
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/EnumProperty.java b/user/src/com/google/gwt/requestfactory/shared/impl/EnumProperty.java
deleted file mode 100644
index 842d745..0000000
--- a/user/src/com/google/gwt/requestfactory/shared/impl/EnumProperty.java
+++ /dev/null
@@ -1,43 +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.shared.impl;
-
-/**
- * Defines a property of a {@link com.google.gwt.requestfactory.shared.EntityProxy}.
- *
- * @param <V> the type of the property's value
- */
-public class EnumProperty<V> extends Property<V> {
-
- private V[] values;
-
- /**
- * @param name the property's name and displayName
- * @param type the class of the property's value
- * @param values the result of Enum.values() method
- */
- public EnumProperty(String name, Class<V> type, V[] values) {
- super(name, type);
- this.values = values;
- }
-
- /**
- * Returns the values that the enum may take on.
- */
- public V[] getValues() {
- return values;
- }
-}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java b/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
index cc106e4..2d9ac74 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
@@ -15,15 +15,16 @@
*/
package com.google.gwt.requestfactory.shared.impl;
+import com.google.gwt.requestfactory.shared.BaseProxy;
import com.google.gwt.requestfactory.shared.EntityProxy;
-import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.ValueProxy;
import com.google.gwt.requestfactory.shared.messages.IdUtil;
import java.util.HashMap;
import java.util.Map;
/**
- * Handles common code for creating SimpleEntityProxyIds.
+ * Handles common code for creating SimpleProxyIds.
*/
public abstract class IdFactory {
/**
@@ -31,57 +32,114 @@
* resolves the problem of EntityProxyIds hashcodes changing after persist.
* Only ids that are created in the RequestFactory are stored here.
*/
- private final Map<String, SimpleEntityProxyId<?>> ephemeralIds = new HashMap<String, SimpleEntityProxyId<?>>();
+ private final Map<String, SimpleProxyId<?>> ephemeralIds = new HashMap<String, SimpleProxyId<?>>();
/**
* Allocates an ephemeral proxy id. This object is only valid for the lifetime
* of the RequestFactory.
*/
- public <P extends EntityProxy> SimpleEntityProxyId<P> allocateId(
- Class<P> clazz) {
- SimpleEntityProxyId<P> toReturn = new SimpleEntityProxyId<P>(clazz,
- ephemeralIds.size() + 1);
+ public <P extends BaseProxy> SimpleProxyId<P> allocateId(Class<P> clazz) {
+ SimpleProxyId<P> toReturn = createId(clazz, ephemeralIds.size() + 1);
ephemeralIds.put(getHistoryToken(toReturn), toReturn);
return toReturn;
}
- public String getHistoryToken(Class<? extends EntityProxy> clazz) {
- return getTypeToken(clazz);
+ /**
+ * Allocates a synthetic proxy id. This object is only valid for the lifetime
+ * of a request.
+ */
+ public <P extends BaseProxy> SimpleProxyId<P> allocateSyntheticId(
+ Class<P> clazz, int syntheticId) {
+ SimpleProxyId<P> toReturn = createId(clazz, "%" + syntheticId);
+ toReturn.setSyntheticId(syntheticId);
+ return toReturn;
}
- public String getHistoryToken(EntityProxyId<?> proxy) {
- SimpleEntityProxyId<?> id = (SimpleEntityProxyId<?>) proxy;
+ /**
+ * A utility function to handle generic type conversion. This method will also
+ * assert that {@code clazz} is actually an EntityProxy type.
+ */
+ @SuppressWarnings("unchecked")
+ public <P extends EntityProxy> Class<P> asEntityProxy(
+ Class<? extends BaseProxy> clazz) {
+ assert isEntityType(clazz);
+ return (Class<P>) clazz;
+ }
+
+ /**
+ * A utility function to handle generic type conversion. This method will also
+ * assert that {@code clazz} is actually a ValueProxy type.
+ */
+ @SuppressWarnings("unchecked")
+ public <P extends ValueProxy> Class<P> asValueProxy(
+ Class<? extends BaseProxy> clazz) {
+ assert isEntityType(clazz);
+ return (Class<P>) clazz;
+ }
+
+ public <P extends BaseProxy> SimpleProxyId<P> getBaseProxyId(
+ String historyToken) {
+ assert !IdUtil.isSynthetic(historyToken) : "Synthetic id resolution"
+ + " should be handled by AbstractRequestContext";
+ if (IdUtil.isPersisted(historyToken)) {
+ return getId(IdUtil.getTypeToken(historyToken),
+ IdUtil.getServerId(historyToken));
+ }
+ if (IdUtil.isEphemeral(historyToken)) {
+ @SuppressWarnings("unchecked")
+ SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(historyToken);
+
+ /*
+ * This is tested in FindServiceTest.testFetchUnpersistedFutureId. In
+ * order to get here, the user would have to get an unpersisted history
+ * token and attempt to use it with a different RequestFactory instance.
+ * This could occur if an ephemeral token were bookmarked. In this case,
+ * we'll create a token, however it will never match anything.
+ */
+ if (toReturn == null) {
+ Class<P> clazz = checkTypeToken(IdUtil.getTypeToken(historyToken));
+ toReturn = createId(clazz, -1 * ephemeralIds.size());
+ ephemeralIds.put(historyToken, toReturn);
+ }
+
+ return toReturn;
+ }
+ throw new IllegalArgumentException(historyToken);
+ }
+
+ public String getHistoryToken(SimpleProxyId<?> proxy) {
+ SimpleProxyId<?> id = (SimpleProxyId<?>) proxy;
+ String token = getTypeToken(proxy.getProxyClass());
if (id.isEphemeral()) {
- return IdUtil.ephemeralId(id.getClientId(),
- getHistoryToken(proxy.getProxyClass()));
+ return IdUtil.ephemeralId(id.getClientId(), token);
+ } else if (id.isSynthetic()) {
+ return IdUtil.syntheticId(id.getSyntheticId(), token);
} else {
- return IdUtil.persistedId(id.getServerId(),
- getHistoryToken(proxy.getProxyClass()));
+ return IdUtil.persistedId(id.getServerId(), token);
}
}
/**
- * Create or retrieve a SimpleEntityProxyId.
+ * Create or retrieve a SimpleProxyId.
*/
- public <P extends EntityProxy> SimpleEntityProxyId<P> getId(Class<P> clazz,
+ public <P extends BaseProxy> SimpleProxyId<P> getId(Class<P> clazz,
String serverId) {
return getId(getTypeToken(clazz), serverId);
}
/**
- * Create or retrieve a SimpleEntityProxyId. If both the serverId and clientId
- * are specified and the id is ephemeral, it will be updated with the server
- * id.
+ * Create or retrieve a SimpleProxyId. If both the serverId and clientId are
+ * specified and the id is ephemeral, it will be updated with the server id.
*/
- public <P extends EntityProxy> SimpleEntityProxyId<P> getId(Class<P> clazz,
+ public <P extends BaseProxy> SimpleProxyId<P> getId(Class<P> clazz,
String serverId, Integer clientId) {
return getId(getTypeToken(clazz), serverId, clientId);
}
/**
- * Create or retrieve a SimpleEntityProxyId.
+ * Create or retrieve a SimpleProxyId.
*/
- public <P extends EntityProxy> SimpleEntityProxyId<P> getId(String typeToken,
+ public <P extends BaseProxy> SimpleProxyId<P> getId(String typeToken,
String serverId) {
return getId(typeToken, serverId, null);
}
@@ -91,7 +149,7 @@
* are specified and the id is ephemeral, it will be updated with the server
* id.
*/
- public <P extends EntityProxy> SimpleEntityProxyId<P> getId(String typeToken,
+ public <P extends BaseProxy> SimpleProxyId<P> getId(String typeToken,
String serverId, Integer clientId) {
/*
* If there's a clientId, that probably means we've just created a brand-new
@@ -101,12 +159,12 @@
// Try a cache lookup for the ephemeral key
String ephemeralKey = IdUtil.ephemeralId(clientId, typeToken);
@SuppressWarnings("unchecked")
- SimpleEntityProxyId<P> toReturn = (SimpleEntityProxyId<P>) ephemeralIds.get(ephemeralKey);
+ SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(ephemeralKey);
// Do we need to allocate an ephemeral id?
if (toReturn == null) {
Class<P> clazz = getTypeFromToken(typeToken);
- toReturn = new SimpleEntityProxyId<P>(clazz, clientId);
+ toReturn = createId(clazz, clientId);
ephemeralIds.put(ephemeralKey, toReturn);
}
@@ -133,7 +191,7 @@
String serverKey = IdUtil.persistedId(serverId, typeToken);
@SuppressWarnings("unchecked")
- SimpleEntityProxyId<P> toReturn = (SimpleEntityProxyId<P>) ephemeralIds.get(serverKey);
+ SimpleProxyId<P> toReturn = (SimpleProxyId<P>) ephemeralIds.get(serverKey);
if (toReturn != null) {
// A cache hit for a locally-created object that has been persisted
return toReturn;
@@ -145,38 +203,14 @@
* case for read-dominated applications.
*/
Class<P> clazz = getTypeFromToken(typeToken);
- return new SimpleEntityProxyId<P>(clazz, serverId);
+ return createId(clazz, serverId);
}
- public <P extends EntityProxy> SimpleEntityProxyId<P> getProxyId(
- String historyToken) {
- if (IdUtil.isPersisted(historyToken)) {
- return getId(IdUtil.getTypeToken(historyToken),
- IdUtil.getServerId(historyToken));
- }
- if (IdUtil.isEphemeral(historyToken)) {
- @SuppressWarnings("unchecked")
- SimpleEntityProxyId<P> toReturn = (SimpleEntityProxyId<P>) ephemeralIds.get(historyToken);
+ public abstract boolean isEntityType(Class<?> clazz);
- /*
- * This is tested in FindServiceTest.testFetchUnpersistedFutureId. In
- * order to get here, the user would have to get an unpersisted history
- * token and attempt to use it with a different RequestFactory instance.
- * This could occur if an ephemeral token was bookmarked. In this case,
- * we'll create a token, however it will never match anything.
- */
- if (toReturn == null) {
- Class<P> clazz = checkTypeToken(IdUtil.getTypeToken(historyToken));
- toReturn = new SimpleEntityProxyId<P>(clazz, -1 * ephemeralIds.size());
- ephemeralIds.put(historyToken, toReturn);
- }
+ public abstract boolean isValueType(Class<?> clazz);
- return toReturn;
- }
- throw new IllegalArgumentException(historyToken);
- }
-
- protected abstract <P extends EntityProxy> Class<P> getTypeFromToken(
+ protected abstract <P extends BaseProxy> Class<P> getTypeFromToken(
String typeToken);
protected abstract String getTypeToken(Class<?> clazz);
@@ -190,4 +224,32 @@
return clazz;
}
+ private <P extends BaseProxy> SimpleProxyId<P> createId(Class<P> clazz,
+ int clientId) {
+ SimpleProxyId<P> toReturn;
+ if (isValueType(clazz)) {
+ toReturn = new SimpleProxyId<P>(clazz, clientId);
+ } else {
+ @SuppressWarnings("unchecked")
+ SimpleProxyId<P> temp = (SimpleProxyId<P>) new SimpleEntityProxyId<EntityProxy>(
+ asEntityProxy(clazz), clientId);
+ toReturn = (SimpleProxyId<P>) temp;
+ }
+ return toReturn;
+ }
+
+ private <P extends BaseProxy> SimpleProxyId<P> createId(Class<P> clazz,
+ String serverId) {
+ SimpleProxyId<P> toReturn;
+ if (isValueType(clazz)) {
+ toReturn = new SimpleProxyId<P>(clazz, serverId);
+ } else {
+ @SuppressWarnings("unchecked")
+ SimpleProxyId<P> temp = (SimpleProxyId<P>) new SimpleEntityProxyId<EntityProxy>(
+ asEntityProxy(clazz), serverId);
+ toReturn = (SimpleProxyId<P>) temp;
+ }
+ return toReturn;
+ }
+
}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/Poser.java b/user/src/com/google/gwt/requestfactory/shared/impl/Poser.java
new file mode 100644
index 0000000..0c16b50
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/Poser.java
@@ -0,0 +1,30 @@
+/*
+ * 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.impl;
+
+/**
+ * Used to lock down mutable, non-proxy, value objects when their owning proxy
+ * is frozen.
+ *
+ * @param <T> the type of simple value the Poser is standing in for
+ */
+public interface Poser<T> {
+ T getPosedValue();
+
+ boolean isFrozen();
+
+ void setFrozen(boolean frozen);
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/Property.java b/user/src/com/google/gwt/requestfactory/shared/impl/Property.java
deleted file mode 100644
index 15e8b84..0000000
--- a/user/src/com/google/gwt/requestfactory/shared/impl/Property.java
+++ /dev/null
@@ -1,83 +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.shared.impl;
-
-/**
- * Defines a property of an {@link EntityProxy}.
- *
- * @param <V> the type of the property's value
- */
-public class Property<V> {
- private final String name;
- private final Class<V> type;
- private final String displayName;
-
- /**
- * @param name the property's name and displayName
- * @param type the class of the property's value
- */
- public Property(String name, Class<V> type) {
- this(name, name, type);
- }
-
- /**
- * @param name the property's name
- * @param displayName the property's user visible name
- * @param type the class of the property's value
- */
- public Property(String name, String displayName, Class<V> type) {
- this.name = name;
- this.displayName = displayName;
- this.type = type;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof Property<?>)) {
- return false;
- }
- Property<?> property = (Property<?>) o;
- if (name != null ? !name.equals(property.name) : property.name != null) {
- return false;
- }
- if (type != null ? !type.equals(property.type) : property.type != null) {
- return false;
- }
- return true;
- }
-
- public String getDisplayName() {
- return displayName;
- }
-
- public String getName() {
- return name;
- }
-
- public Class<V> getType() {
- return type;
- }
-
- @Override
- public int hashCode() {
- int nameHash = name == null ? 0 : name.hashCode();
- int typeHash = type == null ? 0 : type.hashCode();
- return (nameHash * 31) ^ typeHash;
- }
-}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
index 5a98ae6..027a1d6 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleEntityProxyId.java
@@ -17,144 +17,28 @@
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.EntityProxyId;
-import com.google.gwt.requestfactory.shared.messages.IdUtil;
/**
- * Nothing fancy.
+ * Extends SimpleProxyId with the correct parameterization to implement
+ * EntityProxyId.
*
* @param <P> the type of EntityProxy object the id describes
*/
-public class SimpleEntityProxyId<P extends EntityProxy> implements
- EntityProxyId<P> {
- /**
- * A placeholder value for {@link #clientId} to indicate the id was not
- * created locally.
- */
- public static final int NEVER_EPHEMERAL = -1;
-
- /**
- * The client-side id is ephemeral, and is valid only during the lifetime of a
- * module. Any use of the client-side id except to send to the server as a
- * bookkeeping exercise is wrong.
- */
- private final int clientId;
-
- /**
- * The hashcode of the id must remain stable, even if the server id is later
- * assigned.
- */
- private final int hashCode;
-
- private final Class<P> proxyClass;
-
- /**
- * The serverId is totally opaque to the client. It's probably a
- * base64-encoded string, but it could be digits of pi. Any code that does
- * anything other than send the contents of this field back to the server is
- * wrong.
- */
- private String serverId;
+public class SimpleEntityProxyId<P extends EntityProxy> extends
+ SimpleProxyId<P> implements EntityProxyId<P> {
/**
* Construct an ephemeral id. May be called only from
* {@link IdFactory#getId()}.
*/
SimpleEntityProxyId(Class<P> proxyClass, int clientId) {
- assert proxyClass != null;
- this.clientId = clientId;
- this.proxyClass = proxyClass;
- hashCode = clientId;
+ super(proxyClass, clientId);
}
/**
* Construct a stable id. May only be called from {@link IdFactory#getId()}
*/
- SimpleEntityProxyId(Class<P> proxyClass, String serverId) {
- assert proxyClass != null;
- assert serverId != null && !serverId.contains("@")
- && !"null".equals(serverId);
- setServerId(serverId);
- clientId = NEVER_EPHEMERAL;
- hashCode = serverId.hashCode();
- this.proxyClass = proxyClass;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (!(o instanceof SimpleEntityProxyId<?>)) {
- return false;
- }
- SimpleEntityProxyId<?> other = (SimpleEntityProxyId<?>) o;
- if (!proxyClass.equals(other.proxyClass)) {
- return false;
- }
-
- if (clientId != NEVER_EPHEMERAL && clientId == other.clientId) {
- /*
- * Unexpected: It should be the case that locally-created ids are never
- * aliased and will be caught by the first if statement.
- */
- return true;
- }
-
- if (serverId != null && serverId.equals(other.serverId)) {
- return true;
- }
- return false;
- }
-
- public int getClientId() {
- return clientId;
- }
-
- public Class<P> getProxyClass() {
- return proxyClass;
- }
-
- public String getServerId() {
- return serverId;
- }
-
- @Override
- public int hashCode() {
- return hashCode;
- }
-
- public boolean isEphemeral() {
- return serverId == null;
- }
-
- /**
- * Allows the server id token to be set. This method may be called exactly
- * once over the lifetime of an id.
- */
- public void setServerId(String serverId) {
- if (this.serverId != null) {
- throw new IllegalStateException();
- }
- assert !"null".equals(serverId);
- this.serverId = serverId;
- }
-
- /**
- * For debugging use only.
- */
- @Override
- public String toString() {
- if (isEphemeral()) {
- return IdUtil.ephemeralId(clientId, proxyClass.getName());
- } else {
- return IdUtil.persistedId(serverId, proxyClass.getName());
- }
- }
-
- /**
- * Returns <code>true</code> if the id was created as an ephemeral id.
- */
- public boolean wasEphemeral() {
- return clientId != NEVER_EPHEMERAL;
+ SimpleEntityProxyId(Class<P> proxyClass, String encodedAddress) {
+ super(proxyClass, encodedAddress);
}
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java
new file mode 100644
index 0000000..943c970
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java
@@ -0,0 +1,192 @@
+/*
+ * 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.impl;
+
+import com.google.gwt.requestfactory.shared.BaseProxy;
+import com.google.gwt.requestfactory.shared.messages.IdUtil;
+
+/**
+ * The base implementation of id objects in the RequestFactory system. This type
+ * exists to allow ValueProxies to be implemented in the same manner as an
+ * EntityProxy as far as metadata maintenance is concerned. There is a specific
+ * subtype {@link SimpleEntityProxyId} which implements the requisite public
+ * interface for EntityProxy types.
+ *
+ * @param <P> the type of BaseProxy object the id describes
+ */
+public class SimpleProxyId<P extends BaseProxy> {
+ /**
+ * A placeholder value for {@link #clientId} to indicate the id was not
+ * created locally.
+ */
+ public static final int NEVER_EPHEMERAL = -1;
+
+ /**
+ * The client-side id is ephemeral, and is valid only during the lifetime of a
+ * module. Any use of the client-side id except to send to the server as a
+ * bookkeeping exercise is wrong.
+ */
+ private final int clientId;
+
+ /**
+ * The encodedAddress is totally opaque to the client. It's probably a
+ * base64-encoded string, but it could be digits of pi. Any code that does
+ * anything other than send the contents of this field back to the server is
+ * wrong.
+ */
+ private String encodedAddress;
+
+ /**
+ * The hashcode of the id must remain stable, even if the server id is later
+ * assigned.
+ */
+ private final int hashCode;
+
+ /**
+ * The EntityProxy type.
+ */
+ private final Class<P> proxyClass;
+
+ /**
+ * A flag to indicate that the id is synthetic, that it is not valid beyond
+ * the duration of the request.
+ */
+ private int syntheticId;
+
+ /**
+ * Construct an ephemeral id. May be called only from
+ * {@link IdFactory#createId()}.
+ */
+ SimpleProxyId(Class<P> proxyClass, int clientId) {
+ assert proxyClass != null;
+ this.clientId = clientId;
+ this.proxyClass = proxyClass;
+ hashCode = clientId;
+ }
+
+ /**
+ * Construct a stable id. May only be called from {@link IdFactory#createId()}
+ */
+ SimpleProxyId(Class<P> proxyClass, String encodedAddress) {
+ assert proxyClass != null;
+ assert encodedAddress != null && !encodedAddress.contains("@")
+ && !"null".equals(encodedAddress);
+ setServerId(encodedAddress);
+ clientId = NEVER_EPHEMERAL;
+ hashCode = encodedAddress.hashCode();
+ this.proxyClass = proxyClass;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof SimpleProxyId<?>)) {
+ return false;
+ }
+ SimpleProxyId<?> other = (SimpleProxyId<?>) o;
+ if (!proxyClass.equals(other.proxyClass)) {
+ return false;
+ }
+
+ if (clientId != NEVER_EPHEMERAL && clientId == other.clientId) {
+ /*
+ * Unexpected: It should be the case that locally-created ids are never
+ * aliased and will be caught by the first if statement.
+ */
+ return true;
+ }
+
+ if (encodedAddress != null && encodedAddress.equals(other.encodedAddress)) {
+ return true;
+ }
+ return false;
+ }
+
+ public int getClientId() {
+ return clientId;
+ }
+
+ public Class<P> getProxyClass() {
+ return proxyClass;
+ }
+
+ /**
+ * TODO: Rename to getAddress().
+ */
+ public String getServerId() {
+ return encodedAddress;
+ }
+
+ /**
+ * A flag to indicate that the id is synthetic, that it is not valid beyond
+ * the duration of the request.
+ */
+ public int getSyntheticId() {
+ return syntheticId;
+ }
+
+ @Override
+ public int hashCode() {
+ return hashCode;
+ }
+
+ public boolean isEphemeral() {
+ return encodedAddress == null;
+ }
+
+ public boolean isSynthetic() {
+ return syntheticId > 0;
+ }
+
+ /**
+ * Allows the server address token to be set. This method may be called
+ * exactly once over the lifetime of an id.
+ */
+ public void setServerId(String encodedAddress) {
+ if (this.encodedAddress != null) {
+ throw new IllegalStateException();
+ }
+ assert !"null".equals(encodedAddress);
+ this.encodedAddress = encodedAddress;
+ }
+
+ public void setSyntheticId(int syntheticId) {
+ this.syntheticId = syntheticId;
+ }
+
+ /**
+ * For debugging use only.
+ */
+ @Override
+ public String toString() {
+ if (isEphemeral()) {
+ return IdUtil.ephemeralId(clientId, proxyClass.getName());
+ } else if (isSynthetic()) {
+ return IdUtil.syntheticId(syntheticId, proxyClass.getName());
+ } else {
+ return IdUtil.persistedId(encodedAddress, proxyClass.getName());
+ }
+ }
+
+ /**
+ * Returns <code>true</code> if the id was created as an ephemeral id.
+ */
+ public boolean wasEphemeral() {
+ return clientId != NEVER_EPHEMERAL;
+ }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/ValueProxyCategory.java b/user/src/com/google/gwt/requestfactory/shared/impl/ValueProxyCategory.java
new file mode 100644
index 0000000..b2b3422
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/ValueProxyCategory.java
@@ -0,0 +1,61 @@
+/*
+ * 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.impl;
+
+import static com.google.gwt.requestfactory.shared.impl.BaseProxyCategory.stableId;
+
+import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
+import com.google.gwt.requestfactory.shared.ValueProxy;
+
+/**
+ * Contains static implementation of EntityProxy-specific methods.
+ */
+public class ValueProxyCategory {
+
+ /**
+ * ValueProxies are equal if they are from the same RequestContext and all of
+ * their properties are equal.
+ */
+ public static boolean equals(AutoBean<? extends ValueProxy> bean, Object o) {
+ if (!(o instanceof ValueProxy)) {
+ return false;
+ }
+ AutoBean<ValueProxy> other = AutoBeanUtils.getAutoBean((ValueProxy) o);
+ if (other == null) {
+ // Unexpected, could be an user-provided implementation?
+ return false;
+ }
+ if (!stableId(bean).getProxyClass().equals(stableId(other).getProxyClass())) {
+ // Compare AppleProxies to AppleProxies
+ return false;
+ }
+
+ /*
+ * Comparison of ValueProxies is based solely on property values. Unlike an
+ * EntityProxy, neither the id nor the RequestContext is used
+ */
+ return AutoBeanUtils.getAllProperties(bean).equals(
+ AutoBeanUtils.getAllProperties(other));
+ }
+
+ /**
+ * Hashcode depends on property values.
+ */
+ public static int hashCode(AutoBean<? extends ValueProxy> bean) {
+ return AutoBeanUtils.getAllProperties(bean).hashCode();
+ }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/posers/DatePoser.java b/user/src/com/google/gwt/requestfactory/shared/impl/posers/DatePoser.java
new file mode 100644
index 0000000..403ebc3
--- /dev/null
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/posers/DatePoser.java
@@ -0,0 +1,93 @@
+/*
+ * 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.impl.posers;
+
+import com.google.gwt.requestfactory.shared.impl.Poser;
+
+import java.util.Date;
+
+/**
+ * A sometimes-mutable implementation of {@link Date}.
+ */
+@SuppressWarnings("deprecation")
+public class DatePoser extends Date implements Poser<Date> {
+ private boolean frozen;
+
+ public DatePoser(Date copy) {
+ super(copy.getTime());
+ setFrozen(true);
+ }
+
+ public Date getPosedValue() {
+ return new Date(getTime());
+ }
+
+ public boolean isFrozen() {
+ return frozen;
+ }
+
+ @Override
+ public void setDate(int date) {
+ checkFrozen();
+ super.setDate(date);
+ }
+
+ public void setFrozen(boolean frozen) {
+ this.frozen = frozen;
+ }
+
+ @Override
+ public void setHours(int hours) {
+ checkFrozen();
+ super.setHours(hours);
+ }
+
+ @Override
+ public void setMinutes(int minutes) {
+ checkFrozen();
+ super.setMinutes(minutes);
+ }
+
+ @Override
+ public void setMonth(int month) {
+ checkFrozen();
+ super.setMonth(month);
+ }
+
+ @Override
+ public void setSeconds(int seconds) {
+ checkFrozen();
+ super.setSeconds(seconds);
+ }
+
+ @Override
+ public void setTime(long time) {
+ checkFrozen();
+ super.setTime(time);
+ }
+
+ @Override
+ public void setYear(int year) {
+ checkFrozen();
+ super.setYear(year);
+ }
+
+ private void checkFrozen() {
+ if (frozen) {
+ throw new IllegalStateException("The Date has been frozen");
+ }
+ }
+}
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java b/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java
index 09b8aac..bcf5245 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java
@@ -16,12 +16,16 @@
package com.google.gwt.requestfactory.shared.messages;
import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanUtils;
import com.google.gwt.autobean.shared.Splittable;
import com.google.gwt.autobean.shared.ValueCodex;
import com.google.gwt.autobean.shared.impl.LazySplittable;
import com.google.gwt.autobean.shared.impl.StringQuoter;
-import com.google.gwt.requestfactory.shared.EntityProxy;
+import com.google.gwt.requestfactory.shared.BaseProxy;
import com.google.gwt.requestfactory.shared.EntityProxyId;
+import com.google.gwt.requestfactory.shared.impl.BaseProxyCategory;
+import com.google.gwt.requestfactory.shared.impl.Poser;
+import com.google.gwt.requestfactory.shared.impl.SimpleProxyId;
import java.util.ArrayList;
import java.util.Collection;
@@ -37,12 +41,13 @@
* Abstracts the process by which EntityProxies are created.
*/
public interface EntitySource {
- <Q extends EntityProxy> AutoBean<Q> getBeanForPayload(
- String serializedProxyId);
+ <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(String serializedProxyId);
- String getSerializedProxyId(EntityProxyId<?> stableId);
+ String getSerializedProxyId(SimpleProxyId<?> stableId);
boolean isEntityType(Class<?> clazz);
+
+ boolean isValueType(Class<?> clazz);
}
/**
@@ -88,7 +93,8 @@
return collection;
}
- if (source.isEntityType(type) || EntityProxyId.class.equals(type)) {
+ if (source.isEntityType(type) || source.isValueType(type)
+ || EntityProxyId.class.equals(type)) {
return source.getBeanForPayload(split.asString()).as();
}
@@ -133,12 +139,17 @@
return new LazySplittable(toReturn.toString());
}
- if (value instanceof EntityProxy) {
- value = source.getSerializedProxyId(((EntityProxy) value).stableId());
+ if (value instanceof BaseProxy) {
+ AutoBean<BaseProxy> autoBean = AutoBeanUtils.getAutoBean((BaseProxy) value);
+ value = BaseProxyCategory.stableId(autoBean);
}
- if (value instanceof EntityProxyId<?>) {
- value = source.getSerializedProxyId((EntityProxyId<?>) value);
+ if (value instanceof SimpleProxyId<?>) {
+ value = source.getSerializedProxyId((SimpleProxyId<?>) value);
+ }
+
+ if (value instanceof Poser<?>) {
+ value = ((Poser<?>) value).getPosedValue();
}
return ValueCodex.encode(value);
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
index 11c758f..7be22c8 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
@@ -24,6 +24,7 @@
String CLIENT_ID = "C";
String SERVER_ID = "S";
String TYPE_TOKEN = "T";
+ String SYNTHETIC_ID = "Y";
@PropertyName(CLIENT_ID)
int getClientId();
@@ -31,6 +32,9 @@
@PropertyName(SERVER_ID)
String getServerId();
+ @PropertyName(SYNTHETIC_ID)
+ int getSyntheticId();
+
@PropertyName(TYPE_TOKEN)
String getTypeToken();
@@ -40,6 +44,9 @@
@PropertyName(SERVER_ID)
void setServerId(String value);
+ @PropertyName(SYNTHETIC_ID)
+ void setSyntheticId(int value);
+
@PropertyName(TYPE_TOKEN)
void setTypeToken(String value);
}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/IdUtil.java b/user/src/com/google/gwt/requestfactory/shared/messages/IdUtil.java
index de8c906..5efd614 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/IdUtil.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/IdUtil.java
@@ -19,9 +19,11 @@
* Common functions for slicing and dicing EntityProxy ids.
*/
public class IdUtil {
- private static final String ANY_SEPARATOR_PATTERN = "@[01]@";
+ private static final String ANY_SEPARATOR_PATTERN = "@[012]@";
private static final String EPHEMERAL_SEPARATOR = "@1@";
private static final String TOKEN_SEPARATOR = "@0@";
+ private static final String SYNTHETIC_SEPARATOR = "@2@";
+
private static final int ID_TOKEN_INDEX = 0;
private static final int TYPE_TOKEN_INDEX = 1;
@@ -37,6 +39,10 @@
return asPersisted(encodedId)[ID_TOKEN_INDEX];
}
+ public static int getSyntheticId(String encodedId) {
+ return Integer.valueOf(asSynthetic(encodedId)[ID_TOKEN_INDEX]);
+ }
+
public static String getTypeToken(String encodedId) {
String[] split = asAny(encodedId);
if (split.length == 2) {
@@ -53,10 +59,18 @@
return encodedId.contains(TOKEN_SEPARATOR);
}
+ public static boolean isSynthetic(String encodedId) {
+ return encodedId.contains(SYNTHETIC_SEPARATOR);
+ }
+
public static String persistedId(String serverId, String typeToken) {
return serverId + TOKEN_SEPARATOR + typeToken;
}
+ public static String syntheticId(int syntheticId, String historyToken) {
+ return syntheticId + SYNTHETIC_SEPARATOR + historyToken;
+ }
+
private static String[] asAny(String encodedId) {
return encodedId.split(ANY_SEPARATOR_PATTERN);
}
@@ -69,8 +83,12 @@
return encodedId.split(TOKEN_SEPARATOR);
}
+ private static String[] asSynthetic(String encodedId) {
+ return encodedId.split(SYNTHETIC_SEPARATOR);
+ }
+
/**
- * Utility class
+ * Utility class.
*/
private IdUtil() {
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java b/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
index cde822c..5742010 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
@@ -22,10 +22,10 @@
* The factory for creating RequestFactory wire messages.
*/
public interface MessageFactory extends AutoBeanFactory {
- AutoBean<InvocationMessage> invocation();
-
AutoBean<ServerFailureMessage> failure();
+ AutoBean<InvocationMessage> invocation();
+
AutoBean<OperationMessage> operation();
AutoBean<RequestMessage> request();
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java
index e4135c1..2555ea5 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/ResponseMessage.java
@@ -21,7 +21,7 @@
import java.util.List;
/**
- * The result of fulfilling a request on the server;
+ * The result of fulfilling a request on the server.
*/
public interface ResponseMessage extends VersionedMessage {
String GENERAL_FAILURE = "F";
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java
index ea57a59..b891ac0 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/ViolationMessage.java
@@ -18,7 +18,7 @@
import com.google.gwt.autobean.shared.AutoBean.PropertyName;
/**
- * Represents a ConstraintViolation
+ * Represents a ConstraintViolation.
*/
public interface ViolationMessage extends IdMessage {
String MESSAGE = "M";
diff --git a/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java b/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
index 956bfd4..d7164d4 100644
--- a/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
+++ b/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
@@ -43,7 +43,6 @@
import com.google.gwt.requestfactory.shared.RequestContext;
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.Violation;
-import com.google.gwt.requestfactory.shared.impl.Property;
import com.google.gwt.user.client.TakesValue;
import com.google.gwt.user.client.ui.HasText;
@@ -260,9 +259,10 @@
EditorData[] data = m.getEditorData();
assertNotNull(data);
assertEquals(2, data.length);
- assertEquals(Arrays.asList("b", "b.string"), Arrays.asList(
- data[0].getPath(), data[1].getPath()));
- assertEquals(Arrays.asList("bEditor().asEditor()", "stringEditor()"),
+ assertEquals(Arrays.asList("b", "b.string"),
+ Arrays.asList(data[0].getPath(), data[1].getPath()));
+ assertEquals(
+ Arrays.asList("bEditor().asEditor()", "stringEditor()"),
Arrays.asList(data[0].getSimpleExpression(),
data[1].getSimpleExpression()));
}
@@ -285,9 +285,11 @@
"bEditor().viewEditor()"), Arrays.asList(data[0].getExpression(),
data[1].getExpression(), data[2].getExpression(),
data[3].getExpression()));
- assertEquals(Arrays.asList(true, false, true, false), Arrays.asList(
- data[0].isDelegateRequired(), data[1].isDelegateRequired(),
- data[2].isDelegateRequired(), data[3].isDelegateRequired()));
+ assertEquals(
+ Arrays.asList(true, false, true, false),
+ Arrays.asList(data[0].isDelegateRequired(),
+ data[1].isDelegateRequired(), data[2].isDelegateRequired(),
+ data[3].isDelegateRequired()));
}
public void testListDriver() throws UnableToCompleteException {
@@ -333,10 +335,12 @@
public void testMissingGetter() {
UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
builder.setLowestLogLevel(TreeLogger.ERROR);
- builder.expectError(EditorModel.noGetterMessage("missing",
- types.findType("t.MissingGetterEditorDriver.AProxy")), null);
- builder.expectError(EditorModel.noGetterMessage("yetAgain",
- types.findType("t.MissingGetterEditorDriver.AProxy")), null);
+ builder.expectError(
+ EditorModel.noGetterMessage("missing",
+ types.findType("t.MissingGetterEditorDriver.AProxy")), null);
+ builder.expectError(
+ EditorModel.noGetterMessage("yetAgain",
+ types.findType("t.MissingGetterEditorDriver.AProxy")), null);
builder.expectError(EditorModel.poisonedMessage(), null);
UnitTestTreeLogger testLogger = builder.createLogger();
try {
@@ -354,8 +358,9 @@
public void testSanityErrorMessages() {
UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
builder.setLowestLogLevel(TreeLogger.ERROR);
- builder.expectError(EditorModel.unexpectedInputTypeMessage(rfedType,
- types.getJavaLangObject()), null);
+ builder.expectError(
+ EditorModel.unexpectedInputTypeMessage(rfedType,
+ types.getJavaLangObject()), null);
builder.expectError(EditorModel.mustExtendMessage(rfedType), null);
builder.expectError(
EditorModel.tooManyInterfacesMessage(types.findType("t.TooManyInterfacesEditorDriver")),
@@ -384,9 +389,11 @@
public void testUnparameterizedEditor() {
UnitTestTreeLogger.Builder builder = new UnitTestTreeLogger.Builder();
builder.setLowestLogLevel(TreeLogger.ERROR);
- builder.expectError(EditorModel.noEditorParameterizationMessage(
- types.findType(Editor.class.getName()), types.findType(
- SimpleEditor.class.getName()).isGenericType().getRawType()), null);
+ builder.expectError(
+ EditorModel.noEditorParameterizationMessage(
+ types.findType(Editor.class.getName()),
+ types.findType(SimpleEditor.class.getName()).isGenericType().getRawType()),
+ null);
UnitTestTreeLogger testLogger = builder.createLogger();
try {
new EditorModel(testLogger,
@@ -486,7 +493,7 @@
return code;
}
}, new MockJavaResource("t.CyclicEditorDriver") {
- // Tests error-detection when the editor graph isn't a DAG
+ // Tests error-detection when the editor graph isn't a DAG
@Override
protected CharSequence getContent() {
StringBuilder code = new StringBuilder();
@@ -510,7 +517,7 @@
return code;
}
}, new MockJavaResource("t.DottedPathEditorDriver") {
- // Tests error-detection when the editor graph isn't a DAG
+ // Tests error-detection when the editor graph isn't a DAG
@Override
protected CharSequence getContent() {
StringBuilder code = new StringBuilder();
@@ -532,7 +539,7 @@
return code;
}
}, new MockJavaResource("t.ListEditor") {
- // Tests error-detection when the editor graph isn't a DAG
+ // Tests error-detection when the editor graph isn't a DAG
@Override
protected CharSequence getContent() {
StringBuilder code = new StringBuilder();
@@ -555,7 +562,8 @@
return code;
}
}, new MockJavaResource("t.MissingGetterEditorDriver") {
- // Tests error-detection when the editor structure doesn't match the proxy
+ // Tests error-detection when the editor structure doesn't match the
+ // proxy
@Override
protected CharSequence getContent() {
StringBuilder code = new StringBuilder();
@@ -657,7 +665,7 @@
return code;
}
}, new MockJavaResource("t.TooManyInterfacesEditorDriver") {
- // Tests a Driver interface that extends more than RFED
+ // Tests a Driver interface that extends more than RFED
@Override
protected CharSequence getContent() {
StringBuilder code = new StringBuilder();
@@ -671,7 +679,8 @@
return code;
}
}, new MockJavaResource("t.UnparameterizedEditorEditorDriver") {
- // Tests error-detection when the editor structure doesn't match the proxy
+ // Tests error-detection when the editor structure doesn't match the
+ // proxy
@Override
protected CharSequence getContent() {
StringBuilder code = new StringBuilder();
@@ -692,7 +701,8 @@
return code;
}
}, new MockJavaResource("t.UsesIsEditorDriver") {
- // Tests error-detection when the editor structure doesn't match the proxy
+ // Tests error-detection when the editor structure doesn't match the
+ // proxy
@Override
protected CharSequence getContent() {
StringBuilder code = new StringBuilder();
@@ -721,7 +731,8 @@
return code;
}
}, new MockJavaResource("t.UsesIsEditorAndEditorDriver") {
- // Tests error-detection when the editor structure doesn't match the proxy
+ // Tests error-detection when the editor structure doesn't match the
+ // proxy
@Override
protected CharSequence getContent() {
StringBuilder code = new StringBuilder();
@@ -751,7 +762,7 @@
return code;
}
}, new MockJavaResource("java.util.List") {
- // Tests a Driver interface that extends more than RFED
+ // Tests a Driver interface that extends more than RFED
@Override
protected CharSequence getContent() {
StringBuilder code = new StringBuilder();
@@ -774,7 +785,6 @@
new RealJavaResource(IsEditor.class),
new EmptyMockJavaResource(Iterable.class),
new RealJavaResource(LeafValueEditor.class),
- new EmptyMockJavaResource(Property.class),
new EmptyMockJavaResource(EntityProxy.class),
new EmptyMockJavaResource(RequestFactory.class),
new RealJavaResource(RequestFactoryEditorDriver.class),
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
index 642d96a..19d5130 100644
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
@@ -27,6 +27,7 @@
import com.google.gwt.requestfactory.shared.SimpleEnum;
import com.google.gwt.requestfactory.shared.SimpleFooProxy;
import com.google.gwt.requestfactory.shared.SimpleFooRequest;
+import com.google.gwt.requestfactory.shared.SimpleValueProxy;
import com.google.gwt.requestfactory.shared.Violation;
import com.google.gwt.requestfactory.shared.impl.SimpleEntityProxyId;
@@ -187,48 +188,6 @@
}
}
- public void disabled_testEchoComplexFutures() {
- // relate futures on the server. Check if the relationship is still present
- // on the client.
- delayTestFinish(DELAY_TEST_FINISH);
- final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
- EntityProxyChange.registerForProxyType(req.getEventBus(),
- SimpleFooProxy.class, handler);
- SimpleFooRequest context = req.simpleFooRequest();
- final SimpleFooProxy simpleFoo = context.create(SimpleFooProxy.class);
- final SimpleBarProxy simpleBar = context.create(SimpleBarProxy.class);
- context.echoComplex(simpleFoo, simpleBar).fire(
- new Receiver<SimpleFooProxy>() {
- @Override
- public void onSuccess(SimpleFooProxy response) {
- assertEquals(0, handler.totalEventCount);
- checkStableIdEquals(simpleFoo, response);
- SimpleBarProxy responseBar = response.getBarField();
- assertNotNull(responseBar);
- checkStableIdEquals(simpleBar, responseBar);
- finishTestAndReset();
- }
- });
- }
-
- public void disabled_testEchoSimpleFutures() {
- // tests if futureIds can be echoed back.
- delayTestFinish(DELAY_TEST_FINISH);
- final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
- EntityProxyChange.registerForProxyType(req.getEventBus(),
- SimpleFooProxy.class, handler);
- SimpleFooRequest context = req.simpleFooRequest();
- final SimpleFooProxy simpleFoo = context.create(SimpleFooProxy.class);
- context.echo(simpleFoo).fire(new Receiver<SimpleFooProxy>() {
- @Override
- public void onSuccess(SimpleFooProxy response) {
- assertEquals(0, handler.totalEventCount);
- checkStableIdEquals(simpleFoo, response);
- finishTestAndReset();
- }
- });
- }
-
/**
* Test that removing a parent entity and implicitly removing the child sends
* an event to the client that the child was removed.
@@ -925,7 +884,7 @@
*/
public void testNullValueInIntegerListRequest() {
delayTestFinish(DELAY_TEST_FINISH);
- List<Integer> list = Arrays.asList(new Integer[]{1, 2, null});
+ List<Integer> list = Arrays.asList(new Integer[] {1, 2, null});
final Request<Void> fooReq = req.simpleFooRequest().receiveNullValueInIntegerList(
list);
fooReq.fire(new Receiver<Void>() {
@@ -941,7 +900,7 @@
*/
public void testNullValueInStringListRequest() {
delayTestFinish(DELAY_TEST_FINISH);
- List<String> list = Arrays.asList(new String[]{"nonnull", "null", null});
+ List<String> list = Arrays.asList(new String[] {"nonnull", "null", null});
final Request<Void> fooReq = req.simpleFooRequest().receiveNullValueInStringList(
list);
fooReq.fire(new Receiver<Void>() {
@@ -986,11 +945,6 @@
});
}
- /*
- * TODO: all these tests should check the final values. It will be easy when
- * we have better persistence than the singleton pattern.
- */
-
public void testPersistExistingEntityExistingRelation() {
delayTestFinish(DELAY_TEST_FINISH);
@@ -1123,6 +1077,11 @@
}
/*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
+
+ /*
* Find Entity2 Create Entity, Persist Entity Relate Entity2 to Entity Persist
* Entity
*/
@@ -1268,11 +1227,6 @@
});
}
- /*
- * TODO: all these tests should check the final values. It will be easy when
- * we have better persistence than the singleton pattern.
- */
-
public void testPersistRelation() {
delayTestFinish(DELAY_TEST_FINISH);
@@ -1316,11 +1270,6 @@
});
}
- /*
- * TODO: all these tests should check the final values. It will be easy when
- * we have better persistence than the singleton pattern.
- */
-
public void testPersistSelfOneToManyExistingEntityExistingRelation() {
delayTestFinish(DELAY_TEST_FINISH);
@@ -1374,6 +1323,11 @@
* TODO: all these tests should check the final values. It will be easy when
* we have better persistence than the singleton pattern.
*/
+
+ /*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
public void testPersistValueListNull() {
delayTestFinish(DELAY_TEST_FINISH);
simpleFooRequest().findSimpleFooById(999L).fire(
@@ -1402,6 +1356,11 @@
* TODO: all these tests should check the final values. It will be easy when
* we have better persistence than the singleton pattern.
*/
+
+ /*
+ * TODO: all these tests should check the final values. It will be easy when
+ * we have better persistence than the singleton pattern.
+ */
public void testPersistValueListRemove() {
delayTestFinish(DELAY_TEST_FINISH);
simpleFooRequest().findSimpleFooById(999L).fire(
@@ -1617,7 +1576,7 @@
Set<SimpleBarProxy> setField = fooProxy.getOneToManySetField();
final int listCount = setField.size();
assertContains(setField, barProxy);
- setField.remove(barProxy);
+ setField.remove(context.edit(barProxy));
assertNotContains(setField, barProxy);
updReq.fire(new Receiver<SimpleFooProxy>() {
@Override
@@ -1962,6 +1921,80 @@
}
/**
+ * Check if a graph of unpersisted objects can be echoed.
+ */
+ public void testUnpersistedEchoComplexGraph() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
+ EntityProxyChange.registerForProxyType(req.getEventBus(),
+ SimpleFooProxy.class, handler);
+ SimpleFooRequest context = req.simpleFooRequest();
+ final SimpleBarProxy simpleBar = context.create(SimpleBarProxy.class);
+ simpleBar.setUnpersisted(true);
+ final SimpleFooProxy simpleFoo = context.create(SimpleFooProxy.class);
+ simpleFoo.setUnpersisted(true);
+ simpleFoo.setBarField(simpleBar);
+ context.echoComplex(simpleFoo, simpleBar).with("barField").fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ assertEquals(0, handler.totalEventCount);
+ checkStableIdEquals(simpleFoo, response);
+ SimpleBarProxy responseBar = response.getBarField();
+ assertNotNull(responseBar);
+ checkStableIdEquals(simpleBar, responseBar);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Check if an unpersisted object can be echoed.
+ */
+ public void testUnpersistedEchoObject() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ final SimpleFooEventHandler<SimpleFooProxy> handler = new SimpleFooEventHandler<SimpleFooProxy>();
+ EntityProxyChange.registerForProxyType(req.getEventBus(),
+ SimpleFooProxy.class, handler);
+ SimpleFooRequest context = req.simpleFooRequest();
+ final SimpleFooProxy simpleFoo = context.create(SimpleFooProxy.class);
+ simpleFoo.setUnpersisted(true);
+ context.echo(simpleFoo).fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ assertEquals(0, handler.totalEventCount);
+ checkStableIdEquals(simpleFoo, response);
+ finishTestAndReset();
+ }
+ });
+ }
+
+ /**
+ * Return an unpersisted object from a service method and echo it.
+ */
+ public void testUnpersistedObjectFetch() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ req.simpleFooRequest().getUnpersistedInstance().fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(final SimpleFooProxy created) {
+ assertNotNull(created);
+ assertTrue(created.getUnpersisted());
+ req.simpleFooRequest().echo(created).fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ assertNotNull(response);
+ assertEquals(created.stableId(), response.stableId());
+ assertTrue(response.getUnpersisted());
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ /**
* This is analagous to FindServiceTest.testFetchDeletedEntity() only we're
* trying to invoke a method on the deleted entity using a stale EntityProxy
* reference on the client.
@@ -2019,6 +2052,228 @@
});
}
+ public void testValueObjectCreateSetRetrieveUpdate() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleFooRequest req = simpleFooRequest();
+ req.findSimpleFooById(1L).fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ SimpleFooRequest req = simpleFooRequest();
+
+ // Create
+ final SimpleValueProxy created = req.create(SimpleValueProxy.class);
+ created.setNumber(42);
+ created.setString("Hello world!");
+ created.setSimpleFoo(response);
+ // Test cycles in value
+ created.setSimpleValue(Arrays.asList(created));
+
+ // Set
+ response = req.edit(response);
+ response.setSimpleValue(created);
+
+ // Retrieve
+ req.persistAndReturnSelf().using(response).with(
+ "simpleValue.simpleFoo", "simpleValue.simpleValue").to(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ SimpleValueProxy value = response.getSimpleValue();
+ assertEquals(42, value.getNumber());
+ assertEquals("Hello world!", value.getString());
+ assertSame(response, value.getSimpleFoo());
+ assertSame(value, value.getSimpleValue().get(0));
+
+ try {
+ // Require owning object to be editable
+ response.getSimpleValue().setNumber(43);
+ fail("Should have thrown exception");
+ } catch (IllegalStateException expected) {
+ }
+
+ // Update
+ SimpleFooRequest req = simpleFooRequest();
+ response = req.edit(response);
+ response.getSimpleValue().setNumber(43);
+ req.persistAndReturnSelf().using(response).with("simpleValue").to(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ assertEquals(43, response.getSimpleValue().getNumber());
+ finishTestAndReset();
+ }
+ }).fire();
+ }
+ }).fire();
+ }
+ });
+ }
+
+ public void testValueObjectCreateSetRetrieveUpdateViaList() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleFooRequest req = simpleFooRequest();
+ req.findSimpleFooById(1L).fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ SimpleFooRequest req = simpleFooRequest();
+
+ // Create
+ final SimpleValueProxy created = req.create(SimpleValueProxy.class);
+ created.setNumber(42);
+ created.setString("Hello world!");
+ created.setSimpleFoo(response);
+
+ // Set
+ response = req.edit(response);
+ response.setSimpleValues(Arrays.asList(created));
+
+ // Retrieve
+ req.persistAndReturnSelf().using(response).with("simpleValues").to(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ SimpleValueProxy value = response.getSimpleValues().get(0);
+ assertEquals(42, value.getNumber());
+
+ try {
+ // Require owning object to be editable
+ response.getSimpleValues().get(0).setNumber(43);
+ fail("Should have thrown exception");
+ } catch (IllegalStateException expected) {
+ }
+
+ // Update
+ SimpleFooRequest req = simpleFooRequest();
+ response = req.edit(response);
+ response.getSimpleValues().get(0).setNumber(43);
+ req.persistAndReturnSelf().using(response).with("simpleValues").to(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ assertEquals(43,
+ response.getSimpleValues().get(0).getNumber());
+ finishTestAndReset();
+ }
+ }).fire();
+ }
+ }).fire();
+ }
+ });
+ }
+
+ public void testValueObjectEquality() {
+ SimpleFooRequest req = simpleFooRequest();
+ SimpleValueProxy a = req.create(SimpleValueProxy.class);
+ SimpleValueProxy b = req.create(SimpleValueProxy.class);
+ checkEqualityAndHashcode(a, b);
+
+ a.setString("Hello");
+ assertFalse(a.equals(b));
+ assertFalse(b.equals(a));
+
+ b.setString("Hello");
+ checkEqualityAndHashcode(a, b);
+ }
+
+ public void testValueObjectViolationsOnCreate() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleFooRequest req = simpleFooRequest();
+ final SimpleValueProxy value = req.create(SimpleValueProxy.class);
+ value.setShouldBeNull("Hello world");
+
+ SimpleFooProxy foo = req.create(SimpleFooProxy.class);
+ foo.setSimpleValue(value);
+ req.echo(foo).fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ fail();
+ }
+
+ @Override
+ public void onViolation(Set<Violation> errors) {
+ assertEquals(1, errors.size());
+ Violation v = errors.iterator().next();
+ assertEquals(value, v.getInvalidProxy());
+ assertNull(v.getOriginalProxy());
+ assertEquals("shouldBeNull", v.getPath());
+ assertNull(v.getProxyId());
+ finishTestAndReset();
+ }
+ });
+ }
+
+ public void testValueObjectViolationsOnEdit() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().returnValueProxy().fire(
+ new Receiver<SimpleValueProxy>() {
+ @Override
+ public void onSuccess(final SimpleValueProxy response) {
+ SimpleFooRequest req = simpleFooRequest();
+ final SimpleValueProxy value = req.edit(response);
+ value.setShouldBeNull("Hello world");
+ SimpleFooProxy foo = req.create(SimpleFooProxy.class);
+ foo.setSimpleValue(value);
+ req.echo(foo).fire(new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response) {
+ fail();
+ }
+
+ @Override
+ public void onViolation(Set<Violation> errors) {
+ assertEquals(1, errors.size());
+ Violation v = errors.iterator().next();
+ assertEquals(value, v.getInvalidProxy());
+ assertEquals(response, v.getOriginalProxy());
+ assertEquals("shouldBeNull", v.getPath());
+ assertNull(v.getProxyId());
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
+ /**
+ * Since a ValueProxy cannot be passed into RequestContext edit, a proxy
+ * returned from a service method should be mutable by default.
+ */
+ public void testValueObjectReturnedFromRequestIsImmutable() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ simpleFooRequest().returnValueProxy().fire(
+ new Receiver<SimpleValueProxy>() {
+ @Override
+ public void onSuccess(SimpleValueProxy a) {
+ try {
+ a.setNumber(77);
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ // Ensure Dates comply with ValueProxy mutation behaviors
+ a.getDate().setTime(1);
+ fail();
+ } catch (IllegalStateException expected) {
+ }
+ SimpleFooRequest ctx = simpleFooRequest();
+ final SimpleValueProxy toCheck = ctx.edit(a);
+ toCheck.setNumber(77);
+ toCheck.getDate().setTime(1);
+ ctx.returnValueProxy().fire(new Receiver<SimpleValueProxy>() {
+ @Override
+ public void onSuccess(SimpleValueProxy b) {
+ b = simpleFooRequest().edit(b);
+ // Now check that same value is equal across contexts
+ b.setNumber(77);
+ b.setDate(new Date(1));
+ checkEqualityAndHashcode(toCheck, b);
+ finishTestAndReset();
+ }
+ });
+ }
+ });
+ }
+
public void testViolationAbsent() {
delayTestFinish(DELAY_TEST_FINISH);
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTestBase.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTestBase.java
index c162c63..ef88d8b 100644
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTestBase.java
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTestBase.java
@@ -69,6 +69,14 @@
eventBus = req.getEventBus();
}
+ protected void checkEqualityAndHashcode(Object a, Object b) {
+ assertNotNull(a);
+ assertNotNull(b);
+ assertEquals(a.hashCode(), b.hashCode());
+ assertEquals(a, b);
+ assertEquals(b, a);
+ }
+
protected void checkStableIdEquals(EntityProxy expected, EntityProxy actual) {
assertEquals(expected.stableId(), actual.stableId());
assertEquals(expected.stableId().hashCode(), actual.stableId().hashCode());
diff --git a/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java b/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
index 74d268a..3e160e5 100644
--- a/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
+++ b/user/test/com/google/gwt/requestfactory/rebind/model/RequestFactoryModelTest.java
@@ -34,7 +34,7 @@
import com.google.gwt.requestfactory.shared.RequestContext;
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.Service;
-import com.google.gwt.requestfactory.shared.impl.Property;
+import com.google.gwt.requestfactory.shared.ValueProxy;
import junit.framework.TestCase;
@@ -267,11 +267,11 @@
toReturn.addAll(Arrays.asList(new Resource[] {
new EmptyMockJavaResource(Iterable.class),
- new EmptyMockJavaResource(Property.class),
new EmptyMockJavaResource(EntityProxy.class),
new EmptyMockJavaResource(InstanceRequest.class),
new EmptyMockJavaResource(RequestFactory.class),
new EmptyMockJavaResource(Receiver.class),
+ new EmptyMockJavaResource(ValueProxy.class),
new RealJavaResource(Request.class),
new RealJavaResource(Service.class),
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
index 24739f7..46218c4 100644
--- a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
+++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
@@ -24,10 +24,12 @@
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.Service;
import com.google.gwt.requestfactory.shared.SimpleRequestFactory;
+import com.google.gwt.requestfactory.shared.ValueProxy;
import com.google.gwt.requestfactory.shared.impl.FindRequest;
import junit.framework.TestCase;
+import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -101,7 +103,6 @@
return 0;
}
}
-
@ProxyFor(DomainWithOverloads.class)
interface DomainWithOverloadsProxy extends EntityProxy {
void foo();
@@ -142,6 +143,13 @@
Request<Integer> doesNotExist(int a);
}
+ static class Value {
+ }
+
+ @ProxyFor(value = Value.class)
+ interface MyValueProxy extends ValueProxy {
+ }
+
RequestFactoryInterfaceValidator v;
/**
@@ -164,6 +172,51 @@
v.validateRequestContext(FindRequest.class.getName());
}
+ @ProxyFor(HasListDomain.class)
+ interface HasList extends EntityProxy {
+ List<ReachableOnlyThroughReturnedList> getList();
+
+ void setList(List<ReachableOnlyThroughParamaterList> list);
+ }
+
+ static class HasListDomain extends Domain {
+ List<Domain> getList() {
+ return null;
+ }
+
+ void setList(List<Domain> value) {
+ }
+
+ public String getId() {
+ return null;
+ }
+
+ public int getVersion() {
+ return 0;
+ }
+ }
+
+ @ProxyFor(Domain.class)
+ interface ReachableOnlyThroughReturnedList extends EntityProxy {
+ };
+ @ProxyFor(Domain.class)
+ interface ReachableOnlyThroughParamaterList extends EntityProxy {
+ };
+
+ /**
+ * Make sure that proxy types referenced through type parameters of method
+ * return types and paramaters types are examined.
+ */
+ public void testFollowingTypeParameters() {
+ v.validateEntityProxy(HasList.class.getName());
+ assertNotNull(v.getEntityProxyTypeName(HasListDomain.class.getName(),
+ HasList.class.getName()));
+ assertNotNull(v.getEntityProxyTypeName(Domain.class.getName(),
+ ReachableOnlyThroughParamaterList.class.getName()));
+ assertNotNull(v.getEntityProxyTypeName(Domain.class.getName(),
+ ReachableOnlyThroughReturnedList.class.getName()));
+ }
+
/**
* Ensure that the <clinit> methods don't interfere with validation.
*/
@@ -225,6 +278,11 @@
assertFalse(v.isPoisoned());
}
+ public void testValueType() {
+ v.validateValueProxy(MyValueProxy.class.getName());
+ assertFalse(v.isPoisoned());
+ }
+
@Override
protected void setUp() throws Exception {
Logger logger = Logger.getLogger("");
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleBar.java b/user/test/com/google/gwt/requestfactory/server/SimpleBar.java
index d75ff3b..d0a625c 100644
--- a/user/test/com/google/gwt/requestfactory/server/SimpleBar.java
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleBar.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -88,12 +88,7 @@
public static SimpleBar getSingleton() {
return findSimpleBar("1L");
}
-
- public static SimpleBar returnFirst(List<SimpleBar> list) {
- SimpleBar toReturn = list.get(0);
- return toReturn;
- }
-
+
public static void reset() {
resetImpl();
}
@@ -121,11 +116,17 @@
return instance;
}
+ public static SimpleBar returnFirst(List<SimpleBar> list) {
+ SimpleBar toReturn = list.get(0);
+ return toReturn;
+ }
+
Integer version = 1;
private String id = "999L";
private boolean findFails;
private boolean isNew = true;
+ private boolean unpersisted;
private String userName;
public SimpleBar() {
@@ -142,7 +143,11 @@
}
public String getId() {
- return id;
+ return unpersisted ? null : id;
+ }
+
+ public Boolean getUnpersisted() {
+ return unpersisted;
}
public String getUserName() {
@@ -150,7 +155,7 @@
}
public Integer getVersion() {
- return version;
+ return unpersisted ? null : version;
}
public void persist() {
@@ -175,6 +180,10 @@
this.id = id;
}
+ public void setUnpersisted(Boolean unpersisted) {
+ this.unpersisted = unpersisted;
+ }
+
public void setUserName(String userName) {
this.userName = userName;
}
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
index 417aef2..460621d 100644
--- a/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleFoo.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -151,6 +151,12 @@
return foo1;
}
+ public static SimpleFoo getUnpersistedInstance() {
+ SimpleFoo foo = new SimpleFoo();
+ foo.setUnpersisted(true);
+ return foo;
+ }
+
public static Boolean processBooleanList(List<Boolean> values) {
return values.get(0);
}
@@ -273,6 +279,14 @@
return null;
}
+ public static SimpleValue returnValueProxy() {
+ SimpleValue toReturn = new SimpleValue();
+ toReturn.setNumber(42);
+ toReturn.setString("Hello world!");
+ toReturn.setDate(new Date());
+ return toReturn;
+ }
+
@SuppressWarnings("unused")
private static Integer privateMethod() {
return 0;
@@ -321,6 +335,8 @@
private List<Integer> numberListField;
+ private SimpleValue simpleValueField;
+
/*
* isChanged is just a quick-and-dirty way to get version-ing for now.
* Currently, only set by setUserName and setIntId. TODO for later: Use a
@@ -328,6 +344,8 @@
*/
boolean isChanged;
+ private boolean unpersisted;
+
public SimpleFoo() {
intId = 42;
version = 1;
@@ -433,7 +451,7 @@
}
public Long getId() {
- return id;
+ return unpersisted ? null : id;
}
public Integer getIntId() {
@@ -486,12 +504,24 @@
return shortField;
}
+ public SimpleValue getSimpleValue() {
+ return simpleValueField;
+ }
+
+ public List<SimpleValue> getSimpleValues() {
+ return Arrays.asList(simpleValueField);
+ }
+
+ public boolean getUnpersisted() {
+ return unpersisted;
+ }
+
public String getUserName() {
return userName;
}
public Integer getVersion() {
- return version;
+ return unpersisted ? null : version;
}
public String hello(SimpleBar bar) {
@@ -665,6 +695,18 @@
this.shortField = shortField;
}
+ public void setSimpleValue(SimpleValue simpleValueField) {
+ this.simpleValueField = simpleValueField;
+ }
+
+ public void setSimpleValues(List<SimpleValue> simpleValueField) {
+ this.simpleValueField = simpleValueField.get(0);
+ }
+
+ public void setUnpersisted(Boolean unpersisted) {
+ this.unpersisted = unpersisted;
+ }
+
public void setUserName(String userName) {
if (!this.userName.equals(userName)) {
this.userName = userName;
@@ -686,7 +728,7 @@
/**
* Persist this entity and all child entities. This method can handle loops.
- *
+ *
* @param processed the entities that have been processed
*/
private void persistCascadingAndReturnSelfImpl(Set<SimpleFoo> processed) {
diff --git a/user/test/com/google/gwt/requestfactory/server/SimpleValue.java b/user/test/com/google/gwt/requestfactory/server/SimpleValue.java
new file mode 100644
index 0000000..57c2119
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/server/SimpleValue.java
@@ -0,0 +1,85 @@
+/*
+ * 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.server;
+
+import java.util.Date;
+import java.util.List;
+
+import javax.validation.constraints.Null;
+
+/**
+ * A domain object that is used to demonstrate value-object behaviors.
+ */
+public class SimpleValue {
+ private Date date;
+ private int number;
+ private SimpleFoo simpleFoo;
+ /**
+ * Constraint violation testing.
+ */
+ @Null
+ private String shouldBeNull;
+ private List<SimpleValue> simpleValue;
+ private String string;
+
+ public Date getDate() {
+ return date;
+ }
+
+ public int getNumber() {
+ return number;
+ }
+
+ public String getShouldBeNull() {
+ return shouldBeNull;
+ }
+
+ public SimpleFoo getSimpleFoo() {
+ return simpleFoo;
+ }
+
+ public List<SimpleValue> getSimpleValue() {
+ return simpleValue;
+ }
+
+ public String getString() {
+ return string;
+ }
+
+ public void setDate(Date date) {
+ this.date = date;
+ }
+
+ public void setShouldBeNull(String value) {
+ this.shouldBeNull = value;
+ }
+
+ public void setNumber(int number) {
+ this.number = number;
+ }
+
+ public void setSimpleFoo(SimpleFoo simpleFoo) {
+ this.simpleFoo = simpleFoo;
+ }
+
+ public void setSimpleValue(List<SimpleValue> simpleValue) {
+ this.simpleValue = simpleValue;
+ }
+
+ public void setString(String string) {
+ this.string = string;
+ }
+}
diff --git a/user/test/com/google/gwt/requestfactory/shared/BaseFooProxy.java b/user/test/com/google/gwt/requestfactory/shared/BaseFooProxy.java
index 2a40ca3..a628bf3 100644
--- a/user/test/com/google/gwt/requestfactory/shared/BaseFooProxy.java
+++ b/user/test/com/google/gwt/requestfactory/shared/BaseFooProxy.java
@@ -20,6 +20,7 @@
import java.util.Date;
import java.util.List;
import java.util.Set;
+
/**
* A simple proxy used for testing. Has an int field and date field. Add other
* data types as their support gets built in.
@@ -27,59 +28,57 @@
public interface BaseFooProxy extends EntityProxy {
SimpleBarProxy getBarField();
-
+
SimpleBarProxy getBarNullField();
-
+
BigDecimal getBigDecimalField();
-
+
BigInteger getBigIntField();
-
+
Boolean getBoolField();
-
+
Byte getByteField();
-
+
Character getCharField();
-
+
Date getCreated();
-
+
Double getDoubleField();
-
+
SimpleEnum getEnumField();
-
+
Float getFloatField();
-
+
Integer getIntId();
-
+
Long getLongField();
-
+
String getNullField();
-
- Boolean getOtherBoolField();
-
- Integer getPleaseCrash();
-
- String getPassword();
-
- Short getShortField();
-
- String getUserName();
-
- List<SimpleBarProxy> getOneToManyField();
-
- List<SimpleFooProxy> getSelfOneToManyField();
List<Integer> getNumberListField();
+ List<SimpleBarProxy> getOneToManyField();
+
Set<SimpleBarProxy> getOneToManySetField();
- void setOneToManyField(List<SimpleBarProxy> field);
+ Boolean getOtherBoolField();
- void setOneToManySetField(Set<SimpleBarProxy> field);
+ String getPassword();
- void setSelfOneToManyField(List<SimpleFooProxy> field);
+ Integer getPleaseCrash();
- void setNumberListField(List<Integer> field);
-
+ List<SimpleFooProxy> getSelfOneToManyField();
+
+ Short getShortField();
+
+ SimpleValueProxy getSimpleValue();
+
+ List<SimpleValueProxy> getSimpleValues();
+
+ Boolean getUnpersisted();
+
+ String getUserName();
+
void setBarField(SimpleBarProxy barField);
void setBarNullField(SimpleBarProxy barNullField);
@@ -99,21 +98,34 @@
void setDoubleField(Double d);
void setFloatField(Float f);
-
+
void setIntId(Integer intId);
void setLongField(Long longField);
void setNullField(String nullField);
+ void setNumberListField(List<Integer> field);
+
+ void setOneToManyField(List<SimpleBarProxy> field);
+
+ void setOneToManySetField(Set<SimpleBarProxy> field);
+
void setOtherBoolField(Boolean boolField);
void setPassword(String password);
void setPleaseCrash(Integer dummy);
+ void setSelfOneToManyField(List<SimpleFooProxy> field);
+
void setShortField(Short s);
+ void setSimpleValue(SimpleValueProxy value);
+
+ void setSimpleValues(List<SimpleValueProxy> value);
+
+ void setUnpersisted(Boolean unpersisted);
+
void setUserName(String userName);
-
}
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java b/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java
index 6c8c1ae..e3fd376 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleBarProxy.java
@@ -23,6 +23,8 @@
public interface SimpleBarProxy extends EntityProxy {
Boolean getFindFails();
+ Boolean getUnpersisted();
+
/*
* NB: The lack of a getId() here is intentional, to ensure that the system
* does not assume that the id property is available to the client.
@@ -32,6 +34,8 @@
void setFindFails(Boolean fails);
+ void setUnpersisted(Boolean b);
+
void setUserName(String userName);
EntityProxyId<SimpleBarProxy> stableId();
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java b/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
index 9ae1831..a166f9e 100644
--- a/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleFooRequest.java
@@ -51,6 +51,8 @@
Request<SimpleFooProxy> getTripletReference();
+ Request<SimpleFooProxy> getUnpersistedInstance();
+
InstanceRequest<SimpleFooProxy, String> hello(SimpleBarProxy proxy);
InstanceRequest<SimpleFooProxy, Void> persist();
@@ -92,5 +94,7 @@
Request<String> returnNullString();
+ Request<SimpleValueProxy> returnValueProxy();
+
InstanceRequest<SimpleFooProxy, Integer> sum(List<Integer> values);
}
diff --git a/user/test/com/google/gwt/requestfactory/shared/SimpleValueProxy.java b/user/test/com/google/gwt/requestfactory/shared/SimpleValueProxy.java
new file mode 100644
index 0000000..67b6d43
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/shared/SimpleValueProxy.java
@@ -0,0 +1,51 @@
+/*
+ * 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.requestfactory.server.SimpleValue;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * A proxy for a non-addressable value object.
+ */
+@ProxyFor(value = SimpleValue.class)
+public interface SimpleValueProxy extends ValueProxy {
+ Date getDate();
+
+ int getNumber();
+
+ String getShouldBeNull();
+
+ SimpleFooProxy getSimpleFoo();
+
+ List<SimpleValueProxy> getSimpleValue();
+
+ String getString();
+
+ void setDate(Date value);
+
+ void setNumber(int value);
+
+ void setShouldBeNull(String value);
+
+ void setSimpleFoo(SimpleFooProxy value);
+
+ void setSimpleValue(List<SimpleValueProxy> value);
+
+ void setString(String value);
+}