Rework the way OperationMessages are processed to allow all domain objects to be loaded at once.
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/1135801
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9271 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
index 2c2da00..4f20c7b 100644
--- a/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/ReflectiveServiceLayer.java
@@ -26,7 +26,10 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -188,6 +191,23 @@
}
@Override
+ public List<Object> loadDomainObjects(List<Class<?>> classes,
+ List<Object> domainIds) {
+ if (classes.size() != domainIds.size()) {
+ die(null,
+ "Size mismatch in paramaters. classes.size() = %d domainIds.size=%d",
+ classes.size(), domainIds.size());
+ }
+ List<Object> toReturn = new ArrayList<Object>(classes.size());
+ Iterator<Class<?>> classIt = classes.iterator();
+ Iterator<Object> idIt = domainIds.iterator();
+ while (classIt.hasNext()) {
+ toReturn.add(getTop().loadDomainObject(classIt.next(), idIt.next()));
+ }
+ return toReturn;
+ }
+
+ @Override
public void setProperty(Object domainObject, String property,
Class<?> expectedType, Object value) {
Method setter;
diff --git a/user/src/com/google/gwt/requestfactory/server/RequestState.java b/user/src/com/google/gwt/requestfactory/server/RequestState.java
index 3682f1e..520e6d7 100644
--- a/user/src/com/google/gwt/requestfactory/server/RequestState.java
+++ b/user/src/com/google/gwt/requestfactory/server/RequestState.java
@@ -33,8 +33,11 @@
import com.google.gwt.requestfactory.shared.messages.IdMessage;
import com.google.gwt.requestfactory.shared.messages.IdMessage.Strength;
+import java.util.ArrayList;
import java.util.Collections;
import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.List;
import java.util.Map;
/**
@@ -83,6 +86,9 @@
resolver = new Resolver(this);
}
+ /**
+ * Turn a domain value into a wire format message.
+ */
public Splittable flatten(Object domainValue) {
Splittable flatValue;
if (ValueCodex.canDecode(domainValue.getClass())) {
@@ -93,27 +99,15 @@
return flatValue;
}
- public <Q extends BaseProxy> AutoBean<Q> getBeanForPayload(IdMessage idMessage) {
- SimpleProxyId<Q> id;
- if (Strength.SYNTHETIC.equals(idMessage.getStrength())) {
- @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);
- }
-
+ /**
+ * Get or create a BaseProxy AutoBean for the given id.
+ */
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);
+ toReturn = createProxyBean(id, domainObject);
}
return toReturn;
}
@@ -125,7 +119,33 @@
Splittable serializedProxyId) {
IdMessage idMessage = AutoBeanCodex.decode(MessageFactoryHolder.FACTORY,
IdMessage.class, serializedProxyId).as();
- return getBeanForPayload(idMessage);
+ @SuppressWarnings("unchecked")
+ AutoBean<Q> toReturn = (AutoBean<Q>) getBeansForPayload(
+ Collections.singletonList(idMessage)).get(0);
+ return toReturn;
+ }
+
+ /**
+ * Get or create BaseProxy AutoBeans for a list of id-bearing messages.
+ */
+ public List<AutoBean<? extends BaseProxy>> getBeansForPayload(
+ List<? extends IdMessage> idMessages) {
+ List<SimpleProxyId<?>> ids = new ArrayList<SimpleProxyId<?>>(
+ idMessages.size());
+ for (IdMessage idMessage : idMessages) {
+ SimpleProxyId<?> id;
+ if (Strength.SYNTHETIC.equals(idMessage.getStrength())) {
+ Class<? extends BaseProxy> clazz = 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());
+ }
+ ids.add(id);
+ }
+ return getBeansForIds(ids);
}
public IdFactory getIdFactory() {
@@ -188,7 +208,7 @@
/**
* Creates an AutoBean for the given id, tracking a domain object.
*/
- private <Q extends BaseProxy> AutoBean<Q> createEntityProxyBean(
+ private <Q extends BaseProxy> AutoBean<Q> createProxyBean(
SimpleProxyId<Q> id, Object domainObject) {
AutoBean<Q> toReturn = AutoBeanFactoryMagic.createBean(id.getProxyClass(),
SimpleRequestProcessor.CONFIGURATION);
@@ -199,37 +219,76 @@
}
/**
- * Returns the AutoBean corresponding to the given id, or creates if it does
- * not yet exist.
+ * Returns the AutoBeans corresponding to the given ids, or creates them if
+ * they do 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
+ private List<AutoBean<? extends BaseProxy>> getBeansForIds(
+ List<SimpleProxyId<?>> ids) {
+ List<Class<?>> domainClasses = new ArrayList<Class<?>>(ids.size());
+ List<Object> domainIds = new ArrayList<Object>(ids.size());
+ List<SimpleProxyId<?>> idsToLoad = new ArrayList<SimpleProxyId<?>>();
+
+ /*
+ * Create proxies for ephemeral or synthetic ids that we haven't seen. Queue
+ * up the domain ids for entities that need to be loaded.
+ */
+ for (SimpleProxyId<?> id : ids) {
Class<?> domainClass = service.resolveDomainClass(id.getProxyClass());
- Object domain;
- if (id.isEphemeral() || id.isSynthetic()) {
- domain = service.createDomainObject(domainClass);
+ if (beans.containsKey(id)) {
+ // Already have a proxy for this id, no-op
+ } else if (id.isEphemeral() || id.isSynthetic()) {
+ // Create a new domain object for the short-lived id
+ Object domain = service.createDomainObject(domainClass);
if (domain == null) {
throw new UnexpectedException("Could not create instance of "
+ domainClass.getCanonicalName(), null);
}
+ AutoBean<? extends BaseProxy> bean = createProxyBean(id, domain);
+ beans.put(id, bean);
+ domainObjectsToId.put(domain, id);
} else {
- Splittable address = StringQuoter.split(id.getServerId());
+ // Decode the domain parameter
+ Splittable split = StringQuoter.split(id.getServerId());
Class<?> param = service.getIdType(domainClass);
Object domainParam;
if (ValueCodex.canDecode(param)) {
- domainParam = ValueCodex.decode(param, address);
+ domainParam = ValueCodex.decode(param, split);
} else {
domainParam = new SimpleRequestProcessor(service).decodeOobMessage(
- param, address).get(0);
+ param, split).get(0);
}
- domain = service.loadDomainObject(domainClass, domainParam);
+
+ // Enqueue
+ domainClasses.add(service.resolveDomainClass(id.getProxyClass()));
+ domainIds.add(domainParam);
+ idsToLoad.add(id);
}
- domainObjectsToId.put(domain, id);
- toReturn = createEntityProxyBean(id, domain);
+ }
+
+ // Actually load the data
+ if (!domainClasses.isEmpty()) {
+ assert domainClasses.size() == domainIds.size()
+ && domainClasses.size() == idsToLoad.size();
+ List<Object> loaded = service.loadDomainObjects(domainClasses, domainIds);
+ if (idsToLoad.size() != loaded.size()) {
+ throw new UnexpectedException("Expected " + idsToLoad.size()
+ + " objects to be loaded, got " + loaded.size(), null);
+ }
+
+ Iterator<Object> itLoaded = loaded.iterator();
+ for (SimpleProxyId<?> id : idsToLoad) {
+ Object domain = itLoaded.next();
+ domainObjectsToId.put(domain, id);
+ AutoBean<? extends BaseProxy> bean = createProxyBean(id, domain);
+ beans.put(id, bean);
+ }
+ }
+
+ // Construct the return value
+ List<AutoBean<? extends BaseProxy>> toReturn = new ArrayList<AutoBean<? extends BaseProxy>>(
+ ids.size());
+ for (SimpleProxyId<?> id : ids) {
+ toReturn.add(beans.get(id));
}
return toReturn;
}
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
index 9a0ba62..a906e9f 100644
--- a/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
+++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayer.java
@@ -193,6 +193,22 @@
public abstract <T> T loadDomainObject(Class<T> clazz, Object domainId);
/**
+ * Load multiple objects from the backing store. This method is intended to
+ * allow more efficient access to the backing store by providing all objects
+ * referenced in an incoming payload.
+ * <p>
+ * The default implementation of this method will delegate to
+ * {@link #loadDomainObject(Class, Object)}.
+ *
+ * @param classes type type of each object to load
+ * @param domainIds the ids previously returned from {@link #getId(Object)}
+ * @return the requested objects, elements of which may be {@code null} if the
+ * requested objects were irretrievable
+ */
+ public abstract List<Object> loadDomainObjects(List<Class<?>> classes,
+ List<Object> domainIds);
+
+ /**
* Given a type token previously returned from
* {@link #resolveTypeToken(Class)}, return the Class literal associated with
* the token.
diff --git a/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java b/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
index 5acd2bd..b9a2248 100644
--- a/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
+++ b/user/src/com/google/gwt/requestfactory/server/ServiceLayerDecorator.java
@@ -21,6 +21,7 @@
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
+import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
@@ -93,6 +94,12 @@
}
@Override
+ public List<Object> loadDomainObjects(List<Class<?>> classes,
+ List<Object> domainIds) {
+ return getNext().loadDomainObjects(classes, domainIds);
+ }
+
+ @Override
public Class<? extends BaseProxy> resolveClass(String typeToken) {
return getNext().resolveClass(typeToken);
}
diff --git a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
index f54e62e..4be0769 100644
--- a/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
+++ b/user/src/com/google/gwt/requestfactory/server/SimpleRequestProcessor.java
@@ -25,7 +25,6 @@
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;
@@ -54,6 +53,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@@ -445,13 +445,14 @@
return;
}
- for (final OperationMessage operation : operations) {
- // Unflatten properties
- AutoBean<? extends EntityProxy> bean = state.getBeanForPayload(operation);
- // Use the version later to know which objects need to be sent back
- if (operation.getVersion() != null) {
- bean.setTag(Constants.VERSION_PROPERTY_B64, operation.getVersion());
- }
+ List<AutoBean<? extends BaseProxy>> beans = state.getBeansForPayload(operations);
+ assert operations.size() == beans.size();
+
+ Iterator<OperationMessage> itOp = operations.iterator();
+ for (AutoBean<? extends BaseProxy> bean : beans) {
+ OperationMessage operation = itOp.next();
+ // Save the client's version information to reduce payload size later
+ 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);