Re-firing a failed/rejected server call in RequestFactory, is this possible?

283 views
Skip to first unread message

Eric Andresen

unread,
Jul 28, 2011, 5:55:54 PM7/28/11
to google-we...@googlegroups.com
Does anyone know of a way to perform the following scenario?

  1.  Retrieve an EntityProxy
  2.  Edit the proxy in an editor
  3.  Submit the changes using a Request Context
  4.  The server call is rejected due to a server-side validation failure
  5.  Make some more changes to the proxy
  6.  Submit the changes again

I've tried going down a couple of routes to do this:
  A)  Keep a copy of the EntityProxy around and try to re-edit it:  AutoBean Frozen (and no relief in sight for http://code.google.com/p/google-web-toolkit/issues/detail?id=5794)
  B)  Keep a copy of the Context around and try to re-use it:  Also a no-go
  C)  Have the server respond to the failed update in step 4 by returning the modified object and editing and re-submitting that one.
         This one was close, but not quite there.  I realized that when I edit the returned object and send up the changes ( step 6), the RequestFactory only sends up the changes made during step 5, and not the ones made during step 2.

I guess my question is whether anyone smarter than me has come up with a solution to this scenario?  

Thanks,
Eric

Thomas Broyer

unread,
Jul 28, 2011, 7:52:30 PM7/28/11
to google-we...@googlegroups.com
I never actually tried it, but from what I read in the code, in case of onConstraintViolations, the RequestContext that you initially fired should be "unfrozen" and re-fireable after you fix your proxies.
It should even be the same in case of onFailure.

Calls reuse(), which unfreezes the proxies and unlocks the RequestContext.

Eric Andresen

unread,
Jul 29, 2011, 9:14:22 AM7/29/11
to google-we...@googlegroups.com
Hmm, I tried going down the onViolation / onFailure paths, but ran in to other problems with those.

For onFailure, is there a way to send a valueProxy back inside the exception payload?  It looks like the default behavior is just to extract the message out of any thrown exception.  I'd like to return my errors to the client in a meaningful format so I can mark up the editor.  I guess worst case I could serialize the object array as a string and decode it on the client, but I'd like to avoid that if possible.

onConstraintViolation is what I really wanted, but couldn't find any hooks on the server side to allow me to convert my legacy application's server-side violations into the necessary ViolationMessages.  Basically I'd like to run the code from the SimpleRequestProcessor:process(request,response) function:
    // Validate entities
    List<ViolationMessage> errorMessages = validateEntities(source);

    if (!errorMessages.isEmpty()) {
      resp.setViolations(errorMessages);
      return;
    }

but rather than get the errors from the validateEntries method, my service layer method invocation will either return or throw them.  Unfortunately it doesn't look like the SimpleRequestProcessor is override-able since it's declared private final in the RequestFactoryServlet, and I couldn't find any hooks to allow me to post-process the response after the invocation.

Thanks,
Eric

Eric Andresen

unread,
Aug 1, 2011, 11:29:21 AM8/1/11
to google-we...@googlegroups.com
It looks like the onFailure case only unfreezes the context in the case of onTransportFailure or response.getGeneralFailure, not in the case of an invocation failure.   I tested this, and the context is still locked and the beans still frozen when my onFailure callback is called.

I guess I'll look a bit deeper and see if I can find a way to get my errors returned through the OnViolation callback.

Thanks,
Eric

Kevin Jordan

unread,
Sep 6, 2011, 10:22:24 AM9/6/11
to google-we...@googlegroups.com
I'm running into this issue too.  It seems when the server throws an exception it gets sent to the individual Request not the fail() method in the RequestContext.  So reuse() is never called from it in the case of a server exception.  I'm not sure why they designed it this way.  A server exception should do a fail for the overall RequestContext not for an individual request.  There are many cases where you would want to be able to resubmit a request after a server exception (in my case it was a column was the wrong type in my database and so JDBC was throwing an exception about it and once the column is changed, I want to be able to resubmit it on the client-side, but I can't due to the fact that the request still says it's locked).  Except for subclassing AbstractRequestContext and overriding the processPayload to act the same no matter the failure type, or writing a JSNI to get to reuse() on a context, I'm not sure how to fix this.  Does everyone else feel that reuse() should be called no matter what the cause of the exception?

Eric Andresen

