Integrating ExtJS with Seam: Error Handling

If you had read the documentation for the SeamRemotingJsonReader in the code from my post Integrating ExtJS with Seam: Loading stores with Json, you might have noticed mention to error handling in Seam's Remoting framework. Seam is a relatively new framework so it should be expected that there are going to be peripheral areas of it that are still being fleshed out. Error handling in the Remoting framework is one place that the Seam team has recognized as an area that needs some attention.

Let's face it, there are going to be exceptions (bugs, unavailable resources, etc) that will be encountered while processing web requests. Typically these are few and far between, but non the less they can occur. And when they do the client logic needs to be able to gracefully recover and in some cases notify the user that there request could not be processed due to an error on the server.

With tradition Ajax based interactions, the client logic can determine whether the request was processed successfully or not by inspecting the returned Http status. A status of 200 (OK) means, well that is pretty self explanatory, while all others status indicate some other type of failure either on the client or the server.

Seam Remoting is an RPC style abstraction to the XHR. It provides a callback facility where the response is passed. When everything goes well the callback will be invoked, but when things go bad, ie exceptions are raised on the server, the registered callback is never invoked. I brought this up on the Seam forums and found out that this, along with a few other related isses, are known issues. In any case this presents a real problem for my integration solution with ExtJS. Stores fire three events that client listeners can register to take action upon. One of which is 'loadexception'. This is the event that is typically fired when an Ajax request in ExtJs receives a 500 response status. With the current version of Seam we need to add some addition server side code to @WebRemote methods to ensure that our event consumers don't starve, and things like loading indicators don't spin forever making users dizzy.

The solution is really simple. We need to add 'around advice' on the methods that are annotated as @WebRemote. This interceptor does nothing more than put a try catch block around the invocation and returns a string in the case that an exception did propagate out of the Action method. So I guess it's about time to look at the code for the interceptor:

/**
 * Basic try/catch interceptor to deal with exceptions in the Seam Remoting
 * framework.  When exceptions propagate out of methods that are annotated
 * as WebRemote the callbacks on the client side of the call are not invoked.
 * To deal with this deficiency this interceptor will return a static Json String
 * when an exception is caught, '{exception:true}'.  This will tell the client
 * side SeamRemotingJsonReader that an error occured which will ultimately result
 * in the Store firing a 'loadexception' event.      
 */
@Interceptor
public class JsonResultInterceptor {
    @AroundInvoke
    public Object invoke(InvocationContext invocation) throws Exception {
        try { return invocation.proceed(); } 
        catch (Exception e) {
            return "{exception:true}";
        }
    }
}

I created a simple annotation so that the interceptor can be applied declaratively. This annotation is intended to be targeted at a Method, however Seam doesn't currently support Method level interceptors, only class level.

/**
 * Shortcut annotation for applying the JsonResultInterceptor to types and methods. 
 * The intended target is really for Methods, however the Seam interception framework
 * currently only supports custom interceptors at the Type level. 
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Interceptors(JsonResultInterceptor.class)
public @interface JsonResult 
{ }

And finally here's a sample POJO action to show how the annotation is applied.

@JsonResult
@Name("BizService")
public class BizService {
    @WebRemote
    @JsonResult
    public String someMethodThatReturnsJson() {
    	return 
    	    new StringBuffer("{data: [")
	    	    .append("{company:'3m Co',price:71.72,change:0.02,pctChange:0.03,lastChange:'9/1 12:00am'},")
	    	    .append("{company:'Alcoa Inc',price:83.81,change:0.28,pctChange:0.34,lastChange:'9/1 12:00am'},")
	    	    .append("{company:'Altria Group Inc',price:83.81,change:0.28,pctChange:0.34,lastChange:'9/1 12:00am'},")
	    	    .append("]}")
	    	    .toString();
    }        
}