Information is essential for reasonable exception handling. Unfortunately, it is not always affordable to write custom exceptions providing all necessary information, especially in test code, e.g. for selenium tests. So, here is an approach to weave informative exceptions into your [test] code.
We will use an java proxy object in order to add an aspect to every method of an interface our intercepted object implements. The corresponding interceptor implements InvocationHandler
and replaces every non-application exception with the new exception GenericException
augmenting the former with parameter information of the invoked method:
package de.jn.sandbox.proxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import javax.ejb.ApplicationException; import de.jn.sandbox.proxy.AbstractController; public class ExceptionInterceptor implements InvocationHandler { private AbstractController controller; public ExceptionInterceptor(AbstractController controller) { this.controller = controller; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { return method.invoke(controller, args); } catch(InvocationTargetException ite) { final Throwable cause = ite.getCause(); // Application exceptions should not be decorated. if(cause != null && cause.getClass().getAnnotation(ApplicationException.class) != null) { throw cause; } throw new GenericException(method, args, cause); } } }
The exception adds the parameter information to its message. It might look like this:
package de.jn.sandbox.proxy; import java.lang.reflect.Method; import java.util.Arrays; public class ControllerException extends RuntimeException { private static final long serialVersionUID = 6905430007998703680L; public ControllerClientException(final Method method, final Object[] args, final Throwable cause) { super( "An exception occured during executing '" + method.getName() + "' on '" + method.getDeclaringClass().getSimpleName() + "'.\n" + "args: " + Arrays.toString(args) , cause); } }
In order to proxy an object a new proxy instance has to be created. In this event we declare:
- for which interface (
Controller
) the methods should be intercepted, - that the
ExceptionInterceptor
serves as interceptor, and - pass an instance being wrapped (
new ControllerImpl()
).
Here is an example code snippet:
Controller controller = (Controller)Proxy.newProxyInstance( ControllerImpl.class.getClassLoader(), new Class<?>[]{ Controller.class }, new ExceptionInterceptor(new ControllerImpl()));
So, every time an exception occurs that is not an application exception, the interceptor wraps a GenericException
around it and offers the parameter information for the respective invocation.