unread,
Sep 6, 2011, 11:19:56 AM9/6/11
to google-we...@googlegroups.com
Kevin, 

  My "solution" was to make copies of both RequestFactoryServlet and SimpleRequestProcessor in a com.google.web.bindery.requestfactory.server package in my environment, and then inside MySimpleRequestProcessor.process(), add the following change:

    .
    .
    .
    assert invocationResults.size() == invocationSuccess.size();
    if (!invocationResults.isEmpty()) {
      resp.setInvocationResults(invocationResults);
      resp.setStatusCodes(invocationSuccess);
    }
    
    // BEGIN CUSTOMIZATION
    Iterator<Boolean>  successIter = invocationSuccess.iterator();
    Iterator<Splittable> resultIter = invocationResults.iterator();
    
    while (successIter.hasNext() && resultIter.hasNext())
    {
        Boolean success = successIter.next();
        Splittable result = resultIter.next();
        
        if (!success)
        {
            resp.setGeneralFailure(
                    (AutoBeanCodex.decode(FACTORY, ServerFailureMessage.class, result)).as());
            break;
        }
        
    }
    // END CUSTOMIZATION

    
    if (!operations.isEmpty()) {
      resp.setOperations(operations);
    }
  }
  .
  .
  .

I then modified MyRequestFactoryServlet to reference MySimpleRequestProcessor, and modified my application context to launch MyRequestFactoryServlet instead of RequestFactoryServlet.  This is far from ideal, but it was the only way I could find to force a general failure based on the invocation failure, since none of the methods in question are publicly accessible.  It also leaves me requiring hand-edits of these files every time we upgrade to a new GWT version.

Thomas Broyer

unread,
Sep 6, 2011, 4:11:30 PM9/6/11
to google-we...@googlegroups.com
RF invocations are batched in a single HTTP request, but there's no implied transaction. You're free to use a transaction-per-HTTP-request scheme, or a transaction-per-invocation one (or no transaction at all). So when an invocation fail, RF doesn't assume the whole batch can be sent again.
BTW, batching things in a single HTTP request is a network optimization (less HTTP requests => better overall performance). You'd want to batch an important data update with an "unimportant" retrieval; and you wouldn't want a failing unimportant retrieval to make the important update fail as well.

Jan Marten

unread,
Nov 26, 2013, 7:56:35 AM11/26/13
to google-we...@googlegroups.com
Is there any progress on this issue?

Issue https://code.google.com/p/google-web-toolkit/issues/detail?id=5794 is related and is not fixed either.

RF's behaviour in this regard is inconsistent since an "unimportant" retrieval would fail on any ConstraintViolation
in batched requests. It should definitely be possible to batch requests but the current API is not sufficient.

A recommendation would be to pull batching out of the RequestContext interface and expose it in e.g. a RequestContextBatcher.
A RequestContext is used for one request only and is reusable on failure or violation. Thus removing append(), fire(Receiver), etc.
RequestContextBatcher is used for multiple requests.

The specific behaviour in onFailure and onViolation should be discussed further.

Transaction settings might make it necessary for all RequestContexts to be re-executed or to only re-execute the failed/violating requests.
Therefore it must allow customization by the user.

Thomas Broyer

unread,
Nov 26, 2013, 9:04:04 AM11/26/13
to google-we...@googlegroups.com


On Tuesday, November 26, 2013 1:56:35 PM UTC+1, Jan Marten wrote:
Is there any progress on this issue?

Issue https://code.google.com/p/google-web-toolkit/issues/detail?id=5794 is related and is not fixed either.

RF's behaviour in this regard is inconsistent since an "unimportant" retrieval would fail on any ConstraintViolation
in batched requests. It should definitely be possible to batch requests but the current API is not sufficient.

A recommendation would be to pull batching out of the RequestContext interface and expose it in e.g. a RequestContextBatcher.
A RequestContext is used for one request only and is reusable on failure or violation. Thus removing append(), fire(Receiver), etc.
RequestContextBatcher is used for multiple requests.

The specific behaviour in onFailure and onViolation should be discussed further.

The current API has batching built-in: one RequestContext instance == one batch request. You're free to use batches that only contain a single invocation.
 
Transaction settings might make it necessary for all RequestContexts to be re-executed or to only re-execute the failed/violating requests.
Therefore it must allow customization by the user.

RF assumptions are that:
  • for a given HTTP request, there's only one instance for each entity (an entity shouldn't have 2 instances in the same request handling; see https://code.google.com/p/google-web-toolkit/issues/detail?id=7341). That generally means using "cache" scoped at the request, which translates to using the "session per request" (aka "open session in view") pattern.
  • there's one transaction for each "service method", so that each one can fail independently of the others. If that's not what you want, then create a specific "service method" that does all your work that should be executed in a single transaction.
