Allow EntityProxy, ValueProxy, or any simple value type to be used as an entity's id and version values.
Allow RequestFactory to work with inner classes to make self-contained tests easier to write.
Resolves issue 5368.
Patch by: bobv
Review by: rchandia, rjrjr
Review at http://gwt-code-reviews.appspot.com/1127801
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9261 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
index 6d0877e..fec733f 100644
--- a/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
+++ b/user/src/com/google/gwt/autobean/rebind/AutoBeanFactoryGenerator.java
@@ -108,7 +108,7 @@
StringBuilder parameters = new StringBuilder();
for (JParameter param : jmethod.getParameters()) {
parameters.append(",").append(
- ModelUtils.getQualifiedBaseName(param.getType())).append(" ").append(
+ ModelUtils.getQualifiedBaseSourceName(param.getType())).append(" ").append(
param.getName());
}
if (parameters.length() > 0) {
@@ -119,12 +119,12 @@
if (jmethod.getThrows().length > 0) {
for (JType thrown : jmethod.getThrows()) {
throwsDeclaration.append(". ").append(
- ModelUtils.getQualifiedBaseName(thrown));
+ ModelUtils.getQualifiedBaseSourceName(thrown));
}
throwsDeclaration.deleteCharAt(0);
throwsDeclaration.insert(0, "throws ");
}
- String returnName = ModelUtils.getQualifiedBaseName(jmethod.getReturnType());
+ String returnName = ModelUtils.getQualifiedBaseSourceName(jmethod.getReturnType());
assert !returnName.contains("extends");
return String.format("%s %s(%s) %s", returnName, jmethod.getName(),
parameters, throwsDeclaration);
@@ -248,7 +248,7 @@
} else {
// return (ReturnType) values.get(\"foo\");
sw.println("return (%s) values.get(\"%s\");",
- ModelUtils.getQualifiedBaseName(jmethod.getReturnType()),
+ ModelUtils.getQualifiedBaseSourceName(jmethod.getReturnType()),
method.getPropertyName());
}
}
@@ -482,7 +482,7 @@
// Foo toReturn=FooAutoBean.this.get("getFoo", getWrapped().getFoo());
sw.println(
"%s toReturn = %3$s.this.get(\"%2$s\", getWrapped().%2$s());",
- ModelUtils.getQualifiedBaseName(jmethod.getReturnType()),
+ ModelUtils.getQualifiedBaseSourceName(jmethod.getReturnType()),
methodName, type.getSimpleSourceName());
// Non-value types might need to be wrapped
diff --git a/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java b/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
index f3d6a87..7fc9f00 100644
--- a/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
+++ b/user/src/com/google/gwt/autobean/server/AutoBeanFactoryMagic.java
@@ -94,7 +94,7 @@
System.arraycopy(extraInterfaces, 0, intfs, 1, extraInterfaces.length);
}
- return intf.cast(Proxy.newProxyInstance(
- Thread.currentThread().getContextClassLoader(), intfs, handler));
+ return intf.cast(Proxy.newProxyInstance(intf.getClassLoader(), intfs,
+ handler));
}
}
diff --git a/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java b/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
index 3960203..9d228f4 100644
--- a/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
+++ b/user/src/com/google/gwt/autobean/shared/AutoBeanCodex.java
@@ -370,10 +370,9 @@
PropertyContext ctx) {
// Skip primitive types whose values are uninteresting.
Class<?> type = ctx.getType();
- if (value != null) {
- if (value.equals(ValueCodex.getUninitializedFieldValue(type))) {
- return false;
- }
+ Object blankValue = ValueCodex.getUninitializedFieldValue(type);
+ if (value == blankValue || value != null && value.equals(blankValue)) {
+ return false;
}
// Special handling for enums if we have an obfuscation map
diff --git a/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java b/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
index 5545cd1..583c49d 100644
--- a/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
+++ b/user/src/com/google/gwt/editor/rebind/model/ModelUtils.java
@@ -88,11 +88,20 @@
}
/**
+ * Given a JType, return the binary name of the class that is most proximately
+ * assignable to the type. This method will resolve type parameters as well as
+ * wildcard types.
+ */
+ public static String getQualifiedBaseBinaryName(JType type) {
+ return ensureBaseType(type).getErasedType().getQualifiedBinaryName();
+ }
+
+ /**
* Given a JType, return the source name of the class that is most proximately
* assignable to the type. This method will resolve type parameters as well as
* wildcard types.
*/
- public static String getQualifiedBaseName(JType type) {
+ public static String getQualifiedBaseSourceName(JType type) {
return ensureBaseType(type).getErasedType().getQualifiedSourceName();
}
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
index 916eee7..89c992a 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryGenerator.java
@@ -268,11 +268,11 @@
sw.indent();
for (EntityProxyModel type : model.getAllProxyModels()) {
// tokensToTypes.put("Foo", Foo.class);
- sw.println("tokensToTypes.put(\"%1$s\", %1$s.class);",
- type.getQualifiedSourceName());
+ sw.println("tokensToTypes.put(\"%s\", %s.class);",
+ type.getQualifiedBinaryName(), type.getQualifiedSourceName());
// typesToTokens.put(Foo.class, Foo);
- sw.println("typesToTokens.put(%1$s.class, \"%1$s\");",
- type.getQualifiedSourceName());
+ sw.println("typesToTokens.put(%s.class, \"%s\");",
+ type.getQualifiedSourceName(), type.getQualifiedBinaryName());
// fooProxyTypes.add(MyFooProxy.class);
sw.println("%s.add(%s.class);", type.getType().equals(Type.ENTITY)
? "entityProxyTypes" : "valueProxyTypes",
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 7676035..da85d8c 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/EntityProxyModel.java
@@ -23,14 +23,6 @@
*/
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,6 +48,10 @@
toReturn.proxyFor = value;
}
+ public void setQualifiedBinaryName(String qualifiedBinaryName) {
+ toReturn.qualifiedBinaryName = qualifiedBinaryName;
+ }
+
public void setQualifiedSourceName(String name) {
assert !name.contains(" ");
toReturn.qualifiedSourceName = name;
@@ -70,7 +66,16 @@
}
}
+ /**
+ * The kind of proxy. This is an enum in case more proxy types are defined in
+ * the future.
+ */
+ public enum Type {
+ ENTITY, VALUE
+ }
+
private Class<?> proxyFor;
+ private String qualifiedBinaryName;
private String qualifiedSourceName;
private List<RequestMethod> requestMethods;
private Type type;
@@ -82,6 +87,10 @@
return proxyFor;
}
+ public String getQualifiedBinaryName() {
+ return qualifiedBinaryName;
+ }
+
public String getQualifiedSourceName() {
return qualifiedSourceName;
}
@@ -101,5 +110,4 @@
public String toString() {
return qualifiedSourceName;
}
-
}
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 56d859a..2ea756b 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/model/RequestFactoryModel.java
@@ -209,7 +209,8 @@
EntityProxyModel.Builder builder = new EntityProxyModel.Builder();
peerBuilders.put(entityProxyType, builder);
- builder.setQualifiedSourceName(ModelUtils.getQualifiedBaseName(entityProxyType));
+ builder.setQualifiedBinaryName(ModelUtils.getQualifiedBaseBinaryName(entityProxyType));
+ builder.setQualifiedSourceName(ModelUtils.getQualifiedBaseSourceName(entityProxyType));
if (entityProxyInterface.isAssignableFrom(entityProxyType)) {
builder.setType(Type.ENTITY);
} else if (valueProxyInterface.isAssignableFrom(entityProxyType)) {
diff --git a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
index 6ffb4c2..b17acb4 100644
--- a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
@@ -29,9 +29,9 @@
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.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
@@ -86,11 +86,21 @@
public Object createDomainObject(Class<?> clazz) {
Throwable ex;
try {
- return clazz.newInstance();
+ Constructor<?> c = clazz.getConstructor();
+ c.setAccessible(true);
+ return c.newInstance();
} catch (InstantiationException e) {
return report("Could not create a new instance of the requested type");
+ } catch (NoSuchMethodException e) {
+ return report("The requested type is not default-instantiable");
+ } catch (InvocationTargetException e) {
+ return report(e);
} catch (IllegalAccessException e) {
ex = e;
+ } catch (SecurityException e) {
+ ex = e;
+ } catch (IllegalArgumentException e) {
+ ex = e;
}
return die(ex, "Could not create a new instance of domain type %s",
clazz.getCanonicalName());
@@ -138,16 +148,12 @@
clazz.getCanonicalName());
}
- public String getFlatId(EntitySource source, Object domainObject) {
- Object id = getProperty(domainObject, "id");
- if (id == null) {
- return null;
- }
- if (!isKeyType(id.getClass())) {
- die(null, "The type %s is not a valid key type",
- id.getClass().getCanonicalName());
- }
- return EntityCodex.encode(source, id).getPayload();
+ public Object getId(Object domainObject) {
+ return getProperty(domainObject, "id");
+ }
+
+ public Class<?> getIdType(Class<?> domainType) {
+ return getFind(domainType).getParameterTypes()[0];
}
public Object getProperty(Object domainObject, String property) {
@@ -155,6 +161,7 @@
try {
Method method = domainObject.getClass().getMethod(
"get" + capitalize(property));
+ method.setAccessible(true);
Object value = method.invoke(domainObject);
return value;
} catch (SecurityException e) {
@@ -192,22 +199,14 @@
return clazz.getName();
}
- public int getVersion(Object domainObject) {
- // TODO: Make version any value type
- Object version = getProperty(domainObject, "version");
- if (version == null) {
- return 0;
- }
- if (!(version instanceof Integer)) {
- die(null, "The getVersion() method on type %s did not return"
- + " int or Integer", domainObject.getClass().getCanonicalName());
- }
- return ((Integer) version).intValue();
+ public Object getVersion(Object domainObject) {
+ return getProperty(domainObject, "version");
}
- public Object invoke(Method domainMethod, Object[] args) {
+ public Object invoke(Method domainMethod, Object... args) {
Throwable ex;
try {
+ domainMethod.setAccessible(true);
if (Modifier.isStatic(domainMethod.getModifiers())) {
return domainMethod.invoke(null, args);
} else {
@@ -225,46 +224,19 @@
return die(ex, "Could not invoke method %s", domainMethod.getName());
}
- public Object loadDomainObject(EntitySource source, Class<?> clazz,
- String flatId) {
- String searchFor = "find" + clazz.getSimpleName();
- Method found = null;
- for (Method method : clazz.getMethods()) {
- if (!Modifier.isStatic(method.getModifiers())) {
- continue;
- }
- if (!searchFor.equals(method.getName())) {
- continue;
- }
- if (method.getParameterTypes().length != 1) {
- continue;
- }
- if (!isKeyType(method.getParameterTypes()[0])) {
- continue;
- }
- found = method;
- break;
- }
- if (found == null) {
- die(null, "Could not find static method %s with a single"
- + " parameter of a key type", searchFor);
- }
- Object id = EntityCodex.decode(source, found.getParameterTypes()[0], null,
- flatId);
+ /**
+ * This implementation attempts to re-load the object from the backing store.
+ */
+ public boolean isLive(EntitySource source, Object domainObject) {
+ Object id = getId(domainObject);
+ return invoke(getFind(domainObject.getClass()), id) != null;
+ }
+
+ public Object loadDomainObject(EntitySource source, Class<?> clazz, Object id) {
if (id == null) {
- report("Cannot load a domain object with a null id");
+ die(null, "Cannot invoke find method with a null id");
}
- Throwable ex;
- try {
- return found.invoke(null, id);
- } catch (IllegalArgumentException e) {
- ex = e;
- } catch (IllegalAccessException e) {
- ex = e;
- } catch (InvocationTargetException e) {
- return report(e);
- }
- return die(ex, "Cauld not load domain object using id", id.toString());
+ return invoke(getFind(clazz), id);
}
public Class<? extends BaseProxy> resolveClass(String typeToken) {
@@ -288,14 +260,6 @@
public Method resolveDomainMethod(Method requestContextMethod) {
Class<?> enclosing = requestContextMethod.getDeclaringClass();
- synchronized (validator) {
- validator.antidote();
- validator.validateRequestContext(enclosing.getName());
- if (validator.isPoisoned()) {
- die(null, "The type %s did not pass RequestFactory validation",
- enclosing.getCanonicalName());
- }
- }
Class<?> searchIn = null;
Service s = enclosing.getAnnotation(Service.class);
@@ -340,6 +304,14 @@
public Method resolveRequestContextMethod(String requestContextClass,
String methodName) {
+ synchronized (validator) {
+ validator.antidote();
+ validator.validateRequestContext(requestContextClass);
+ if (validator.isPoisoned()) {
+ die(null, "The RequestContext type %s did not pass validation",
+ requestContextClass);
+ }
+ }
Class<?> searchIn = forName(requestContextClass);
for (Method method : searchIn.getMethods()) {
if (method.getName().equals(methodName)) {
@@ -352,12 +324,13 @@
public void setProperty(Object domainObject, String property,
Class<?> expectedType, Object value) {
- Method getId;
+ Method setter;
Throwable ex;
try {
- getId = domainObject.getClass().getMethod("set" + capitalize(property),
+ setter = domainObject.getClass().getMethod("set" + capitalize(property),
expectedType);
- getId.invoke(domainObject, value);
+ setter.setAccessible(true);
+ setter.invoke(domainObject, value);
return;
} catch (SecurityException e) {
ex = e;
@@ -395,6 +368,10 @@
throw new UnexpectedException(msg, e);
}
+ /**
+ * Call {@link Class#forName(String)} and report any errors through
+ * {@link #die()}.
+ */
private Class<?> forName(String name) {
try {
return Class.forName(name, false,
@@ -404,9 +381,41 @@
}
}
- private boolean isKeyType(Class<?> clazz) {
- return ValueCodex.canDecode(clazz)
- || BaseProxy.class.isAssignableFrom(clazz);
+ private Method getFind(Class<?> clazz) {
+ if (clazz == null) {
+ return die(null, "Could not find static method with a single"
+ + " parameter of a key type");
+ }
+ String searchFor = "find" + clazz.getSimpleName();
+ for (Method method : clazz.getMethods()) {
+ if (!Modifier.isStatic(method.getModifiers())) {
+ continue;
+ }
+ if (!searchFor.equals(method.getName())) {
+ continue;
+ }
+ if (method.getParameterTypes().length != 1) {
+ continue;
+ }
+ if (!isKeyType(method.getParameterTypes()[0])) {
+ continue;
+ }
+ return method;
+ }
+ return getFind(clazz.getSuperclass());
+ }
+
+ /**
+ * Returns <code>true</code> if the given class can be used as an id or
+ * version key.
+ */
+ private boolean isKeyType(Class<?> domainClass) {
+ if (ValueCodex.canDecode(domainClass)) {
+ return true;
+ }
+
+ return BaseProxy.class.isAssignableFrom(getClientType(domainClass,
+ BaseProxy.class));
}
/**
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
index ed21fc4..e65603a 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidator.java
@@ -140,7 +140,6 @@
private class DomainMapper extends EmptyVisitor {
private final ErrorContext logger;
private String domainInternalName;
- private boolean isValueType;
public DomainMapper(ErrorContext logger) {
this.logger = logger;
@@ -151,10 +150,6 @@
return domainInternalName;
}
- public boolean isValueType() {
- return isValueType;
- }
-
@Override
public void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
@@ -177,8 +172,6 @@
public void visit(String name, Object value) {
if ("value".equals(name)) {
domainInternalName = ((Type) value).getInternalName();
- } else if ("isValueType".equals(name)) {
- isValueType = (Boolean) value;
}
}
};
@@ -208,9 +201,6 @@
domainInternalName = desc.toString();
logger.spam(domainInternalName);
- } else if ("isValueType".equals(name)) {
- isValueType = (Boolean) value;
- logger.spam("isValueType: %s", isValueType);
}
}
};
@@ -569,6 +559,11 @@
*/
private final Set<Type> valueTypes = new HashSet<Type>();
+ /**
+ * Maps a domain object to the type returned from its getId method.
+ */
+ private final Map<Type, Type> unresolvedKeyTypes = new HashMap<Type, Type>();
+
public RequestFactoryInterfaceValidator(Logger logger, Loader loader) {
this.parentLogger = new ErrorContext(logger);
this.loader = loader;
@@ -702,6 +697,8 @@
checkClientMethodInDomain(logger, method, domainServiceType);
maybeCheckReferredProxies(logger, method);
}
+
+ checkUnresolvedKeyTypes(logger);
}
/**
@@ -887,18 +884,55 @@
return;
}
logger = logger.setType(domainType);
- Method getIdString = new Method("getId", "()Ljava/lang/String;");
- Method getIdLong = new Method("getId", "()Ljava/lang/Long;");
- if (findCompatibleMethod(logger, domainType, getIdString, false, true) == null
- && findCompatibleMethod(logger, domainType, getIdLong, false, true) == null) {
- logger.poison("Did not find a getId() method that"
- + " returns a String or a Long");
+ String findMethodName = "find"
+ + BinaryName.getShortClassName(domainType.getClassName());
+ Type keyType = null;
+ RFMethod findMethod = null;
+
+ boolean foundFind = false;
+ boolean foundId = false;
+ boolean foundVersion = false;
+ for (RFMethod method : getMethodsInHierarchy(logger, domainType)) {
+ if ("getId".equals(method.getName())
+ && method.getArgumentTypes().length == 0) {
+ foundId = true;
+ keyType = method.getReturnType();
+ if (!isResolvedKeyType(logger, keyType)) {
+ unresolvedKeyTypes.put(domainType, keyType);
+ }
+ } else if ("getVersion".equals(method.getName())
+ && method.getArgumentTypes().length == 0) {
+ foundVersion = true;
+ if (!isResolvedKeyType(logger, method.getReturnType())) {
+ unresolvedKeyTypes.put(domainType, method.getReturnType());
+ }
+ } else if (findMethodName.equals(method.getName())
+ && method.getArgumentTypes().length == 1) {
+ foundFind = true;
+ findMethod = method;
+ }
+ if (foundFind && foundId && foundVersion) {
+ break;
+ }
+ }
+ if (!foundId) {
+ logger.poison("There is no getId() method in type %s", print(domainType));
+ }
+ if (!foundVersion) {
+ logger.poison("There is no getVersion() method in type %s",
+ print(domainType));
}
- Method getVersion = new Method("getVersion", "()Ljava/lang/Integer;");
- if (findCompatibleMethod(logger, domainType, getVersion) == null) {
- logger.poison("Did not find a getVersion() method"
- + " that returns an Integer");
+ if (foundFind) {
+ if (keyType != null
+ && !isAssignable(logger, findMethod.getArgumentTypes()[0], keyType)) {
+ logger.poison("The key type returned by %s getId()"
+ + " cannot be used as the argument to %s(%s)", print(keyType),
+ findMethod.getName(), print(findMethod.getArgumentTypes()[0]));
+ }
+ } else {
+ logger.poison("There is no %s method in type %s that returns %2$s",
+ findMethodName, print(domainType));
}
}
@@ -914,6 +948,21 @@
createDomainMethod(logger, clientPropertyMethod));
}
+ private void checkUnresolvedKeyTypes(ErrorContext logger) {
+ unresolvedKeyTypes.values().removeAll(domainToClientType.keySet());
+ if (unresolvedKeyTypes.isEmpty()) {
+ return;
+ }
+
+ for (Map.Entry<Type, Type> type : unresolvedKeyTypes.entrySet()) {
+ logger.setType(type.getKey()).poison(
+ "The domain type %s uses a non-simple key type (%s)"
+ + " in its getId() or getVersion() method that"
+ + " does not have a proxy mapping.", print(type.getKey()),
+ print(type.getValue()));
+ }
+ }
+
/**
* Convert a method declaration using client types (e.g. FooProxy) to domain
* types (e.g. Foo).
@@ -1091,12 +1140,10 @@
domainType = errorType;
} else {
domainType = Type.getObjectType(pv.getDomainInternalName());
- if (pv.isValueType()) {
- valueTypes.add(clientType);
- }
}
}
addToDomainMap(logger, domainType, clientType);
+ maybeCheckProxyType(logger, clientType);
return domainType;
}
@@ -1218,6 +1265,22 @@
|| "java/util/Set".equals(type.getInternalName());
}
+ /**
+ * Keep in sync with {@code ReflectiveServiceLayer.isKeyType()}.
+ */
+ private boolean isResolvedKeyType(ErrorContext logger, Type type) {
+ if (isValueType(logger, type)) {
+ return true;
+ }
+
+ // We have already seen a mapping for the key type
+ if (domainToClientType.containsKey(type)) {
+ return true;
+ }
+
+ return false;
+ }
+
private boolean isValueType(ErrorContext logger, Type type) {
if (type.getSort() != Type.OBJECT) {
return true;
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestState.java b/user/src/com/google/gwt/requestfactory/server/RequestState.java
index 9dcccdc..5cb6c6c 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestState.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestState.java
@@ -17,6 +17,10 @@
import com.google.gwt.autobean.server.AutoBeanFactoryMagic;
import com.google.gwt.autobean.shared.AutoBean;
+import com.google.gwt.autobean.shared.AutoBeanCodex;
+import com.google.gwt.autobean.shared.Splittable;
+import com.google.gwt.autobean.shared.ValueCodex;
+import com.google.gwt.autobean.shared.impl.StringQuoter;
import com.google.gwt.requestfactory.server.SimpleRequestProcessor.IdToEntityMap;
import com.google.gwt.requestfactory.server.SimpleRequestProcessor.ServiceLayer;
import com.google.gwt.requestfactory.shared.BaseProxy;
@@ -24,10 +28,13 @@
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.MessageFactoryHolder;
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 com.google.gwt.requestfactory.shared.messages.IdMessage;
+import com.google.gwt.requestfactory.shared.messages.IdMessage.Strength;
+import java.util.Collections;
import java.util.IdentityHashMap;
import java.util.Map;
@@ -77,6 +84,31 @@
resolver = new Resolver(this);
}
+ public Splittable flatten(Object domainValue) {
+ Splittable flatValue;
+ if (ValueCodex.canDecode(domainValue.getClass())) {
+ flatValue = ValueCodex.encode(domainValue);
+ } else {
+ flatValue = new SimpleRequestProcessor(service).createOobMessage(Collections.singletonList(domainValue));
+ }
+ return flatValue;
+ }
+
+ public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(IdMessage idMessage) {
+ SimpleProxyId<Q> id;
+ if (idMessage.getSyntheticId() > 0) {
+ @SuppressWarnings("unchecked")
+ Class<Q> clazz = (Class<Q>) service.resolveClass(idMessage.getTypeToken());
+ id = idFactory.allocateSyntheticId(clazz, idMessage.getSyntheticId());
+ } else {
+ String decodedId = idMessage.getServerId() == null ? null
+ : SimpleRequestProcessor.fromBase64(idMessage.getServerId());
+ id = idFactory.getId(idMessage.getTypeToken(), decodedId,
+ idMessage.getClientId());
+ }
+ return getBeanForPayload(id);
+ }
+
public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
SimpleProxyId<Q> id, Object domainObject) {
@SuppressWarnings("unchecked")
@@ -91,18 +123,10 @@
* 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);
+ Splittable serializedProxyId) {
+ IdMessage idMessage = AutoBeanCodex.decode(MessageFactoryHolder.FACTORY,
+ IdMessage.class, serializedProxyId).as();
+ return getBeanForPayload(idMessage);
}
public IdFactory getIdFactory() {
@@ -118,20 +142,23 @@
* {@link IdFactory#getHistoryToken(SimpleProxyId)} except that it
* base64-encodes the server ids.
* <p>
- * XXX: Fix the above
+ * XXX: Merge this with AbstsractRequestContext's implementation
*/
- 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()));
+ public Splittable getSerializedProxyId(SimpleProxyId<?> stableId) {
+ AutoBean<IdMessage> bean = MessageFactoryHolder.FACTORY.id();
+ IdMessage ref = bean.as();
+ ref.setTypeToken(service.getTypeToken(stableId.getProxyClass()));
+ if (stableId.isSynthetic()) {
+ ref.setStrength(Strength.SYNTHETIC);
+ ref.setSyntheticId(stableId.getSyntheticId());
+ } else if (stableId.isEphemeral()) {
+ ref.setStrength(Strength.EPHEMERAL);
+ ref.setClientId(stableId.getClientId());
} else {
- return IdUtil.persistedId(
- SimpleRequestProcessor.toBase64(stableId.getServerId()),
- service.getTypeToken(stableId.getProxyClass()));
+ ref.setStrength(Strength.PERSISTED);
+ ref.setServerId(SimpleRequestProcessor.toBase64(stableId.getServerId()));
}
+ return AutoBeanCodex.encode(bean);
}
public ServiceLayer getServiceLayer() {
@@ -185,11 +212,19 @@
// Resolve the domain object
Class<?> domainClass = service.getDomainClass(id.getProxyClass());
Object domain;
- if (id.isEphemeral()) {
+ if (id.isEphemeral() || id.isSynthetic()) {
domain = service.createDomainObject(domainClass);
} else {
- String address = id.getServerId();
- domain = service.loadDomainObject(this, domainClass, address);
+ Splittable address = StringQuoter.split(id.getServerId());
+ Class<?> param = service.getIdType(domainClass);
+ Object domainParam;
+ if (ValueCodex.canDecode(param)) {
+ domainParam = ValueCodex.decode(param, address);
+ } else {
+ domainParam = new SimpleRequestProcessor(service).decodeOobMessage(
+ param, address).get(0);
+ }
+ domain = service.loadDomainObject(this, domainClass, domainParam);
}
domainObjectsToId.put(domain, id);
toReturn = createEntityProxyBean(id, domain);
diff --git a/user/src/com/google/gwt/requestfactory/server/Resolver.java b/user/src/com/google/gwt/requestfactory/server/Resolver.java
index 20abe2c..24a7fed 100644
--- a/user/src/com/google/gwt/requestfactory/server/Resolver.java
+++ b/user/src/com/google/gwt/requestfactory/server/Resolver.java
@@ -19,6 +19,7 @@
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.Splittable;
import com.google.gwt.autobean.shared.ValueCodex;
import com.google.gwt.requestfactory.server.SimpleRequestProcessor.ServiceLayer;
import com.google.gwt.requestfactory.shared.BaseProxy;
@@ -187,7 +188,8 @@
* @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
+ * should be assignable. A value of {@code null} indicates that any
+ * resolution will suffice.
* @param propertyRefs the property references requested by the client
*/
public Object resolveClientValue(Object domainValue, Type assignableTo,
@@ -266,22 +268,22 @@
boolean isEntityProxy = state.isEntityType(proxyType);
final boolean isOwnerValueProxy = state.isValueType(proxyType);
- int version;
+ Object domainVersion;
// 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;
+ Object domainId;
if (isEntityProxy) {
// Compute data needed to return id to the client
- address = service.getFlatId(state, domainEntity);
- version = service.getVersion(domainEntity);
+ domainId = service.getId(domainEntity);
+ domainVersion = service.getVersion(domainEntity);
} else {
- address = null;
- version = 0;
+ domainId = null;
+ domainVersion = null;
}
if (id == null) {
- if (address == null) {
+ if (domainId == 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
@@ -293,24 +295,32 @@
id = state.getIdFactory().allocateSyntheticId(proxyType,
++syntheticId);
} else {
- id = state.getIdFactory().getId(proxyType, address, null);
+ Splittable flatValue = state.flatten(domainId);
+ id = state.getIdFactory().getId(proxyType, flatValue.getPayload(), 0);
}
- } else {
- id.setServerId(address);
+ } else if (domainId != null) {
+ // Mark an ephemeral id as having been persisted
+ Splittable flatValue = state.flatten(domainId);
+ id.setServerId(flatValue.getPayload());
}
} else if (isEntityProxy) {
// Already have the id, just pull the current version
- version = service.getVersion(domainEntity);
+ domainVersion = service.getVersion(domainEntity);
} else {
- // The version of a value object is always 0
- version = 0;
+ // The version of a value object is always null
+ domainVersion = null;
}
@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);
+ if (domainVersion != null) {
+ Splittable flatVersion = state.flatten(domainVersion);
+ bean.setTag(Constants.VERSION_PROPERTY_B64,
+ SimpleRequestProcessor.toBase64(flatVersion.getPayload()));
+ }
+
bean.accept(new AutoBeanVisitor() {
@Override
@@ -376,6 +386,11 @@
return null;
}
+ boolean anyType = returnType == null;
+ if (anyType) {
+ returnType = Object.class;
+ }
+
Class<?> assignableTo = TypeUtils.ensureBaseType(returnType);
ResolutionKey key = new ResolutionKey(domainValue, returnType);
@@ -387,6 +402,10 @@
Class<?> returnClass = service.getClientType(domainValue.getClass(),
assignableTo);
+ if (anyType) {
+ assignableTo = returnClass;
+ }
+
// Pass simple values through
if (ValueCodex.canDecode(returnClass)) {
return assignableTo.cast(domainValue);
@@ -396,8 +415,9 @@
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);
+ Class<? extends BaseProxy> proxyClass = assignableTo.asSubclass(BaseProxy.class);
+ BaseProxy entity = resolveClientProxy(domainValue, proxyClass,
+ propertyRefs, key, prefix);
if (isId) {
return assignableTo.cast(((EntityProxy) entity).stableId());
}
diff --git a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
index 8637cf6..03c830a 100644
--- a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
@@ -37,7 +37,6 @@
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;
import com.google.gwt.requestfactory.shared.messages.InvocationMessage;
import com.google.gwt.requestfactory.shared.messages.MessageFactory;
import com.google.gwt.requestfactory.shared.messages.OperationMessage;
@@ -84,7 +83,13 @@
* May return {@code null} to indicate that the domain object has not been
* persisted.
*/
- String getFlatId(EntitySource source, Object domainObject);
+ Object getId(Object domainObject);
+
+ /**
+ * Returns the type of object the domain type's {@code findFoo()} expects to
+ * receive.
+ */
+ Class<?> getIdType(Class<?> domainType);
Object getProperty(Object domainObject, String property);
@@ -96,11 +101,21 @@
String getTypeToken(Class<?> domainClass);
- int getVersion(Object domainObject);
+ /**
+ * May return {@code null} to indicate that the domain object has not been
+ * persisted.
+ */
+ Object getVersion(Object domainObject);
- Object invoke(Method domainMethod, Object[] args);
+ Object invoke(Method domainMethod, Object... args);
- Object loadDomainObject(EntitySource source, Class<?> clazz, String flatId);
+ /**
+ * Returns {@code true} if the given domain object is still live (i.e. not
+ * deleted) in the backing store.
+ */
+ boolean isLive(EntitySource source, Object domainObject);
+
+ Object loadDomainObject(EntitySource source, Class<?> clazz, Object domainId);
Class<? extends BaseProxy> resolveClass(String typeToken);
@@ -154,7 +169,6 @@
}
private ExceptionHandler exceptionHandler = new DefaultExceptionHandler();
-
private final ServiceLayer service;
public SimpleRequestProcessor(ServiceLayer serviceLayer) {
@@ -182,6 +196,61 @@
}
/**
+ * Encode a list of objects into a self-contained message that can be used for
+ * out-of-band communication.
+ */
+ <T> Splittable createOobMessage(List<T> domainValues) {
+ RequestState state = new RequestState(service);
+
+ List<Splittable> encodedValues = new ArrayList<Splittable>(
+ domainValues.size());
+ for (T domainValue : domainValues) {
+ Object clientValue;
+ if (domainValue == null) {
+ clientValue = null;
+ } else {
+ Class<?> clientType = service.getClientType(domainValue.getClass(),
+ BaseProxy.class);
+ clientValue = state.getResolver().resolveClientValue(domainValue,
+ clientType, Collections.<String> emptySet());
+ }
+ encodedValues.add(EntityCodex.encode(state, clientValue));
+ }
+
+ IdToEntityMap map = new IdToEntityMap();
+ map.putAll(state.beans);
+ List<OperationMessage> operations = new ArrayList<OperationMessage>();
+ createReturnOperations(operations, state, map);
+
+ InvocationMessage invocation = FACTORY.invocation().as();
+ invocation.setParameters(encodedValues);
+
+ AutoBean<RequestMessage> bean = FACTORY.request();
+ RequestMessage resp = bean.as();
+ resp.setInvocations(Collections.singletonList(invocation));
+ resp.setOperations(operations);
+ return AutoBeanCodex.encode(bean);
+ }
+
+ /**
+ * Decode an out-of-band message.
+ */
+ <T> List<T> decodeOobMessage(Class<T> domainClass, Splittable payload) {
+ Class<?> proxyType = service.getClientType(domainClass, BaseProxy.class);
+ RequestState state = new RequestState(service);
+ RequestMessage message = AutoBeanCodex.decode(FACTORY,
+ RequestMessage.class, payload).as();
+ processOperationMessages(state, message);
+ List<Object> decoded = decodeInvocationArguments(state,
+ message.getInvocations().get(0).getParameters(),
+ new Class<?>[] {proxyType}, new Type[] {domainClass});
+
+ @SuppressWarnings("unchecked")
+ List<T> toReturn = (List<T>) decoded;
+ return toReturn;
+ }
+
+ /**
* Main processing method.
*/
void process(RequestMessage req, ResponseMessage resp) {
@@ -246,21 +315,24 @@
id.getProxyClass(), Collections.<String> emptySet());
}
- int version;
- if (id.isEphemeral() || id.isSynthetic()) {
+ if (id.isEphemeral() || id.isSynthetic() || domainObject == null) {
// 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) {
+ } else if (!service.isLive(returnState, domainObject)) {
writeOperation = WriteOperation.DELETE;
- version = 0;
} else if (id.wasEphemeral()) {
writeOperation = WriteOperation.PERSIST;
- version = service.getVersion(domainObject);
} else {
writeOperation = WriteOperation.UPDATE;
- version = service.getVersion(domainObject);
+ }
+
+ Splittable version = null;
+ if (writeOperation == WriteOperation.PERSIST
+ || writeOperation == WriteOperation.UPDATE) {
+ Object domainVersion = service.getVersion(domainObject);
+ if (domainVersion != null) {
+ version = returnState.flatten(domainVersion);
+ }
}
boolean inResponse = bean.getTag(Constants.IN_RESPONSE) != null;
@@ -271,8 +343,9 @@
* the domain version.
*/
if (WriteOperation.UPDATE.equals(writeOperation) && !inResponse) {
- if (Integer.valueOf(version).equals(
- bean.getTag(Constants.ENCODED_VERSION_PROPERTY))) {
+ String previousVersion = bean.<String> getTag(Constants.VERSION_PROPERTY_B64);
+ if (version != null && previousVersion != null
+ && version.equals(fromBase64(previousVersion))) {
continue;
}
}
@@ -310,44 +383,15 @@
op.setSyntheticId(id.getSyntheticId());
op.setTypeToken(service.getTypeToken(id.getProxyClass()));
- op.setVersion(version);
+ if (version != null) {
+ op.setVersion(toBase64(version.getPayload()));
+ }
operations.add(op);
}
}
/**
- * Handles instance invocations as the instance at the 0th parameter.
- */
- private List<Object> decodeInvocationArguments(RequestState source,
- InvocationMessage invocation, Class<?>[] contextArgs, Type[] genericArgs) {
- if (invocation.getParameters() == null) {
- return Collections.emptyList();
- }
-
- assert invocation.getParameters().size() == contextArgs.length;
- List<Object> args = new ArrayList<Object>(contextArgs.length);
- for (int i = 0, j = contextArgs.length; i < j; i++) {
- Class<?> type = contextArgs[i];
- Class<?> elementType = null;
- Splittable split;
- if (Collection.class.isAssignableFrom(type)) {
- elementType = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
- Collection.class, genericArgs[i]));
- split = invocation.getParameters().get(i);
- } else {
- split = invocation.getParameters().get(i);
- }
- Object arg = EntityCodex.decode(source, type, elementType, split);
- arg = source.getResolver().resolveDomainValue(arg,
- !EntityProxyId.class.equals(contextArgs[i]));
- args.add(arg);
- }
-
- return args;
- }
-
- /**
* Decode the arguments to pass into the domain method. If the domain method
* is not static, the instance object will be in the 0th position.
*/
@@ -370,8 +414,39 @@
System.arraycopy(contextMethod.getGenericParameterTypes(), 0, genericArgs,
offset, baseLength);
- List<Object> args = decodeInvocationArguments(source, invocation,
- contextArgs, genericArgs);
+ List<Object> args = decodeInvocationArguments(source,
+ invocation.getParameters(), contextArgs, genericArgs);
+ return args;
+ }
+
+ /**
+ * Handles instance invocations as the instance at the 0th parameter.
+ */
+ private List<Object> decodeInvocationArguments(RequestState source,
+ List<Splittable> parameters, Class<?>[] contextArgs, Type[] genericArgs) {
+ if (parameters == null) {
+ return Collections.emptyList();
+ }
+
+ assert parameters.size() == contextArgs.length;
+ List<Object> args = new ArrayList<Object>(contextArgs.length);
+ for (int i = 0, j = contextArgs.length; i < j; i++) {
+ Class<?> type = contextArgs[i];
+ Class<?> elementType = null;
+ Splittable split;
+ if (Collection.class.isAssignableFrom(type)) {
+ elementType = TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
+ Collection.class, genericArgs[i]));
+ split = parameters.get(i);
+ } else {
+ split = parameters.get(i);
+ }
+ Object arg = EntityCodex.decode(source, type, elementType, split);
+ arg = source.getResolver().resolveDomainValue(arg,
+ !EntityProxyId.class.equals(contextArgs[i]));
+ args.add(arg);
+ }
+
return args;
}
@@ -415,13 +490,11 @@
for (final OperationMessage operation : operations) {
// Unflatten properties
- String payloadId = operation.getOperation().equals(WriteOperation.PERSIST)
- ? IdUtil.ephemeralId(operation.getClientId(),
- operation.getTypeToken()) : IdUtil.persistedId(
- operation.getServerId(), operation.getTypeToken());
- AutoBean<? extends EntityProxy> bean = state.getBeanForPayload(payloadId);
+ AutoBean<? extends EntityProxy> bean = state.getBeanForPayload(operation);
// Use the version later to know which objects need to be sent back
- bean.setTag(Constants.ENCODED_VERSION_PROPERTY, operation.getVersion());
+ if (operation.getVersion() != null) {
+ bean.setTag(Constants.VERSION_PROPERTY_B64, operation.getVersion());
+ }
// Load the domain object with properties, if it exists
final Object domain = bean.getTag(Constants.DOMAIN_OBJECT);
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 1b3befb..8d90add 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestContext.java
@@ -38,7 +38,7 @@
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.IdMessage.Strength;
import com.google.gwt.requestfactory.shared.messages.InvocationMessage;
import com.google.gwt.requestfactory.shared.messages.MessageFactory;
import com.google.gwt.requestfactory.shared.messages.OperationMessage;
@@ -220,14 +220,11 @@
* EntityCodex support.
*/
public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
- String serializedProxyId) {
- SimpleProxyId<Q> id;
- if (IdUtil.isSynthetic(serializedProxyId)) {
- id = allocateSyntheticId(IdUtil.getTypeToken(serializedProxyId),
- IdUtil.getSyntheticId(serializedProxyId));
- } else {
- id = requestFactory.getBaseProxyId(serializedProxyId);
- }
+ Splittable serializedProxyId) {
+ IdMessage ref = AutoBeanCodex.decode(MessageFactoryHolder.FACTORY,
+ IdMessage.class, serializedProxyId).as();
+ @SuppressWarnings("unchecked")
+ SimpleProxyId<Q> id = (SimpleProxyId<Q>) getId(ref);
return getProxyForReturnPayloadGraph(id);
}
@@ -238,8 +235,21 @@
/**
* EntityCodex support.
*/
- public String getSerializedProxyId(SimpleProxyId<?> stableId) {
- return requestFactory.getHistoryToken(stableId);
+ public Splittable getSerializedProxyId(SimpleProxyId<?> stableId) {
+ AutoBean<IdMessage> bean = MessageFactoryHolder.FACTORY.id();
+ IdMessage ref = bean.as();
+ ref.setServerId(stableId.getServerId());
+ ref.setTypeToken(getRequestFactory().getTypeToken(stableId.getProxyClass()));
+ if (stableId.isSynthetic()) {
+ ref.setStrength(Strength.SYNTHETIC);
+ ref.setSyntheticId(stableId.getSyntheticId());
+ } else if (stableId.isEphemeral()) {
+ ref.setStrength(Strength.EPHEMERAL);
+ ref.setClientId(stableId.getClientId());
+ } else {
+ ref.setStrength(Strength.PERSISTED);
+ }
+ return AutoBeanCodex.encode(bean);
}
public boolean isChanged() {
@@ -514,11 +524,8 @@
if (op.getSyntheticId() > 0) {
return allocateSyntheticId(op.getTypeToken(), op.getSyntheticId());
}
- if (op.getClientId() > 0) {
- return requestFactory.getId(op.getTypeToken(), op.getServerId(),
- op.getClientId());
- }
- return requestFactory.getId(op.getTypeToken(), op.getServerId());
+ return requestFactory.getId(op.getTypeToken(), op.getServerId(),
+ op.getClientId());
}
/**
@@ -592,7 +599,7 @@
assert parent != null;
// Send our version number to the server to cut down on future payloads
- Integer version = currentView.getTag(Constants.ENCODED_VERSION_PROPERTY);
+ String version = currentView.getTag(Constants.VERSION_PROPERTY_B64);
if (version != null) {
operation.setVersion(version);
}
@@ -667,7 +674,7 @@
OperationMessage op, WriteOperation... operations) {
AutoBean<Q> toMutate = getProxyForReturnPayloadGraph(id);
- toMutate.setTag(Constants.ENCODED_VERSION_PROPERTY, op.getVersion());
+ toMutate.setTag(Constants.VERSION_PROPERTY_B64, op.getVersion());
final Map<String, Splittable> properties = op.getPropertyMap();
if (properties != null) {
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 48cfab3..b9c95ce 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/AbstractRequestFactory.java
@@ -26,7 +26,6 @@
import com.google.gwt.requestfactory.shared.Request;
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.RequestTransport;
-import com.google.gwt.requestfactory.shared.messages.IdUtil;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -42,10 +41,10 @@
private EventBus eventBus;
@SuppressWarnings("serial")
- private final Map<String, Integer> version = new LinkedHashMap<String, Integer>(
+ private final Map<String, String> version = new LinkedHashMap<String, String>(
16, 0.75f, true) {
@Override
- protected boolean removeEldestEntry(Entry<String, Integer> eldest) {
+ protected boolean removeEldestEntry(Entry<String, String> eldest) {
return size() > MAX_VERSION_ENTRIES;
}
};
@@ -135,9 +134,10 @@
* Used by {@link AbstractRequestContext} to quiesce update events for objects
* that haven't truly changed.
*/
- protected boolean hasVersionChanged(SimpleProxyId<?> id, int observedVersion) {
+ protected boolean hasVersionChanged(SimpleProxyId<?> id,
+ String observedVersion) {
String key = getHistoryToken(id);
- Integer existingVersion = version.get(key);
+ String existingVersion = version.get(key);
// Return true if we haven't seen this before or the versions differ
boolean toReturn = existingVersion == null
|| !existingVersion.equals(observedVersion);
@@ -146,5 +146,4 @@
}
return toReturn;
}
-
}
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 58f3543..5524e05 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/Constants.java
@@ -20,7 +20,7 @@
*/
public interface Constants {
String DOMAIN_OBJECT = "domainObject";
- String ENCODED_VERSION_PROPERTY = "version";
+ String VERSION_PROPERTY_B64 = "version";
String IN_RESPONSE = "inResponse";
String REQUEST_CONTEXT = "requestContext";
String STABLE_ID = "stableId";
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 2d9ac74..eaae080 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/IdFactory.java
@@ -18,7 +18,6 @@
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.messages.IdUtil;
import java.util.HashMap;
import java.util.Map;
@@ -62,7 +61,8 @@
@SuppressWarnings("unchecked")
public <P extends EntityProxy> Class<P> asEntityProxy(
Class<? extends BaseProxy> clazz) {
- assert isEntityType(clazz);
+ assert isEntityType(clazz) : clazz.getName()
+ + " is not an EntityProxy type";
return (Class<P>) clazz;
}
@@ -73,7 +73,7 @@
@SuppressWarnings("unchecked")
public <P extends ValueProxy> Class<P> asValueProxy(
Class<? extends BaseProxy> clazz) {
- assert isEntityType(clazz);
+ assert isValueType(clazz) : clazz.getName() + " is not a ValueProxy type";
return (Class<P>) clazz;
}
@@ -120,19 +120,11 @@
}
/**
- * Create or retrieve a SimpleProxyId.
- */
- public <P extends BaseProxy> SimpleProxyId<P> getId(Class<P> clazz,
- String serverId) {
- return getId(getTypeToken(clazz), serverId);
- }
-
- /**
* 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 BaseProxy> SimpleProxyId<P> getId(Class<P> clazz,
- String serverId, Integer clientId) {
+ String serverId, int clientId) {
return getId(getTypeToken(clazz), serverId, clientId);
}
@@ -141,7 +133,7 @@
*/
public <P extends BaseProxy> SimpleProxyId<P> getId(String typeToken,
String serverId) {
- return getId(typeToken, serverId, null);
+ return getId(typeToken, serverId, 0);
}
/**
@@ -150,12 +142,12 @@
* id.
*/
public <P extends BaseProxy> SimpleProxyId<P> getId(String typeToken,
- String serverId, Integer clientId) {
+ String serverId, int clientId) {
/*
* If there's a clientId, that probably means we've just created a brand-new
* EntityProxy or have just persisted something on the server.
*/
- if (clientId != null) {
+ if (clientId > 0) {
// Try a cache lookup for the ephemeral key
String ephemeralKey = IdUtil.ephemeralId(clientId, typeToken);
@SuppressWarnings("unchecked")
@@ -203,6 +195,7 @@
* case for read-dominated applications.
*/
Class<P> clazz = getTypeFromToken(typeToken);
+ assert clazz != null : "No class literal for " + typeToken;
return createId(clazz, serverId);
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/IdUtil.java b/user/src/com/google/gwt/requestfactory/shared/impl/IdUtil.java
similarity index 97%
rename from user/src/com/google/gwt/requestfactory/shared/messages/IdUtil.java
rename to user/src/com/google/gwt/requestfactory/shared/impl/IdUtil.java
index 5efd614..fd71302 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/IdUtil.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/IdUtil.java
@@ -13,12 +13,12 @@
* License for the specific language governing permissions and limitations under
* the License.
*/
-package com.google.gwt.requestfactory.shared.messages;
+package com.google.gwt.requestfactory.shared.impl;
/**
* Common functions for slicing and dicing EntityProxy ids.
*/
-public class IdUtil {
+class IdUtil {
private static final String ANY_SEPARATOR_PATTERN = "@[012]@";
private static final String EPHEMERAL_SEPARATOR = "@1@";
private static final String TOKEN_SEPARATOR = "@0@";
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java b/user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
index 069509d..a36a6ae 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/MessageFactoryHolder.java
@@ -21,6 +21,6 @@
/**
* This class has a super-source version with a client-only implementation.
*/
-interface MessageFactoryHolder {
+public interface MessageFactoryHolder {
MessageFactory FACTORY = AutoBeanFactoryMagic.create(MessageFactory.class);
}
diff --git a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java
index 943c970..dce3a15 100644
--- a/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java
+++ b/user/src/com/google/gwt/requestfactory/shared/impl/SimpleProxyId.java
@@ -16,7 +16,6 @@
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
@@ -82,8 +81,7 @@
*/
SimpleProxyId(Class<P> proxyClass, String encodedAddress) {
assert proxyClass != null;
- assert encodedAddress != null && !encodedAddress.contains("@")
- && !"null".equals(encodedAddress);
+ assert encodedAddress != null;
setServerId(encodedAddress);
clientId = NEVER_EPHEMERAL;
hashCode = encodedAddress.hashCode();
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 bcf5245..82de771 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/EntityCodex.java
@@ -41,9 +41,18 @@
* Abstracts the process by which EntityProxies are created.
*/
public interface EntitySource {
- <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(String serializedProxyId);
+ /**
+ * Expects an encoded
+ * {@link com.google.gwt.requestfactory.shared.messages.IdMessage}.
+ */
+ <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(
+ Splittable serializedIdMessage);
- String getSerializedProxyId(SimpleProxyId<?> stableId);
+ /**
+ * Should return an encoded
+ * {@link com.google.gwt.requestfactory.shared.messages.IdMessage}.
+ */
+ Splittable getSerializedProxyId(SimpleProxyId<?> stableId);
boolean isEntityType(Class<?> clazz);
@@ -95,7 +104,7 @@
if (source.isEntityType(type) || source.isValueType(type)
|| EntityProxyId.class.equals(type)) {
- return source.getBeanForPayload(split.asString()).as();
+ return source.getBeanForPayload(split).as();
}
// Fall back to values
@@ -119,6 +128,10 @@
return LazySplittable.NULL;
}
+ if (value instanceof Poser<?>) {
+ value = ((Poser<?>) value).getPosedValue();
+ }
+
if (value instanceof Iterable<?>) {
StringBuffer toReturn = new StringBuffer();
toReturn.append('[');
@@ -145,11 +158,7 @@
}
if (value instanceof SimpleProxyId<?>) {
- value = source.getSerializedProxyId((SimpleProxyId<?>) value);
- }
-
- if (value instanceof Poser<?>) {
- value = ((Poser<?>) value).getPosedValue();
+ return source.getSerializedProxyId((SimpleProxyId<?>) value);
}
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 7be22c8..2b8919b 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/IdMessage.java
@@ -21,9 +21,35 @@
* Used as a base type for messages that are about a particular id.
*/
public interface IdMessage {
+ /**
+ * Describes the longevity of the id.
+ */
+ public enum Strength {
+ /**
+ * The id is indefinitely persistent and can be freely reused by the client
+ * and the server.
+ */
+ @PropertyName("0")
+ PERSISTED,
+
+ /**
+ * The id is managed by the client and is generally unknown to the server.
+ */
+ @PropertyName("1")
+ EPHEMERAL,
+
+ /**
+ * The id not not managed by the client or server and is valid only for the
+ * duration of a single request or response.
+ */
+ @PropertyName("2")
+ SYNTHETIC;
+ }
+
String CLIENT_ID = "C";
String SERVER_ID = "S";
String TYPE_TOKEN = "T";
+ String STRENGTH = "R";
String SYNTHETIC_ID = "Y";
@PropertyName(CLIENT_ID)
@@ -32,6 +58,9 @@
@PropertyName(SERVER_ID)
String getServerId();
+ @PropertyName(STRENGTH)
+ Strength getStrength();
+
@PropertyName(SYNTHETIC_ID)
int getSyntheticId();
@@ -44,6 +73,9 @@
@PropertyName(SERVER_ID)
void setServerId(String value);
+ @PropertyName(STRENGTH)
+ void setStrength(Strength value);
+
@PropertyName(SYNTHETIC_ID)
void setSyntheticId(int value);
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 5742010..a6201f5 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/MessageFactory.java
@@ -24,6 +24,8 @@
public interface MessageFactory extends AutoBeanFactory {
AutoBean<ServerFailureMessage> failure();
+ AutoBean<IdMessage> id();
+
AutoBean<InvocationMessage> invocation();
AutoBean<OperationMessage> operation();
diff --git a/user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java b/user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java
index 6563ec1..e216e37 100644
--- a/user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java
+++ b/user/src/com/google/gwt/requestfactory/shared/messages/VersionedMessage.java
@@ -24,8 +24,8 @@
String VERSION = "V";
@PropertyName(VERSION)
- int getVersion();
+ String getVersion();
@PropertyName(VERSION)
- void setVersion(int version);
+ void setVersion(String version);
}
diff --git a/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java b/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
index 19ea814..342aaa3 100644
--- a/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
+++ b/user/test/com/google/gwt/requestfactory/RequestFactoryJreSuite.java
@@ -16,6 +16,7 @@
package com.google.gwt.requestfactory;
import com.google.gwt.requestfactory.rebind.model.RequestFactoryModelTest;
+import com.google.gwt.requestfactory.server.ComplexKeysJreTest;
import com.google.gwt.requestfactory.server.FindServiceJreTest;
import com.google.gwt.requestfactory.server.RequestFactoryInterfaceValidatorTest;
import com.google.gwt.requestfactory.server.RequestFactoryJreTest;
@@ -31,6 +32,7 @@
public static Test suite() {
TestSuite suite = new TestSuite(
"requestfactory package tests that require the JRE");
+ suite.addTestSuite(ComplexKeysJreTest.class);
suite.addTestSuite(FindServiceJreTest.class);
suite.addTestSuite(RequestFactoryJreTest.class);
suite.addTestSuite(SimpleEntityProxyIdTest.class);
diff --git a/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java b/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java
index 2efef63..9eff4c3 100644
--- a/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java
+++ b/user/test/com/google/gwt/requestfactory/RequestFactorySuite.java
@@ -21,6 +21,7 @@
import com.google.gwt.requestfactory.client.RequestFactoryPolymorphicTest;
import com.google.gwt.requestfactory.client.RequestFactoryTest;
import com.google.gwt.requestfactory.client.ui.EditorTest;
+import com.google.gwt.requestfactory.shared.ComplexKeysTest;
import junit.framework.Test;
@@ -31,11 +32,12 @@
public static Test suite() {
GWTTestSuite suite = new GWTTestSuite(
"Test suite for requestfactory gwt code.");
+ suite.addTestSuite(ComplexKeysTest.class);
suite.addTestSuite(EditorTest.class);
+ suite.addTestSuite(FindServiceTest.class);
suite.addTestSuite(RequestFactoryTest.class);
suite.addTestSuite(RequestFactoryExceptionHandlerTest.class);
suite.addTestSuite(RequestFactoryPolymorphicTest.class);
- suite.addTestSuite(FindServiceTest.class);
return suite;
}
}
diff --git a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
index b50b784..a52ab68 100644
--- a/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/RequestFactoryTest.java
@@ -172,13 +172,14 @@
private static final int DELAY_TEST_FINISH = 5000;
public <T extends EntityProxy> void assertContains(Collection<T> col, T value) {
+ EntityProxyId<?> lookFor = value.stableId();
for (T x : col) {
- if (x.stableId().equals(value.stableId())) {
+ EntityProxyId<?> found = x.stableId();
+ if (found.equals(lookFor)) {
return;
}
}
- assertTrue(
- ("Value " + value + " not found in collection ") + col.toString(),
+ assertTrue("Value " + value + " not found in collection " + col.toString(),
false);
}
diff --git a/user/test/com/google/gwt/requestfactory/server/ComplexKeysJreTest.java b/user/test/com/google/gwt/requestfactory/server/ComplexKeysJreTest.java
new file mode 100644
index 0000000..e7bcaeb
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/server/ComplexKeysJreTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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.requestfactory.shared.ComplexKeysTest;
+
+/**
+ * JRE version of ComplexKeysTest.
+ */
+public class ComplexKeysJreTest extends ComplexKeysTest {
+ @Override
+ public String getModuleName() {
+ return null;
+ }
+
+ @Override
+ protected Factory createFactory() {
+ return RequestFactoryJreTest.createInProcess(Factory.class);
+ }
+}
diff --git a/user/test/com/google/gwt/requestfactory/server/FindServiceJreTest.java b/user/test/com/google/gwt/requestfactory/server/FindServiceJreTest.java
index de8028b..1169ec7 100644
--- a/user/test/com/google/gwt/requestfactory/server/FindServiceJreTest.java
+++ b/user/test/com/google/gwt/requestfactory/server/FindServiceJreTest.java
@@ -30,6 +30,6 @@
@Override
protected SimpleRequestFactory createFactory() {
- return RequestFactoryJreTest.createInProcess();
+ return RequestFactoryJreTest.createInProcess(SimpleRequestFactory.class);
}
}
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
index 46218c4..d8eca03 100644
--- a/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
+++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryInterfaceValidatorTest.java
@@ -30,6 +30,7 @@
import junit.framework.TestCase;
import java.util.List;
+import java.util.Random;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -38,6 +39,10 @@
*/
public class RequestFactoryInterfaceValidatorTest extends TestCase {
static class ClinitEntity {
+ static ClinitEntity findClinitEntity(String key) {
+ return null;
+ }
+
static ClinitEntity request() {
return null;
}
@@ -111,6 +116,42 @@
class Foo {
}
+ @ProxyFor(HasListDomain.class)
+ interface HasList extends EntityProxy {
+ List<ReachableOnlyThroughReturnedList> getList();
+
+ void setList(List<ReachableOnlyThroughParamaterList> list);
+ }
+
+ static class HasListDomain extends Domain {
+ public String getId() {
+ return null;
+ }
+
+ public int getVersion() {
+ return 0;
+ }
+
+ List<Domain> getList() {
+ return null;
+ }
+
+ void setList(List<Domain> value) {
+ }
+ }
+
+ @ProxyFor(value = Value.class)
+ interface MyValueProxy extends ValueProxy {
+ }
+
+ @ProxyFor(Domain.class)
+ interface ReachableOnlyThroughParamaterList extends EntityProxy {
+ }
+
+ @ProxyFor(Domain.class)
+ interface ReachableOnlyThroughReturnedList extends EntityProxy {
+ }
+
interface RequestContextMissingAnnotation extends RequestContext {
}
@@ -125,7 +166,6 @@
interface ServiceRequestMismatchedParam extends RequestContext {
Request<Integer> foo(long a);
}
-
@Service(Domain.class)
interface ServiceRequestMismatchedReturn extends RequestContext {
Request<Long> foo(int a);
@@ -143,15 +183,27 @@
Request<Integer> doesNotExist(int a);
}
+ static class UnexpectedIdAndVersionDomain {
+ Random getId() {
+ return null;
+ }
+
+ Random getVersion() {
+ return null;
+ }
+ }
+
+ @ProxyFor(UnexpectedIdAndVersionDomain.class)
+ interface UnexpectedIdAndVersionProxy extends EntityProxy {
+ }
+
static class Value {
}
- @ProxyFor(value = Value.class)
- interface MyValueProxy extends ValueProxy {
- }
-
RequestFactoryInterfaceValidator v;
+ private static final boolean DUMP_PAYLOAD = Boolean.getBoolean("gwt.rpc.dumpPayload");;
+
/**
* Ensure that calling {@link RequestFactoryInterfaceValidator#antidote()}
* doesn't cause information to be lost.
@@ -163,7 +215,7 @@
assertFalse(v.isPoisoned());
v.validateRequestContext(RequestContextMissingAnnotation.class.getName());
assertTrue(v.isPoisoned());
- }
+ };
/**
* Test the {@link FindRequest} context used to implement find().
@@ -172,37 +224,6 @@
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.
@@ -278,6 +299,11 @@
assertFalse(v.isPoisoned());
}
+ public void testUnexpectedIdAndVersion() {
+ v.validateEntityProxy(UnexpectedIdAndVersionProxy.class.getName());
+ assertTrue(v.isPoisoned());
+ }
+
public void testValueType() {
v.validateValueProxy(MyValueProxy.class.getName());
assertFalse(v.isPoisoned());
@@ -286,7 +312,7 @@
@Override
protected void setUp() throws Exception {
Logger logger = Logger.getLogger("");
- logger.setLevel(Level.OFF);
+ logger.setLevel(DUMP_PAYLOAD ? Level.ALL : Level.OFF);
v = new RequestFactoryInterfaceValidator(logger, new ClassLoaderLoader(
Thread.currentThread().getContextClassLoader()));
}
diff --git a/user/test/com/google/gwt/requestfactory/server/RequestFactoryJreTest.java b/user/test/com/google/gwt/requestfactory/server/RequestFactoryJreTest.java
index d0d69b1..0e33457 100644
--- a/user/test/com/google/gwt/requestfactory/server/RequestFactoryJreTest.java
+++ b/user/test/com/google/gwt/requestfactory/server/RequestFactoryJreTest.java
@@ -21,6 +21,7 @@
import com.google.gwt.requestfactory.server.SimpleRequestProcessor.ServiceLayer;
import com.google.gwt.requestfactory.server.testing.InProcessRequestTransport;
import com.google.gwt.requestfactory.server.testing.RequestFactoryMagic;
+import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.SimpleRequestFactory;
/**
@@ -28,9 +29,9 @@
*/
public class RequestFactoryJreTest extends RequestFactoryTest {
- public static SimpleRequestFactory createInProcess() {
+ public static <T extends RequestFactory> T createInProcess(Class<T> clazz) {
EventBus eventBus = new SimpleEventBus();
- SimpleRequestFactory req = RequestFactoryMagic.create(SimpleRequestFactory.class);
+ T req = RequestFactoryMagic.create(clazz);
ServiceLayer serviceLayer = new ReflectiveServiceLayer();
SimpleRequestProcessor processor = new SimpleRequestProcessor(serviceLayer);
req.initialize(eventBus, new InProcessRequestTransport(processor));
@@ -44,6 +45,6 @@
@Override
protected SimpleRequestFactory createFactory() {
- return createInProcess();
+ return createInProcess(SimpleRequestFactory.class);
}
}
diff --git a/user/test/com/google/gwt/requestfactory/shared/ComplexKeysTest.java b/user/test/com/google/gwt/requestfactory/shared/ComplexKeysTest.java
new file mode 100644
index 0000000..60af61d
--- /dev/null
+++ b/user/test/com/google/gwt/requestfactory/shared/ComplexKeysTest.java
@@ -0,0 +1,223 @@
+/*
+ * 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.core.client.GWT;
+import com.google.gwt.event.shared.SimpleEventBus;
+import com.google.gwt.junit.client.GWTTestCase;
+
+/**
+ * Tests the use of non-trivial EntityProxy and ValueProxy key types.
+ */
+public class ComplexKeysTest extends GWTTestCase {
+
+ /**
+ * The factory being tested.
+ */
+ protected interface Factory extends RequestFactory {
+ Context context();
+ }
+
+ @Service(ContextImpl.class)
+ interface Context extends RequestContext {
+ Request<DomainWithEntityKeyProxy> createEntity(String key);
+
+ Request<DomainWithValueKeyProxy> createValue(String key);
+ }
+
+ static class ContextImpl {
+ public static DomainWithEntityKey createEntity(String key) {
+ return new DomainWithEntityKey(new EntityKey(key));
+ }
+
+ public static DomainWithValueKey createValue(String key) {
+ return new DomainWithValueKey(new ValueKey(key));
+ }
+ }
+
+ static class DomainWithEntityKey {
+
+ public static DomainWithEntityKey findDomainWithEntityKey(EntityKey key) {
+ return new DomainWithEntityKey(key);
+ }
+
+ private final EntityKey key;
+
+ public DomainWithEntityKey(EntityKey key) {
+ if (key == null) {
+ throw new IllegalArgumentException("Key key");
+ }
+ this.key = key;
+ }
+
+ public EntityKey getId() {
+ return key;
+ }
+
+ public Void getVersion() {
+ return null;
+ }
+ }
+
+ @ProxyFor(DomainWithEntityKey.class)
+ interface DomainWithEntityKeyProxy extends EntityProxy {
+ EntityKeyProxy getId();
+
+ EntityProxyId<DomainWithEntityKeyProxy> stableId();
+ }
+
+ static class DomainWithValueKey {
+ public static DomainWithValueKey create(String key) {
+ return new DomainWithValueKey(new ValueKey(key));
+ }
+
+ public static DomainWithValueKey findDomainWithValueKey(ValueKey key) {
+ return new DomainWithValueKey(key);
+ }
+
+ private final ValueKey key;
+
+ public DomainWithValueKey(ValueKey key) {
+ if (key == null) {
+ throw new IllegalArgumentException("Key key");
+ }
+ this.key = key;
+ }
+
+ public ValueKey getId() {
+ return key;
+ }
+
+ public Void getVersion() {
+ return null;
+ }
+ }
+
+ @ProxyFor(DomainWithValueKey.class)
+ interface DomainWithValueKeyProxy extends EntityProxy {
+ ValueKeyProxy getId();
+
+ EntityProxyId<DomainWithValueKeyProxy> stableId();
+ }
+
+ static class EntityKey {
+ public static EntityKey findEntityKey(String key) {
+ return new EntityKey(key);
+ }
+
+ private final String key;
+
+ public EntityKey(String key) {
+ assertEquals("key", key);
+ this.key = key;
+ }
+
+ public String getId() {
+ return key;
+ }
+
+ public Void getVersion() {
+ return null;
+ }
+ }
+
+ @ProxyFor(EntityKey.class)
+ interface EntityKeyProxy extends EntityProxy {
+ }
+
+ static class ValueKey {
+ private String key;
+
+ public ValueKey() {
+ }
+
+ public ValueKey(String key) {
+ setKey(key);
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public void setKey(String key) {
+ assertEquals("key", key);
+ this.key = key;
+ }
+ }
+
+ @ProxyFor(ValueKey.class)
+ interface ValueKeyProxy extends ValueProxy {
+ String getKey();
+ }
+
+ private static final int TEST_DELAY = 5000;
+ private Factory factory;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.requestfactory.RequestFactorySuite";
+ }
+
+ public void testEntityKey() {
+ delayTestFinish(TEST_DELAY);
+ context().createEntity("key").fire(
+ new Receiver<DomainWithEntityKeyProxy>() {
+ @Override
+ public void onSuccess(final DomainWithEntityKeyProxy response) {
+ factory.find(response.stableId()).fire(
+ new Receiver<DomainWithEntityKeyProxy>() {
+ @Override
+ public void onSuccess(DomainWithEntityKeyProxy found) {
+ assertEquals(response.stableId(), found.stableId());
+ finishTest();
+ }
+ });
+ }
+ });
+ }
+
+ public void testValueKey() {
+ delayTestFinish(TEST_DELAY);
+ context().createValue("key").fire(new Receiver<DomainWithValueKeyProxy>() {
+ @Override
+ public void onSuccess(final DomainWithValueKeyProxy response) {
+ factory.find(response.stableId()).fire(
+ new Receiver<DomainWithValueKeyProxy>() {
+ @Override
+ public void onSuccess(DomainWithValueKeyProxy found) {
+ assertEquals(response.stableId(), found.stableId());
+ finishTest();
+ }
+ });
+ }
+ });
+ }
+
+ protected Factory createFactory() {
+ Factory toReturn = GWT.create(Factory.class);
+ toReturn.initialize(new SimpleEventBus());
+ return toReturn;
+ }
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ factory = createFactory();
+ }
+
+ private Context context() {
+ return factory.context();
+ }
+}