Having explored the GWT RPC exception limitations, on this post we will take another approach of simplifying GWT exception handling for known business types. Assuming our application has few known runtime errors we would like to be able to throw those exception without having to declare them on the RPC methods. At the same time we expect our client code to capture those errors gracefully.
The solution is a combination of steps from overriding GWT RemoteServiceServlet to injecting our supported business exceptions.
Hooking the business exception / Injecting it into GWT SerializationPolicy.
When an error is being propagated within a GWT service this error needs to be declared within the RPC method signature, otherwise GWT SerilizablePolicy marshaling will complain about the type not being recognized. Well we could easily overcome this limitation by creating a dummy hook method with only purpose of white-listing the business exception type within the given RPC signature Serialization policy:
interface AbstractRemoteServiceAsync {
void hookException(AsyncCallback<Void> callback);
}
interface AbstractRemoteService extends RemoteService {
void hookException() throws MyException;
}
class AbstractRemoteServiceServlet extends RemoteServiceServlet
implements AbstractRemoteService {
/**
* This method reson vivre is to hook our business MyException type into
* GWT Serialization policy
*/
@Override
public String hookException() throws MyException {
// Blank
}
}
/**
* The business exception
*/
public class MyException extends RuntimeException {
private static final long serialVersionUID = 1L;
}
Overriding RemoteServiceServlet default exception flow.
Let’s open the RemoteServiceServlet implementation. One of the many things this class do is, capturing any failures and handling them appropriately. Well this is exactly what we need – change the default exception handler. Luckily GWT team designed this class to follow a template method pattern with hook methods ready for extending. What we will acomplish is extending RemoteServiceServlet class and inject a behaviour handling our business exceptions (code in red).
And this is my humble contribution to GWT community. All sources and project are hosted at Google Code:
/**
* This class overrides RemoteServiceServlet to provide automatic exception
* handling for known business exceptions
*/
public class AbstractRemoteServiceServlet extends RemoteServiceServlet
implements AbstractRemoteService {
/**
* Overriding method so we can inject business exception type discovery
* and handling
*/
@Override
public String processCall(String payload) throws SerializationException {
try {
RPCRequest rpcRequest = RPC.decodeRequest(payload, this.getClass(),
this);
return handleProcessCall(this, rpcRequest);
} catch (IncompatibleRemoteServiceException ex) {
log("Incompatible error was thrown while processing this call.", ex);
return RPC.encodeResponseForFailure(null, ex);
}
}
/**
* Tis method digest the given throwable and tries to find any know business
* exception. When a known business error is found it is being flushed (the
* business type) to response stream, thus client callback receives the known
* type but not the default GWT UnexpectedException.
*/
@Override
protected void doUnexpectedFailure(Throwable e) {
discoverAndWriteError(e, e);
}
/**
* This method reson vivre is to hook the recognized MyException type into
* GWT Serialization policy
*/
@Override
public void hookException() throws MyException {
// Blank
}
/**
* Method handles GWT RPC call and on failure it digests the error,
* finds supported business exception and handles them gracefully.
*/
private String handleProcessCall(Object target, RPCRequest rpcRequest)
throws SerializationException {
Method serviceMethod = rpcRequest.getMethod();
Object[] params = rpcRequest.getParameters();
SerializationPolicy serializationPolicy = rpcRequest
.getSerializationPolicy();
try {
return RPC.invokeAndEncodeResponse(target, serviceMethod, params,
serializationPolicy);
} catch (UnexpectedException ex) {
Throwable cause = ex.getCause();
// This could be a list of supported types ..etc..
// Leaving it up to your imagination
if (cause.getClass().equals(MyException.class)) {
throw new MyException();
}
return encodeResponse(cause.getClass(), cause, true,
serializationPolicy);
}
}
/**
* This method recursively iterates through given 'masterError'
* finding any supported failures and handling the latter
* appropriately.
*/
private void discoverAndWriteError(Throwable masterError, Throwable e) {
if (e == null) {
writeError(new UnexpectedException(masterError.getMessage(),
masterError));
return;
}
Throwable cause = e.getCause();
if (cause == null) {
writeError(new UnexpectedException(masterError.getMessage(),
masterError));
return;
}
// This could be a list of supported types, etc..
// Leaving it up to your imagination
if (cause.getClass().equals(MyException.class)) {
writeError(new MyException());
return;
} else {
// continue with 'recursion' cause discovery
discoverAndWriteError(masterError, cause);
}
}
/**
* This method writes known business exception the response HTTP packet
* Code borrowed from com.google.gwt.user.server.rpc.RPCServletUtils
*/
private void writeError(Throwable cause) {
// Send SC_OK/200 status, serialize the gwt error, flush it to response,
// and let the client deal with the business error
HttpServletResponse resp = getThreadLocalResponse();
try {
resp.setContentType("text/plain");
resp.setStatus(HttpServletResponse.SC_OK);
String excMsg = RPC.encodeResponseForFailure(null, cause);
resp.getWriter().write(excMsg);
} catch (IOException e) {
log("Failed to send failure to client", e);
} catch (SerializationException e) {
log("Failed to send failure to client", e);
}
}
/**
* Borrowed from RPC.java. Returns a string that encodes the results of an
* RPC call. Private overload that takes a flag signaling the preamble of
* the response payload.
*/
private static String encodeResponse(Class responseClass, Object object,
boolean wasThrown, SerializationPolicy serializationPolicy)
throws SerializationException {
ServerSerializationStreamWriter stream = new
ServerSerializationStreamWriter(serializationPolicy);
stream.prepareToWrite();
if (responseClass != void.class) {
stream.serializeValue(object, responseClass);
}
String bufferStr = (wasThrown ? "//EX" : "//OK") + stream.toString();
return bufferStr;
}
}
With the new RemoteService classes being created above, our GWT Services will derive from them as bellow:
@RemoteServiceRelativePath("greet")
interface GreetingService extends AbstractRemoteService {
/* Notice, no exception is being declared*/
String greetServer(String name);
}
interface GreetingServiceAsync extends AbstractRemoteServiceAsync {
/* Notice, no exception is being declared*/
void greetServer(String input, AsyncCallback callback);
}
class GreetingServiceImpl extends AbstractRemoteServiceServlet implements
GreetingService {
/* Notice, no exception is being declared*/
public String greetServer(String input) {
{
throw new MyException();
}
}
}
The last piece of course is executing the service and see it in real browser:
Feel free to explore project sources at Google Code.



