Add RequestContext.append() to allow actions across different domain service types to be combined in a single HTTP request.
Patch by: bobv
Review by: rjrjr
Review at http://gwt-code-reviews.appspot.com/1423805
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10052 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/RequestContext.java b/user/src/com/google/web/bindery/requestfactory/shared/RequestContext.java
index 91e45d0..6dc0186 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/RequestContext.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/RequestContext.java
@@ -20,6 +20,26 @@
*/
public interface RequestContext {
/**
+ * Joins another RequestContext to this RequestContext.
+ *
+ * <pre>
+ * SomeContext ctx = myFactory.someContext();
+ * // Perform operations on ctx
+ * OtherContext other = ctx.append(myFactory.otherContext());
+ * // Perform operations on both other and ctx
+ * ctx.fire() // or other.fire() are equivalent
+ * </pre>
+ *
+ * @param other a freshly-constructed RequestContext whose state should be
+ * bound to this RequestContext
+ * @return {@code other}
+ * @throws IllegalStateException if any methods have been called on
+ * {@code other} or if {@code other} was constructed by a different
+ * RequestFactory instance
+ */
+ <T extends RequestContext> T append(T other);
+
+ /**
* Returns a new mutable proxy that this request can carry to the server,
* perhaps to be persisted. If the object is succesfully persisted, a PERSIST
* event will be posted including the EntityProxyId of this proxy.
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java
index 0e10a3a..5e2a151 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/AbstractRequestContext.java
@@ -16,7 +16,7 @@
package com.google.web.bindery.requestfactory.shared.impl;
import static com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory.stableId;
-import static com.google.web.bindery.requestfactory.shared.impl.Constants.REQUEST_CONTEXT;
+import static com.google.web.bindery.requestfactory.shared.impl.Constants.REQUEST_CONTEXT_STATE;
import static com.google.web.bindery.requestfactory.shared.impl.Constants.STABLE_ID;
import com.google.web.bindery.autobean.shared.AutoBean;
@@ -88,6 +88,63 @@
abstract DialectImpl create(AbstractRequestContext context);
}
+ /**
+ * Encapsulates all state contained by the AbstractRequestContext.
+ */
+ protected static class State {
+ public final AbstractRequestContext canonical;
+ public final DialectImpl dialect;
+ public final List<AbstractRequest<?>> invocations = new ArrayList<AbstractRequest<?>>();
+
+ public boolean locked;
+ /**
+ * A map of all EntityProxies that the RequestContext has interacted with.
+ * Objects are placed into this map by being returned from {@link #create},
+ * passed into {@link #edit}, or used as an invocation argument.
+ */
+ public final Map<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> editedProxies =
+ new LinkedHashMap<SimpleProxyId<?>, AutoBean<? extends BaseProxy>>();
+ /**
+ * A map that contains the canonical instance of an entity to return in the
+ * return graph, since this is built from scratch.
+ */
+ public final Map<SimpleProxyId<?>, AutoBean<?>> returnedProxies =
+ new HashMap<SimpleProxyId<?>, AutoBean<?>>();
+
+ public final AbstractRequestFactory requestFactory;
+
+ /**
+ * A map that allows us to handle the case where the server has sent back an
+ * unpersisted entity. Because we assume that the server is stateless, the
+ * client will need to swap out the request-local ids with a regular
+ * client-allocated id.
+ */
+ public final Map<Integer, SimpleProxyId<?>> syntheticIds =
+ new HashMap<Integer, SimpleProxyId<?>>();
+
+ public State(AbstractRequestFactory requestFactory, DialectImpl dialect,
+ AbstractRequestContext canonical) {
+ this.requestFactory = requestFactory;
+ this.dialect = dialect;
+ this.canonical = canonical;
+ }
+
+ public AbstractRequestContext getCanonicalContext() {
+ return canonical;
+ }
+
+ public boolean isClean() {
+ return editedProxies.isEmpty() && invocations.isEmpty() && !locked
+ && returnedProxies.isEmpty() && syntheticIds.isEmpty();
+ }
+
+ public boolean isCompatible(State state) {
+ // Object comparison intentional
+ return requestFactory == state.requestFactory
+ && dialect.getClass().equals(state.dialect.getClass());
+ }
+ }
+
interface DialectImpl {
void addInvocation(AbstractRequest<?> request);
@@ -107,17 +164,17 @@
* ironed out. Once this is done, addInvocation() can be removed from the
* DialectImpl interface and restored to to AbstractRequestContext.
*/
- if (!invocations.isEmpty()) {
+ if (!state.invocations.isEmpty()) {
throw new RuntimeException("Only one invocation per request, pending backend support");
}
- invocations.add(request);
+ state.invocations.add(request);
for (Object arg : request.getRequestData().getOrderedParameters()) {
retainArg(arg);
}
}
public String makePayload() {
- RequestData data = invocations.get(0).getRequestData();
+ RequestData data = state.invocations.get(0).getRequestData();
AutoBean<JsonRpcRequest> bean = MessageFactoryHolder.FACTORY.jsonRpcRequest();
JsonRpcRequest request = bean.as();
@@ -145,7 +202,7 @@
Splittable raw = StringQuoter.split(payload);
@SuppressWarnings("unchecked")
- Receiver<Object> callback = (Receiver<Object>) invocations.get(0).getReceiver();
+ Receiver<Object> callback = (Receiver<Object>) state.invocations.get(0).getReceiver();
if (!raw.isNull("error")) {
Splittable error = raw.get("error");
@@ -159,7 +216,7 @@
Splittable result = raw.get("result");
@SuppressWarnings("unchecked")
Class<BaseProxy> target =
- (Class<BaseProxy>) invocations.get(0).getRequestData().getReturnType();
+ (Class<BaseProxy>) state.invocations.get(0).getRequestData().getReturnType();
SimpleProxyId<BaseProxy> id = getRequestFactory().allocateId(target);
AutoBean<BaseProxy> bean = createProxy(target, id);
@@ -197,7 +254,7 @@
* Called by generated subclasses to enqueue a method invocation.
*/
public void addInvocation(AbstractRequest<?> request) {
- invocations.add(request);
+ state.invocations.add(request);
for (Object arg : request.getRequestData().getOrderedParameters()) {
retainArg(arg);
}
@@ -260,15 +317,15 @@
// Send return values
Set<Throwable> causes = null;
- for (int i = 0, j = invocations.size(); i < j; i++) {
+ for (int i = 0, j = state.invocations.size(); i < j; i++) {
try {
if (response.getStatusCodes().get(i)) {
- invocations.get(i).onSuccess(response.getInvocationResults().get(i));
+ state.invocations.get(i).onSuccess(response.getInvocationResults().get(i));
} else {
ServerFailureMessage failure =
AutoBeanCodex.decode(MessageFactoryHolder.FACTORY, ServerFailureMessage.class,
response.getInvocationResults().get(i)).as();
- invocations.get(i).onFail(
+ state.invocations.get(i).onFail(
new ServerFailure(failure.getMessage(), failure.getExceptionType(), failure
.getStackTrace(), failure.isFatal()));
}
@@ -291,9 +348,9 @@
}
}
// After success, shut down the context
- editedProxies.clear();
- invocations.clear();
- returnedProxies.clear();
+ state.editedProxies.clear();
+ state.invocations.clear();
+ state.returnedProxies.clear();
if (causes != null) {
throw new UmbrellaException(causes);
@@ -321,7 +378,7 @@
AutoBean<BaseProxy> stub = getProxyForReturnPayloadGraph(baseId);
// So pick up the instance that we just sent to the server
- AutoBean<?> edited = editedProxies.get(BaseProxyCategory.stableId(stub));
+ AutoBean<?> edited = state.editedProxies.get(BaseProxyCategory.stableId(stub));
currentProxy = (BaseProxy) edited.as();
// Try to find the original, immutable version.
@@ -357,38 +414,24 @@
WriteOperation.PERSIST, WriteOperation.UPDATE};
private static final WriteOperation[] UPDATE_ONLY = {WriteOperation.UPDATE};
private static int payloadId = 100;
- protected final List<AbstractRequest<?>> invocations = new ArrayList<AbstractRequest<?>>();
- private boolean locked;
- private final AbstractRequestFactory requestFactory;
- /**
- * A map of all EntityProxies that the RequestContext has interacted with.
- * Objects are placed into this map by being passed into {@link #edit} or as
- * an invocation argument.
- */
- private final Map<SimpleProxyId<?>, AutoBean<? extends BaseProxy>> editedProxies =
- new LinkedHashMap<SimpleProxyId<?>, AutoBean<? extends BaseProxy>>();
- /**
- * A map that contains the canonical instance of an entity to return in the
- * return graph, since this is built from scratch.
- */
- private final Map<SimpleProxyId<?>, AutoBean<?>> returnedProxies =
- new HashMap<SimpleProxyId<?>, AutoBean<?>>();
-
- /**
- * A map that allows us to handle the case where the server has sent back an
- * unpersisted entity. Because we assume that the server is stateless, the
- * client will need to swap out the request-local ids with a regular
- * client-allocated id.
- */
- private final Map<Integer, SimpleProxyId<?>> syntheticIds =
- new HashMap<Integer, SimpleProxyId<?>>();
-
- private final DialectImpl dialect;
+ private State state;
protected AbstractRequestContext(AbstractRequestFactory factory, Dialect dialect) {
- this.requestFactory = factory;
- this.dialect = dialect.create(this);
+ this.state = new State(factory, dialect.create(this), this);
+ }
+
+ public <T extends RequestContext> T append(T other) {
+ AbstractRequestContext child = (AbstractRequestContext) other;
+ if (!state.isCompatible(child.state)) {
+ throw new IllegalStateException(getClass().getName() + " and " + child.getClass().getName()
+ + " are not compatible");
+ }
+ if (!child.state.isClean()) {
+ throw new IllegalStateException("The provided RequestContext has been changed");
+ }
+ child.state = state;
+ return other;
}
/**
@@ -397,7 +440,7 @@
public <T extends BaseProxy> T create(Class<T> clazz) {
checkLocked();
- SimpleProxyId<T> id = requestFactory.allocateId(clazz);
+ SimpleProxyId<T> id = state.requestFactory.allocateId(clazz);
AutoBean<T> created = createProxy(clazz, id);
return takeOwnership(created);
}
@@ -426,7 +469,8 @@
checkLocked();
@SuppressWarnings("unchecked")
- AutoBean<T> previouslySeen = (AutoBean<T>) editedProxies.get(BaseProxyCategory.stableId(bean));
+ AutoBean<T> previouslySeen =
+ (AutoBean<T>) state.editedProxies.get(BaseProxyCategory.stableId(bean));
if (previouslySeen != null && !previouslySeen.isFrozen()) {
/*
* If we've seen the object before, it might be because it was passed in
@@ -450,7 +494,7 @@
*/
public void fire() {
boolean needsReceiver = true;
- for (AbstractRequest<?> request : invocations) {
+ for (AbstractRequest<?> request : state.invocations) {
if (request.hasReceiver()) {
needsReceiver = false;
break;
@@ -488,7 +532,7 @@
}
public AbstractRequestFactory getRequestFactory() {
- return requestFactory;
+ return state.requestFactory;
}
/**
@@ -517,7 +561,7 @@
* simple flag-check because of the possibility of "unmaking" a change, per
* the JavaDoc.
*/
- for (AutoBean<? extends BaseProxy> bean : editedProxies.values()) {
+ for (AutoBean<? extends BaseProxy> bean : state.editedProxies.values()) {
AutoBean<?> previous = bean.getTag(Constants.PARENT_OBJECT);
if (previous == null) {
// Compare to empty object
@@ -535,26 +579,26 @@
* EntityCodex support.
*/
public boolean isEntityType(Class<?> clazz) {
- return requestFactory.isEntityType(clazz);
+ return state.requestFactory.isEntityType(clazz);
}
public boolean isLocked() {
- return locked;
+ return state.locked;
}
/**
* EntityCodex support.
*/
public boolean isValueType(Class<?> clazz) {
- return requestFactory.isValueType(clazz);
- }
+ return state.requestFactory.isValueType(clazz);
+ };
/**
* Called by generated subclasses to enqueue a method invocation.
*/
protected void addInvocation(AbstractRequest<?> request) {
- dialect.addInvocation(request);
- };
+ state.dialect.addInvocation(request);
+ }
/**
* Invoke the appropriate {@code onFailure} callbacks, possibly throwing an
@@ -563,7 +607,7 @@
protected void fail(Receiver<Void> receiver, ServerFailure failure) {
reuse();
Set<Throwable> causes = null;
- for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(invocations)) {
+ for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(state.invocations)) {
try {
request.onFail(failure);
} catch (Throwable t) {
@@ -602,7 +646,7 @@
protected void violation(final Receiver<Void> receiver, Set<Violation> errors) {
reuse();
Set<Throwable> causes = null;
- for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(invocations)) {
+ for (AbstractRequest<?> request : new ArrayList<AbstractRequest<?>>(state.invocations)) {
try {
request.onViolation(errors);
} catch (Throwable t) {
@@ -635,7 +679,7 @@
if (Strength.SYNTHETIC.equals(op.getStrength())) {
return allocateSyntheticId(op.getTypeToken(), op.getSyntheticId());
}
- return requestFactory.getId(op.getTypeToken(), op.getServerId(), op.getClientId());
+ return state.requestFactory.getId(op.getTypeToken(), op.getServerId(), op.getClientId());
}
/**
@@ -644,11 +688,11 @@
*/
<Q extends BaseProxy> AutoBean<Q> getProxyForReturnPayloadGraph(SimpleProxyId<Q> id) {
@SuppressWarnings("unchecked")
- AutoBean<Q> bean = (AutoBean<Q>) returnedProxies.get(id);
+ AutoBean<Q> bean = (AutoBean<Q>) state.returnedProxies.get(id);
if (bean == null) {
Class<Q> proxyClass = id.getProxyClass();
bean = createProxy(proxyClass, id);
- returnedProxies.put(id, bean);
+ state.returnedProxies.put(id, bean);
}
return bean;
@@ -664,7 +708,7 @@
// The OperationMessages describes operations on exactly one entity
AutoBean<OperationMessage> toReturn = MessageFactoryHolder.FACTORY.operation();
OperationMessage operation = toReturn.as();
- operation.setTypeToken(requestFactory.getTypeToken(stableId.getProxyClass()));
+ operation.setTypeToken(state.requestFactory.getTypeToken(stableId.getProxyClass()));
// Find the object to compare against
AutoBean<?> parent;
@@ -784,14 +828,14 @@
* Notify subscribers if the object differs from when it first came into the
* RequestContext.
*/
- if (operations != null && requestFactory.isEntityType(id.getProxyClass())) {
+ if (operations != null && state.requestFactory.isEntityType(id.getProxyClass())) {
for (WriteOperation writeOperation : operations) {
if (writeOperation.equals(WriteOperation.UPDATE)
- && !requestFactory.hasVersionChanged(id, op.getVersion())) {
+ && !state.requestFactory.hasVersionChanged(id, op.getVersion())) {
// No updates if the server reports no change
continue;
}
- requestFactory.getEventBus().fireEventFromSource(
+ state.requestFactory.getEventBus().fireEventFromSource(
new EntityProxyChange<EntityProxy>((EntityProxy) proxy, writeOperation),
id.getProxyClass());
}
@@ -807,16 +851,17 @@
private <Q extends BaseProxy> SimpleProxyId<Q> allocateSyntheticId(String typeToken,
int syntheticId) {
@SuppressWarnings("unchecked")
- SimpleProxyId<Q> toReturn = (SimpleProxyId<Q>) syntheticIds.get(syntheticId);
+ SimpleProxyId<Q> toReturn = (SimpleProxyId<Q>) state.syntheticIds.get(syntheticId);
if (toReturn == null) {
- toReturn = requestFactory.allocateId(requestFactory.<Q> getTypeFromToken(typeToken));
- syntheticIds.put(syntheticId, toReturn);
+ toReturn =
+ state.requestFactory.allocateId(state.requestFactory.<Q> getTypeFromToken(typeToken));
+ state.syntheticIds.put(syntheticId, toReturn);
}
return toReturn;
}
private void checkLocked() {
- if (locked) {
+ if (state.locked) {
throw new IllegalStateException("A request is already in progress");
}
}
@@ -832,13 +877,13 @@
throw new IllegalArgumentException(object.getClass().getName());
}
- RequestContext context = bean.getTag(REQUEST_CONTEXT);
- if (!bean.isFrozen() && context != this) {
+ State otherState = bean.getTag(REQUEST_CONTEXT_STATE);
+ if (!bean.isFrozen() && otherState != this.state) {
/*
* This means something is way off in the weeds. If a bean is editable,
* it's supposed to be associated with a RequestContext.
*/
- assert context != null : "Unfrozen bean with null RequestContext";
+ assert otherState != null : "Unfrozen bean with null RequestContext";
/*
* Already editing the object in another context or it would have been in
@@ -937,18 +982,18 @@
private void doFire(final Receiver<Void> receiver) {
checkLocked();
- locked = true;
+ state.locked = true;
freezeEntities(true);
- String payload = dialect.makePayload();
- requestFactory.getRequestTransport().send(payload, new TransportReceiver() {
+ String payload = state.dialect.makePayload();
+ state.requestFactory.getRequestTransport().send(payload, new TransportReceiver() {
public void onTransportFailure(ServerFailure failure) {
fail(receiver, failure);
}
public void onTransportSuccess(String payload) {
- dialect.processPayload(receiver, payload);
+ state.dialect.processPayload(receiver, payload);
}
});
}
@@ -957,7 +1002,7 @@
* Set the frozen status of all EntityProxies owned by this context.
*/
private void freezeEntities(boolean frozen) {
- for (AutoBean<?> bean : editedProxies.values()) {
+ for (AutoBean<?> bean : state.editedProxies.values()) {
bean.setFrozen(frozen);
}
}
@@ -969,7 +1014,7 @@
// Always diff'ed against itself, producing a no-op
toMutate.setTag(Constants.PARENT_OBJECT, toMutate);
// Act with entity-identity semantics
- toMutate.setTag(REQUEST_CONTEXT, null);
+ toMutate.setTag(REQUEST_CONTEXT_STATE, null);
toMutate.setFrozen(true);
}
@@ -981,7 +1026,7 @@
MessageFactory f = MessageFactoryHolder.FACTORY;
List<InvocationMessage> invocationMessages = new ArrayList<InvocationMessage>();
- for (AbstractRequest<?> invocation : invocations) {
+ for (AbstractRequest<?> invocation : state.invocations) {
// RequestData is produced by the generated subclass
RequestData data = invocation.getRequestData();
InvocationMessage message = f.invocation().as();
@@ -1014,7 +1059,7 @@
*/
private List<OperationMessage> makePayloadOperations() {
List<OperationMessage> operations = new ArrayList<OperationMessage>();
- for (AutoBean<? extends BaseProxy> currentView : editedProxies.values()) {
+ for (AutoBean<? extends BaseProxy> currentView : state.editedProxies.values()) {
OperationMessage operation =
makeOperationMessage(BaseProxyCategory.stableId(currentView), currentView, true).as();
operations.add(operation);
@@ -1079,15 +1124,15 @@
*/
private void reuse() {
freezeEntities(false);
- locked = false;
+ state.locked = false;
}
/**
* Make the EnityProxy bean edited and owned by this RequestContext.
*/
private <T extends BaseProxy> T takeOwnership(AutoBean<T> bean) {
- editedProxies.put(stableId(bean), bean);
- bean.setTag(REQUEST_CONTEXT, this);
+ state.editedProxies.put(stableId(bean), bean);
+ bean.setTag(REQUEST_CONTEXT_STATE, this.state);
return bean.as();
}
}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java
index 481f4c8..1772203 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/BaseProxyCategory.java
@@ -15,12 +15,13 @@
*/
package com.google.web.bindery.requestfactory.shared.impl;
-import static com.google.web.bindery.requestfactory.shared.impl.Constants.REQUEST_CONTEXT;
+import static com.google.web.bindery.requestfactory.shared.impl.Constants.REQUEST_CONTEXT_STATE;
import static com.google.web.bindery.requestfactory.shared.impl.Constants.STABLE_ID;
import com.google.web.bindery.autobean.shared.AutoBean;
import com.google.web.bindery.autobean.shared.AutoBeanUtils;
import com.google.web.bindery.requestfactory.shared.BaseProxy;
+import com.google.web.bindery.requestfactory.shared.impl.AbstractRequestContext.State;
/**
* Contains behaviors common to all proxy instances.
@@ -65,17 +66,17 @@
*/
AutoBean<T> otherBean = AutoBeanUtils.getAutoBean(returnValue);
if (otherBean != null) {
- otherBean.setTag(REQUEST_CONTEXT, bean.getTag(REQUEST_CONTEXT));
+ otherBean.setTag(REQUEST_CONTEXT_STATE, bean.getTag(REQUEST_CONTEXT_STATE));
}
return returnValue;
}
public static AbstractRequestContext requestContext(AutoBean<?> bean) {
- return bean.getTag(REQUEST_CONTEXT);
+ State state = bean.<AbstractRequestContext.State> getTag(REQUEST_CONTEXT_STATE);
+ return state == null ? null : state.getCanonicalContext();
}
- public static <T extends BaseProxy> SimpleProxyId<T> stableId(
- AutoBean<? extends T> bean) {
+ public static <T extends BaseProxy> SimpleProxyId<T> stableId(AutoBean<? extends T> bean) {
return bean.getTag(STABLE_ID);
}
}
diff --git a/user/src/com/google/web/bindery/requestfactory/shared/impl/Constants.java b/user/src/com/google/web/bindery/requestfactory/shared/impl/Constants.java
index 33d914e..279bc86 100644
--- a/user/src/com/google/web/bindery/requestfactory/shared/impl/Constants.java
+++ b/user/src/com/google/web/bindery/requestfactory/shared/impl/Constants.java
@@ -22,7 +22,7 @@
String DOMAIN_OBJECT = "domainObject";
String IN_RESPONSE = "inResponse";
String PARENT_OBJECT = "parentObject";
- String REQUEST_CONTEXT = "requestContext";
+ String REQUEST_CONTEXT_STATE = "requestContext";
String STABLE_ID = "stableId";
String VERSION_PROPERTY_B64 = "version";
}
diff --git a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
index bcdb434..d912119 100644
--- a/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
+++ b/user/src/com/google/web/bindery/requestfactory/vm/InProcessRequestContext.java
@@ -42,8 +42,11 @@
*/
class InProcessRequestContext extends AbstractRequestContext {
class RequestContextHandler implements InvocationHandler {
- public Object invoke(Object proxy, Method method, final Object[] args)
- throws Throwable {
+ public InProcessRequestContext getContext() {
+ return InProcessRequestContext.this;
+ }
+
+ public Object invoke(Object proxy, Method method, final Object[] args) throws Throwable {
// Maybe delegate to superclass
Class<?> owner = method.getDeclaringClass();
if (Object.class.equals(owner) || RequestContext.class.equals(owner)
@@ -63,9 +66,9 @@
Type returnGenericType;
boolean isInstance = InstanceRequest.class.isAssignableFrom(method.getReturnType());
if (isInstance) {
- returnGenericType = TypeUtils.getParameterization(
- InstanceRequest.class, method.getGenericReturnType(),
- method.getReturnType())[1];
+ returnGenericType =
+ TypeUtils.getParameterization(InstanceRequest.class, method.getGenericReturnType(),
+ method.getReturnType())[1];
if (args == null) {
actualArgs = new Object[1];
} else {
@@ -74,8 +77,9 @@
System.arraycopy(args, 0, actualArgs, 1, args.length);
}
} else {
- returnGenericType = TypeUtils.getSingleParameterization(Request.class,
- method.getGenericReturnType(), method.getReturnType());
+ returnGenericType =
+ TypeUtils.getSingleParameterization(Request.class, method.getGenericReturnType(),
+ method.getReturnType());
if (args == null) {
actualArgs = NO_ARGS;
} else {
@@ -84,26 +88,23 @@
}
Class<?> returnType = TypeUtils.ensureBaseType(returnGenericType);
- Class<?> elementType = Collection.class.isAssignableFrom(returnType)
- ? TypeUtils.ensureBaseType(TypeUtils.getSingleParameterization(
- Collection.class, returnGenericType)) : null;
+ Class<?> elementType =
+ Collection.class.isAssignableFrom(returnType) ? TypeUtils.ensureBaseType(TypeUtils
+ .getSingleParameterization(Collection.class, returnGenericType)) : null;
final RequestData data;
if (dialect.equals(Dialect.STANDARD)) {
- String operation = method.getDeclaringClass().getName() + "::"
- + method.getName();
+ String operation = method.getDeclaringClass().getName() + "::" + method.getName();
data = new RequestData(operation, actualArgs, returnType, elementType);
} else {
// Calculate request metadata
- JsonRpcWireName wireInfo = method.getReturnType().getAnnotation(
- JsonRpcWireName.class);
+ JsonRpcWireName wireInfo = method.getReturnType().getAnnotation(JsonRpcWireName.class);
String apiVersion = wireInfo.version();
String operation = wireInfo.value();
int foundContent = -1;
- final String[] parameterNames = args == null ? new String[0]
- : new String[args.length];
+ final String[] parameterNames = args == null ? new String[0] : new String[args.length];
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
parameter : for (int i = 0, j = parameterAnnotations.length; i < j; i++) {
for (Annotation annotation : parameterAnnotations[i]) {
@@ -115,10 +116,8 @@
continue parameter;
}
}
- throw new UnsupportedOperationException("No "
- + PropertyName.class.getCanonicalName()
- + " annotation on parameter " + i + " of method "
- + method.toString());
+ throw new UnsupportedOperationException("No " + PropertyName.class.getCanonicalName()
+ + " annotation on parameter " + i + " of method " + method.toString());
}
final int contentIdx = foundContent;
@@ -134,14 +133,14 @@
}
// Create the request, just filling in the RequestData details
- final AbstractRequest<Object> req = new AbstractRequest<Object>(
- InProcessRequestContext.this) {
- @Override
- protected RequestData makeRequestData() {
- data.setPropertyRefs(propertyRefs);
- return data;
- }
- };
+ final AbstractRequest<Object> req =
+ new AbstractRequest<Object>(InProcessRequestContext.this) {
+ @Override
+ protected RequestData makeRequestData() {
+ data.setPropertyRefs(propertyRefs);
+ return data;
+ }
+ };
if (!isInstance) {
// Instance invocations are enqueued when using() is called
@@ -153,19 +152,15 @@
} else if (dialect.equals(Dialect.JSON_RPC)) {
// Support optional parameters for JSON-RPC payloads
Class<?> requestType = method.getReturnType().asSubclass(Request.class);
- return Proxy.newProxyInstance(requestType.getClassLoader(),
- new Class<?>[] {requestType}, new InvocationHandler() {
- public Object invoke(Object proxy, Method method, Object[] args)
- throws Throwable {
+ return Proxy.newProxyInstance(requestType.getClassLoader(), new Class<?>[] {requestType},
+ new InvocationHandler() {
+ public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())
|| Request.class.equals(method.getDeclaringClass())) {
return method.invoke(req, args);
- } else if (BeanMethod.SET.matches(method)
- || BeanMethod.SET_BUILDER.matches(method)) {
- req.getRequestData().setNamedParameter(
- BeanMethod.SET.inferName(method), args[0]);
- return Void.TYPE.equals(method.getReturnType()) ? null
- : proxy;
+ } else if (BeanMethod.SET.matches(method) || BeanMethod.SET_BUILDER.matches(method)) {
+ req.getRequestData().setNamedParameter(BeanMethod.SET.inferName(method), args[0]);
+ return Void.TYPE.equals(method.getReturnType()) ? null : proxy;
}
throw new UnsupportedOperationException(method.toString());
}
@@ -179,13 +174,19 @@
static final Object[] NO_ARGS = new Object[0];
private final Dialect dialect;
- protected InProcessRequestContext(AbstractRequestFactory factory,
- Dialect dialect) {
+ protected InProcessRequestContext(AbstractRequestFactory factory, Dialect dialect) {
super(factory, dialect);
this.dialect = dialect;
}
@Override
+ public <T extends RequestContext> T append(T other) {
+ RequestContextHandler h = (RequestContextHandler) Proxy.getInvocationHandler(other);
+ super.append(h.getContext());
+ return other;
+ }
+
+ @Override
protected AutoBeanFactory getAutoBeanFactory() {
return ((InProcessRequestFactory) getRequestFactory()).getAutoBeanFactory();
}
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTest.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTest.java
index 6b6f4fd..b23c76e 100644
--- a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTest.java
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTest.java
@@ -223,6 +223,87 @@
});
}
+ public void testAppend() {
+ delayTestFinish(DELAY_TEST_FINISH);
+ SimpleFooRequest c1 = req.simpleFooRequest();
+ SimpleFooProxy foo1 = c1.create(SimpleFooProxy.class);
+ SimpleBarRequest c2 = c1.append(req.simpleBarRequest());
+ SimpleFooRequest c3 = c2.append(req.simpleFooRequest());
+
+ assertNotSame(c1, c3);
+ assertSame(foo1, c2.edit(foo1));
+ assertSame(foo1, c3.edit(foo1));
+
+ SimpleBarProxy foo2 = c2.create(SimpleBarProxy.class);
+ assertSame(foo2, c1.edit(foo2));
+ assertSame(foo2, c3.edit(foo2));
+
+ SimpleFooProxy foo3 = c3.create(SimpleFooProxy.class);
+ assertSame(foo3, c1.edit(foo3));
+ assertSame(foo3, c2.edit(foo3));
+
+ try {
+ // Throws exception because c3 has already accumulated some state
+ req.simpleValueContext().append(c3);
+ fail("Should have thrown IllegalStateException");
+ } catch (IllegalStateException expected) {
+ }
+
+ try {
+ // Throws exception because a different RequestFactory instance is used
+ c3.append(createFactory().simpleFooRequest());
+ fail("Should have thrown IllegalStateException");
+ } catch (IllegalStateException expected) {
+ }
+
+ // Queue up two invocations, and test that both Receivers are called
+ final boolean[] seen = {false, false};
+ c1.add(1, 2).to(new Receiver<Integer>() {
+ @Override
+ public void onSuccess(Integer response) {
+ seen[0] = true;
+ assertEquals(3, response.intValue());
+ }
+ });
+ c2.countSimpleBar().to(new Receiver<Long>() {
+ @Override
+ public void onSuccess(Long response) {
+ seen[1] = true;
+ assertEquals(2, response.longValue());
+ }
+ });
+
+ // It doesn't matter which context instance is fired
+ c2.fire(new Receiver<Void>() {
+ @Override
+ public void onSuccess(Void response) {
+ assertTrue(seen[0]);
+ assertTrue(seen[1]);
+ finishTestAndReset();
+ }
+ });
+
+ /*
+ * Since the common State has been locked, calling any other
+ * context-mutation methods should fail.
+ */
+ try {
+ c1.fire();
+ fail("Should have thrown exception");
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ c3.fire();
+ fail("Should have thrown exception");
+ } catch (IllegalStateException expected) {
+ }
+ try {
+ c3.create(SimpleFooProxy.class);
+ fail("Should have thrown exception");
+ } catch (IllegalStateException expected) {
+ }
+ }
+
/**
* Test that we can commit child objects.
*/
diff --git a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTestBase.java b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTestBase.java
index cac062b..43f9681 100644
--- a/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTestBase.java
+++ b/user/test/com/google/web/bindery/requestfactory/gwt/client/RequestFactoryTestBase.java
@@ -1,12 +1,12 @@
/*
* Copyright 2010 Google Inc.
- *
+ *
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
- *
+ *
* http://www.apache.org/licenses/LICENSE-2.0
- *
+ *
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
@@ -27,6 +27,7 @@
import com.google.web.bindery.requestfactory.shared.EntityProxyChange;
import com.google.web.bindery.requestfactory.shared.ProxySerializer;
import com.google.web.bindery.requestfactory.shared.Receiver;
+import com.google.web.bindery.requestfactory.shared.SimpleFooRequest;
import com.google.web.bindery.requestfactory.shared.SimpleRequestFactory;
import com.google.web.bindery.requestfactory.shared.impl.BaseProxyCategory;
import com.google.web.bindery.requestfactory.shared.impl.Constants;
@@ -36,7 +37,7 @@
* A base class for anything that makes use of the SimpleRequestFactory.
* Subclasses must always use {@link #finishTestAndReset()} in order to allow
* calls to the reset methods to complete before the next test starts.
- *
+ *
*/
public abstract class RequestFactoryTestBase extends GWTTestCase {
@@ -113,13 +114,11 @@
assertTrue(AutoBeanUtils.deepEquals(originalBean, restoredBean));
if (proxy instanceof EntityProxy && !id.isEphemeral()) {
- assertEquals(((EntityProxy) proxy).stableId(),
- ((EntityProxy) restored).stableId());
+ assertEquals(((EntityProxy) proxy).stableId(), ((EntityProxy) restored).stableId());
}
// In deference to testing stable ids, copy the original id into the clone
- restoredBean.setTag(Constants.STABLE_ID,
- originalBean.getTag(Constants.STABLE_ID));
+ restoredBean.setTag(Constants.STABLE_ID, originalBean.getTag(Constants.STABLE_ID));
return restored;
}
@@ -143,23 +142,13 @@
}
protected void finishTestAndReset() {
- final boolean[] reallyDone = {false, false};
- req.simpleFooRequest().reset().fire(new Receiver<Void>() {
+ SimpleFooRequest ctx = req.simpleFooRequest();
+ ctx.reset();
+ ctx.append(req.simpleBarRequest()).reset();
+ ctx.fire(new Receiver<Void>() {
@Override
public void onSuccess(Void response) {
- reallyDone[0] = true;
- if (reallyDone[0] && reallyDone[1]) {
- finishTest();
- }
- }
- });
- req.simpleBarRequest().reset().fire(new Receiver<Void>() {
- @Override
- public void onSuccess(Void response) {
- reallyDone[1] = true;
- if (reallyDone[0] && reallyDone[1]) {
- finishTest();
- }
+ finishTest();
}
});
}