Tuesday, October 06, 2009

Enhance Exceptions with Context

Don't you wish sometimes that Java had the feature for exceptions to automatically capture the values of the variables in the context that were present when an exception occurs? Well I guess we just have to do it manually. Let's see what it would look like:

public class SomeClass {
public void someMethod(String x) {
try {
int pos = x.indexOf("abc");
String y = x.susstring(pos);
} catch (RuntimeException e) {
throw new RuntimeException(
"there was a problem here, with param x="
+ x, e);
}
}
}

Ok, now whenever someone calls someMethod("noa-b-c"); they will get a friendly message in the exception telling them what the value of x was during the invocation. That wasn't too bad, but what if the method had 3 parameters? What if you didn't have the try block around the whole body and it still threw an exception?

There must be a better way? I don't want to always tell the computer how to do things, how come I can't just say what I want? I want to tell Java that when an exception happens in my method, it should capture the context and chain the original exception. I will just use an annotation to tell Java to do this for me.
public class SomeClass {
@ThrowsContext
public void someMethod(String x) {
int pos = x.indexOf("abc");
String y = x.substring(pos);
}
}

There, that is better! Um, wait, how is that going to work? Now I am going to have to make something that will process that annotation and put the try catch there and include the parameter values. I could run my code in some sort of container that I can proxy the object of SomeClass and in the proxy I can add this new feature, then I need to make sure that all my clients to this method go through the proxy, then oops, I need to use CGLIB because I didn't use an interface, man, that is goign to all be easy.

There must be a better way! AspectJ is still Java, let's see if that wil work. We can create a pointcut for methods that have the @ThrowsContext annotation, and after the method throws an exception, we will chain it with our exception with a message of the context and chain teh original exception.

public aspect ExceptionContext {
pointcut ctx(ThrowsContext t, Object th) :
execution(@ThrowsContext * *..*(..)) &&
@annotation(t) &&
this(th);

after(ThrowsContext t, Object th)
throwing(RuntimeException e) : ctx(t, th) {
Object[] args = thisJoinPoint.getArgs();
throw new RuntimeException("parameters: " +
Arrays.toString(args) + "\nthis=" +th, e);
}
}

That is pretty to the point, it says what I want to do which makes programming more productive and fun. Maybe you don't want the context to leak out of your components, but you need this information to support debugging durign development, you can use LTW Load Time Weaving supported by AspectJ to only add this aspect behavior during deployments that you decide should have it.

Next time we will enhance the context and provide options to which context to include.


And here is the annotation:
@Retention(value=RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ThrowsContext {
}

Labels