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);