This is not going to change (what might change is the restriction that one proxy can only be edited by a single RequestContext at a time, which is discussed in the issue you linked to)

Jan Marten

unread,
Nov 26, 2013, 10:10:08 AM11/26/13
to google-we...@googlegroups.com
The inconsistency with the current RequestFactory is that if in a batch request, a single sub-request has a constraint violation
then onConstraintViolation is called for every sub-request and the whole batch request fails (onFailure is called).
Whereas if in a sub-request an exception is raised on the server only for this single sub-request onFailure is called and the surrounding
batch-request succeeds with onSuccess.

Thus, the RequestContext cannot be reused since "reuse" is only called for constraint violations and failures.

Hence, as described in the original post, after a server failure the proxy cannot be reused and the user-entered data is gone (in contrast to a constraint violation).

Thomas Broyer

unread,
Nov 26, 2013, 10:25:06 AM11/26/13
to google-we...@googlegroups.com


On Tuesday, November 26, 2013 4:10:08 PM UTC+1, Jan Marten wrote:
The inconsistency with the current RequestFactory is that if in a batch request, a single sub-request has a constraint violation

There's no inconsistency, because that's not how things work.

  1. All objects (entities and "value objects") are "deserialized" from the request (for entities, it involves first retrieving them from the data store)
  2. Then they're all validated. If there's a constraint violation, things stop here and you'll have onConstraintViolations on the client-side, in each and every Receiver attached to the RequestContext (i.e. or one of its Requests/InstanceRequests)
  3. Otherwise, "invocations" are processed, in the same order they were added to the RequestContext on client side. Each invocation either succeeds or fails, and the onSuccess or onFailure will be called accordingly on the client side for the corresponding Receiver. The exception raised might be a ConstraintViolationException, it doesn't change anything: onFailure (not onConstraintViolation) will be called for the appropriate Receiver (not all receivers)
  4. Then all entities (including those returned by "invocations") are checked for "liveness", to tell the client which kind of EntityProxyChange event to fire (PERSIST/UPDATE/DELETE)
  5. And finally the response is constructed, with serialized objects, etc.
 
then onConstraintViolation is called for every sub-request and the whole batch request fails (onFailure is called).
Whereas if in a sub-request an exception is raised on the server only for this single sub-request onFailure is called and the surrounding
batch-request succeeds with onSuccess.

 
Thus, the RequestContext cannot be reused since "reuse" is only called for constraint violations and failures.

Hence, as described in the original post, after a server failure the proxy cannot be reused and the user-entered data is gone (in contrast to a constraint violation).

Yes. Exceptions are meant to be "exceptional", you should use a "return value" to convey errors. In other words, onFailure should never be called, unless something unpredictable happens.

Jan Marten

unread,
Nov 26, 2013, 10:59:38 AM11/26/13
to google-we...@googlegroups.com
Thank you for clarifying the behavior.

Nevertheless, in my opinion there is a use-case where the data should be reusable after a server failure.

SQL exceptions like a unique constraint violation could be checked before sending the request but 
there might be server failures like a non-reachable database which is unpredictable.

When something like this happens one could prompt the user to retry after a few minutes.
But since the request cannot be reused the user cannot resend it's entered data.

Thomas Broyer

unread,
Nov 26, 2013, 12:33:22 PM11/26/13
to google-we...@googlegroups.com
I don't disagree. A non-reachable database is likely to trigger a "global failure" though (in steps 1 or 4 from my previous message), that, IIRC should make the RequestContext "reusable" (it does in some cases, I just can't remember which ones)
That said, you could also handle the error on the server-side and replay the request yourself (and I'm not saying it's easy!)

Arlo O'Keeffe

unread,
Nov 27, 2013, 5:21:21 AM11/27/13
to google-we...@googlegroups.com
Jan and I further looked into the issue and actually found a working solution that is "less hacky" than Eric's.

private void setupRequest(UserRequest request, UserProxy proxy) {
final UserProxy editableProxy = request.edit(proxy);
editorDriver.edit(editableProxy, request);
request.save(editableProxy).to(new Receiver<T>() {

@Override
public void onFailure(ServerFailure error) {
...
setupRequest(getRequest(), editableProxy);
}

@Override
public void onConstraintViolation(
        ...
}

@Override
public void onSuccess(T response) {
...
}

});
}

This keeps the data around and allows "re-firing" the request.

The explicit request.edit() is necessary because otherwise the entered data is lost when another failure occurs.
Reply all
Reply to author
Forward
0 new messages