In my opinion, it's currently unnecessarily difficult to integrate basic 
transaction management with GWT-RPC.

Usually, in a web application it's best to start a transaction, when a call 
arrives at the server, and to commit it, when the server has finished 
processing and created the response. For GWT-RPC, this would mean to wrap 
all service methods with transaction handling code.

Of course, it's possible to write each method like

   public String greetServer(final String input) throws 
IllegalArgumentException 
{
     beginTx();
     try {
       String result = doGreetServer(input);
       commit();
     } catch (Throwable t) {
       rollback();
       throw t;
     }
   }

But you definitely want to eliminate that boilerplate. So you can override 
RemoteServiceServlet's processCall() method. But look at the code:

  public String processCall(String payload) throws SerializationException {
    // First, check for possible XSRF situation
    checkPermutationStrongName();

    try {
      RPCRequest rpcRequest = RPC.decodeRequest(payload, 
delegate.getClass(), this);
      onAfterRequestDeserialized(rpcRequest);
      return RPC.invokeAndEncodeResponse(delegate, rpcRequest.getMethod(),
          rpcRequest.getParameters(), rpcRequest.getSerializationPolicy(),
          rpcRequest.getFlags());
    } catch (IncompatibleRemoteServiceException ex) {
      log(
          "An IncompatibleRemoteServiceException was thrown while 
processing this call.",
          ex);
      return RPC.encodeResponseForFailure(null, ex);
    } catch (RpcTokenException tokenException) {
      log("An RpcTokenException was thrown while processing this call.",
          tokenException);
      return RPC.encodeResponseForFailure(null, tokenException);
    }
  }

The straightforward way would be to override it like

  public String processCall(String payload) throws SerializationException {
    beginTx();
    try {
      super.processCall(payload);
      commit();
    } catch (Throwable t) {
      rollback();
      /* Optionally perform additional logging etc. here */
      throw t;
    }
  }

But that doesn't work reliably, because IncompatibleRemoteServiceException 
and RpcTokenException will escape, i.e. they won't reach the rollback(). 
>From the Javadoc of java.sql.Connection.close(): 

It is <b>strongly recommended</b> that an application explicitly commits or 
> rolls back an active transaction prior to calling the <code>close</code> 
> method.  If the <code>close</code> method is called and there is an active 
> transaction, the results are implementation-defined.


So all that can be currently done is copying and pasting the entire 
processCall() method to a subclass, replacing "delegate" (unfortunately 
private) with "this" (a hack!), and adding the transaction handling code 
there. Other options are of course using a dispatch approach like 
gwt-dispatch <http://code.google.com/p/gwt-dispatch/>, or maybe AOP - but 
this is not always desirable.

What I would recommend is simply changing processCall() to:

  public String processCall(final String payload) throws 
SerializationException {
    // First, check for possible XSRF situation
    checkPermutationStrongName();

    try {
      final RPCRequest rpcRequest = RPC.decodeRequest(payload, 
delegate.getClass(), this);
      return doProcessCall(rpcRequest);
      
    } catch (final IncompatibleRemoteServiceException ex) {
      log(
          "An IncompatibleRemoteServiceException was thrown while 
processing this call.",
          ex);
      onAfterRequestDeserialized(rpcRequest);
      return RPC.encodeResponseForFailure(null, ex);
    } catch (final RpcTokenException tokenException) {
      log("An RpcTokenException was thrown while processing this call.",
          tokenException);
      return RPC.encodeResponseForFailure(null, tokenException);
    }
  }

  protected String doProcessCall(final RPCRequest rpcRequest) throws 
SerializationException {
    return RPC.invokeAndEncodeResponse(delegate, rpcRequest.getMethod(),
        rpcRequest.getParameters(), rpcRequest.getSerializationPolicy(),
        rpcRequest.getFlags());
  }

Then, it's easy for everyone to override doProcessCall like:

  public String doProcessCall(RPCRequest rpcRequest) throws 
SerializationException {
    beginTx();
    try {
      super.doProcessCall(rpcRequest);
      commit();
    } catch (Throwable t) {
      rollback();
      /* Optionally perform additional logging etc. here */
      throw t;
    }
  }

This would be a very simple change, and it would allow to do all kinds of 
things at the beginning and end of each service method, as well as 
handling/logging top level Exceptions in a central place.

-- 
http://groups.google.com/group/Google-Web-Toolkit-Contributors

Reply via email to