Exception Advice: An Aspect-Oriented Model
Pages: 1, 2

Hitting the Fault Barrier

The ExceptionHandling aspect ensures that a FaultException always reaches the Fault Barrier, regardless of how the intervening code behaves. This guarantees that the Fault Barrier always gets to terminate the processing sequence in an orderly way. It frees the intervening code from worrying about inadvertently catching a FaultException. This advice makes these exceptions "slippery"; they cannot be caught by any handler outside of a Fault Barrier. The allHandlers() pointcut applies to every exception handler in the application and makes the class containing the handler and the exception being handled available to the before() advice logic. The advice executes before the code in the exception handler. The advice takes no action unless the exception is a FaultException. For a FaultException, the advice checks that the handler is within a class designated as a Fault Barrier. If so, the handler is allowed to catch the FaultException. If not, the FaultException is thrown again, bypassing the handler that was going to catch it. Eventually, the FaultException will reach a handler within a designated Fault Barrier class.

public abstract aspect ExceptionHandling {

  ...



  pointcut allHandlers(Object handlerType, Throwable throwable):

          handler(Throwable+) && args(throwable) && 

          this(handlerType);



  before(Object handler, Throwable throwable):

      allHandlers(handler, throwable) {

      if (throwable instanceof FaultException) {

          if (!(isFaultBarrier(handler))) {

              FaultException fault = (FaultException) throwable;

              throw (fault);

          }

      }

  }



  abstract boolean isFaultBarrier(Object exceptionHandler);



  ...

}

Listing 4. Only the Fault Barrier catches faults

How does the aspect know if a handler is within a designated Fault Barrier? One way to accomplish it would be to hard-code the class names of designated Fault Barrier classes into the ExceptionHandling aspect. But that would tie the aspect to a particular application. To make the ExceptionHandling aspect as flexible as possible, it declares an abstract method to answer the Fault Barrier question. The implementation of isFaultBarrier() is supplied in a subaspect that knows the specifics of the application and is able to judge if a handler object is a Fault Barrier or not. That means that ExceptionHandling must be declared as an abstract aspect. It must be extended by a concrete subaspect before any of its advice will be activated. The subaspect needs to supply only an implementation for isFaultBarrier() plus one other method that will be discussed next.

Better Fault Diagnostics

The ExceptionHandling aspect described so far ensures that every uncaught exception thrown by the application will arrive at the Fault Barrier as a FaultException. This applies to unexpected exceptions from Java library methods, accidental exceptions caused by bugs in application code, and FaultExceptions explicitly thrown when fault conditions are discovered. The Fault Barrier needs to catch only the FaultException instead of any Throwable, as required by the traditional implementation. Application code can be structured in any way that seems natural, without considering how it might affect the application's fault handling capacity.

That is a nice advantage of the aspect-oriented approach. But the ExceptionHandling aspect really proves its worth in the quality of diagnostic information it can provide when a fault occurs. An aspect can observe the entire application as it runs. The ExceptionHandling aspect uses this ability to trace the arguments passed to every method and constructor in the application. When a fault occurs, the aspect appends a special Application Trace section to the standard exception and stack trace information it records. Each item in the Application Trace describes the type of processing, the name of the class, method, or constructor, and the names, types, and values of the arguments used to invoke it. The result looks like this:

FATAL : exception.ServiceExceptionHandling - Application Fault Detected
                        
exception.FaultException: Unexpected failure on catalog query: com.ibm.db2.jcc.b.SQLException: The string constant beginning with "'" does not have an ending string delimiter.
at domain.CatalogDAO.performQuery(CatalogDAO.java:86)
at domain.CatalogDAO.getCatalogEntries(CatalogDAO.java:57)
at domain.CatalogService.getCatalogEntry(CatalogService.java:16)
at domain.CartItem.<init>(CartItem.java:22)
at domain.ShoppingCart.addToCart(ShoppingCart.java:28)
at action.SelectItemAction.performAction(SelectItemAction.java:44)
at action.BaseAction.execute(BaseAction.java:57)
at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:484)
at org.apache.struts.action.RequestProcessor.process(RequestProcessor.java:274)
at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1482)
at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:525)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:763)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:856)
at weblogic.servlet.internal.StubSecurityHelper$ServletServiceAction.run(StubSecurityHelper.java:225)
at weblogic.servlet.internal.StubSecurityHelper.invokeServlet(StubSecurityHelper.java:127)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:283)
at weblogic.servlet.internal.ServletStubImpl.execute(ServletStubImpl.java:175)
at weblogic.servlet.internal.WebAppServletContext$ServletInvocationAction.run(WebAppServletContext.java:3214)
at weblogic.security.acl.internal.AuthenticatedSubject.doAs(AuthenticatedSubject.java:321)
at weblogic.security.service.SecurityManager.runAs(SecurityManager.java:121)
at weblogic.servlet.internal.WebAppServletContext.securedExecute(WebAppServletContext.java:1983)
at weblogic.servlet.internal.WebAppServletContext.execute(WebAppServletContext.java:1890)
at weblogic.servlet.internal.ServletRequestImpl.run(ServletRequestImpl.java:1344)
at weblogic.work.ExecuteThread.execute(ExecuteThread.java:209)
at weblogic.work.ExecuteThread.run(ExecuteThread.java:181)

Application Trace:
method-execution: domain.CatalogDAO.performQuery(query:java.lang.String=SELECT * FROM ADMINISTRATOR.CATALOG WHERE CATALOGID = 'BAD'INPUT', transaction:integration.Transaction=jdbc:db2://localhost:50000/DOCMGMT)
method-execution: domain.CatalogDAO.getCatalogEntries(catalogID:java.lang.String=BAD'INPUT, transaction:integration.Transaction=jdbc:db2://localhost:50000/DOCMGMT)
method-execution: domain.CatalogService.getCatalogEntry(catalogID:java.lang.String=BAD'INPUT)
constructor-execution: domain.CartItem.<init>(catalogID:java.lang.String=BAD'INPUT, quantity:int=1)
initialization: domain.CartItem.<init>(catalogID:java.lang.String=BAD'INPUT, quantity:int=1)
method-execution: domain.ShoppingCart.addToCart(catalogID:java.lang.String=BAD'INPUT, quantity:int=1)
method-execution: action.SelectItemAction.performAction(cart:domain.ShoppingCart=Cart0024988, action:org.apache.struts.action.ActionMapping=ActionConfig[path=/SelectItem,name=SelectItemForm,scope=session,type=action.SelectItemAction, form:org.apache.struts.action.ActionForm=CatalogID-BAD'INPUT Quantity-1, request:javax.servlet.http.HttpServletRequest=Http Request: /ShoppingServices/SelectItem.do, response:javax.servlet.http.HttpServletResponse=weblogic.servlet.internal.ServletResponseImpl@22a6c2)
method-execution: action.BaseAction.execute(action:org.apache.struts.action.ActionMapping=ActionConfig[path=/SelectItem,name=SelectItemForm,scope=session,type=action.SelectItemAction, form:org.apache.struts.action.ActionForm=CatalogID-BAD'INPUT Quantity-1, request:javax.servlet.http.HttpServletRequest=Http Request: /ShoppingServices/SelectItem.do, response:javax.servlet.http.HttpServletResponse=weblogic.servlet.internal.ServletResponseImpl@22a6c2)
Listing 5. The result of the Application Trace

The Application Trace contains only those methods that are subject to the ExceptionHandling aspect's influence: the methods that are specifically part of our application. Notice that the items in the Application Trace correspond roughly to the entries at the top of the stack trace. (The bottom half of the stack trace covers classes that are part of the WebLogic Server implementation.) The example here comes from a Struts application that permitted a user to supply a form parameter ( BAD'INPUT) containing a single quote character that caused a syntax error in an SQL statement. Having the argument values visible in the diagnostic recording helps determine when things went wrong. There is a good bit of code in the ExceptionHandling aspect revolving around fault recording. To begin, take a look at how the aspect controls how faults are recorded.

public abstract aspect ExceptionHandling {

  ...



  private boolean FaultException.logged = false;



  private boolean FaultException.isLogged() {

      return this.logged;

  }



  private void FaultException.setLogged() {

      this.logged = true;

  }



  after() throwing(FaultException fault): exceptionAdvicePoints(){

      if (!fault.isLogged()) {

          logFault(fault);

          fault.setLogged();

      }

  }



  ...

}

Listing 6. Fault logging advice

The after-throwing advice runs whenever a FaultException is thrown from any point in the application. Its job is to invoke the aspect's logFault() method, which does the actual recording. A single fault may trigger the advice multiple times as it propagates through all of the methods on the call stack, so the advice needs a way to know when the recording has already been done. To do this, it uses another AOP technique: member introduction. The aspect introduces a boolean flag into the FaultException type along with methods to access it. The flag and methods are appropriately marked private; they are invisible outside of the ExceptionHandling aspect. The overall effect is that diagnostic recording happens immediately after a fault occurs and at no other time.

A fault can occur at any instant. To prepare for that eventuality, the ExceptionHandling aspect needs to track activity as the application runs. That way, it is ready to record the sequence of calls leading to the fault, along their argument values, should a fault occur. To do this, the aspect maintains a per-thread stack of JoinPoint object references. The aspect's trace stack grows and shrinks in parallel with the call stack as the application executes. The AspectJ runtime makes JoinPoint objects available to advice logic through the language construct thisJoinPoint. JoinPoint objects contain dynamic context information for advice logic, allowing the logic to learn specifics about the situation that triggered the advice.

public abstract aspect ExceptionHandling {

  ...

 

  private static ThreadLocal<Stack<JoinPoint>> traceStack = 

                         new ThreadLocal<Stack<JoinPoint>>() {

      protected Stack<JoinPoint> initialValue() {

          return new Stack<JoinPoint>();

      }

  };

 

  private static void pushJoinPoint(JoinPoint joinPoint) {

      traceStack.get().push(joinPoint);

  }

 

  private static JoinPoint popJoinPoint() {

      Stack<JoinPoint> stack = traceStack.get();

      if (stack.empty()) {

          return null;

      } else {

          JoinPoint joinPoint = stack.pop();

          return joinPoint;

      }

  }

 

  private static JoinPoint[] getJoinPointTrace() {

      Stack<JoinPoint> stack = traceStack.get();

      return stack.toArray(new JoinPoint[stack.size()]);

  }

 

  ...

}

Listing 7. ThreadLocal call tracing methods

With these methods in place, the advice that tracks application calls is very simple. The join points that are pushed onto the stack are those identified by the exceptionAdvicePoints() pointcut—any execution sequence that can terminate abruptly due to an exception. Before the sequence starts, the JoinPoint object is pushed onto the thread's trace stack. After the sequence is complete, its JoinPoint object is popped from the stack. The JoinPoint objects in the trace stack are never dereferenced unless a fault occurs.

public abstract aspect ExceptionHandling {

        ...



    before(): exceptionAdvicePoints(){

        pushJoinPoint(thisJoinPoint);

    }



    after(): exceptionAdvicePoints(){

        popJoinPoint();

    }



        ...

}

Listing 8. Call tracing advice

When a fault happens, the advice in Listing 6 runs, invoking the methods below to render diagnostics. The information comes from the stack trace contained in the FaultException and from the aspect's own per-thread stack of join points. The formatJoinPoint() method extracts what we want from each JoinPointobject: the qualified method or constructor name, the names and types of its formal parameters, and the values passed as arguments for those parameters.

public abstract aspect ExceptionHandling {

  ...

 

  private void logFault(FaultException fault) {

      ByteArrayOutputStream traceInfo = 

                                 new ByteArrayOutputStream();

      PrintStream traceStream = new PrintStream(traceInfo);

      fault.printStackTrace(traceStream);

      StringBuffer applicationTrace = new StringBuffer();

      JoinPoint[] joinPoints = getJoinPointTrace();

      for (int i = joinPoints.length - 1; i >= 0; i--) {

          applicationTrace.append("\n\t"

                          + formatJoinPoint(joinPoints[i]));

      }

      recordFaultDiagnostics("Application Fault Detected"

                      + "\n" + traceInfo.toString()

                      + "\nApplication Trace:"

                      + applicationTrace.toString());

  }

 

  abstract void recordFaultDiagnostics(String diagnostics);

 

  private String formatJoinPoint(JoinPoint joinPoint) {

      CodeSignature signature = (CodeSignature) 

                                  joinPoint.getSignature();

      String[] names = signature.getParameterNames();

      Class[] types = signature.getParameterTypes();

      Object[] args = joinPoint.getArgs();

      StringBuffer argumentList = new StringBuffer();

      for (int i = 0; i < args.length; i++) {

          if (argumentList.length() != 0) {

              argumentList.append(", ");

          }

          argumentList.append(names[i]);

          argumentList.append(":");

          argumentList.append(types[i].getName());

          argumentList.append("=");

          argumentList.append(args[i]);

      }

      StringBuffer format = new StringBuffer();

 

      format.append(joinPoint.getKind());

      format.append(": ");

      format.append(signature.getDeclaringTypeName());

      format.append(".");

      format.append(signature.getName());

      format.append("(");

      format.append(argumentList);

      format.append(")");

      return format.toString();

  }

 

}

Listing 9. Fault logging methods

The ExceptionHandling aspect defines the abstract method recordFaultDiagnostics() to allow an application to specify how it wants to record the diagnostic information produced by the aspect. An application supplies an implementation for that method in a concrete subaspect. This arrangement keeps the recording specifics out of the base aspect, keeping it as flexible as possible.

The ability of an aspect to observe the rest of the application enables it to furnish these extended diagnostics when a fault arises. It does this job without the knowledge or cooperation of other application components. Concentrating the code that implements the concern of diagnostic reporting in one place is one of the benefits of the aspect-oriented approach.

Conclusion

The Fault-Contingency exception model makes sense for lots of Java applications. Implementing the concerns of the model using AOP techniques has some nice advantages. The ability to detect departures from the model at compile time is one of them. Isolating the logic related to fault processing in one place is another. Thinking in terms of Faults and Contingencies eliminates lots of confusing code in your application. Thinking in terms of aspects makes the application code even simpler, reducing the chance of inadvertent errors creeping into code. Is this inspiring enough to consider AOP? Only you can decide.

References

  • A more thorough discussion of the Fault-Contingency exception model can be found in the Dev2Dev article Effective Java Exceptions.
  • The Eclipse AspectJ project site is the jump point for all things AspectJ.
  • The AspectJ Programming Guide contains a full description of the concepts and constructs of the AspectJ language.
  • The Dev2Dev article JRockit JVM Support For AOP includes a short introduction to AOP and AspectJ.

Barry Ruzek has been named a Master Certified IT Architect by the Open Group. He has over 30 years of experience developing operating systems and enterprise applications.