Exception Handling




Exception Handling

Exception handling with interceptors is simple yet powerful. Since interceptors sit directly in the Java call stack of the bean method or callback that is being invoked, you can put a try/catch/finally block around the InvocationContext.proceed( ) method. You can abort an invocation before it reaches the actual bean method by throwing an exception within the @AroundInvoke or callback method. You are also allowed to catch a bean-method-thrown exception and throw a different exception, or suppress the exception. With @AroundInvoke interception, you are even allowed to retry the bean method call after catching an exception from the bean method. Let's look at some examples.

Aborting a Method Invocation

Parameter validation is business logic that checks to see that the parameters of a business method are valid values before proceeding with the logic of the method. The ProcessPayment EJB method, byCheck( ), uses validation to determine whether the CheckDO parameter has a minimum check number. Maybe we are selling our Titan Cruise reservation software as an ERP system to various cruise companies throughout the world. We may want to turn off check validation for one deployment of our ProcessPayment EJB. For another deployment, we might want to add more complex validation, such as checking the name and account number of the check against a fraud database. Interceptors give us the ability to encapsulate our validation logic in an interceptor class and to configure and apply it as needed to different deployments of our ProcessPayment EJB. Because interceptors allow you to abort the EJB method invocation in the interceptor class itself before even reaching the actual bean method, it is possible to modularize validation logic in this way:

package com.titan.interceptors;

import javax.ejb.*;
import javax.annotation.*;
import javax.interceptor.*;

public class CheckValidation 
 {
   @Resource int minimumCheckNumber; 


   @AroundInvoke
   public Object validateCheck(InvocationContext ctx) throws Exception {
      CheckDO 
 check = ctx.getParameters( )[1];
      if (check.checkNumber < minimumCheckNumber) {
         throw new PaymentException 
("Check number is too low");
      }
      return ctx.proceed( );
   }
}

This CheckValidation class has encapsulated the validation logic of the ProcessPaymentBean.byCheck( ) method. It uses the InvocationContext.getParameters( ) method to obtain the CheckDO parameter. The minimum check number is injected into the minimumCheckNumber variable of the interceptor class from an <env-entry>. The validateCheck( ) method verifies that the check number in the CheckDO parameter is greater than the minimum check number. It aborts the invocation with a PaymentException if the validation fails.

Validation is just one example of where you might want to abort an EJB invocation within an @AroundInvoke method. Another example is the case where you are implementing your own custom security framework. EJB security is pretty basic and sometimes you have larger security requirements. For instance, you may want to integrate your EJB with a rules engine that analyzes the user as well as the method and parameters to determine whether the user is allowed to invoke the method. This can also be done from within an interceptor.

Catch and Rethrow Exceptions

Besides aborting a given method invocation, you can also catch exceptions thrown by the bean method within the interceptor's @AroundInvoke method. For example, you can use interceptor classes as an abstraction mechanism to create exception-handling frameworks. Consider JDBC and the java.sql.SQLException . When an SQLException is thrown, your code, programmatically, does not know the cause of the exception without looking at the error number or the message of the exception. Unfortunately, error codes and messages differ per database vendor and thus, if you wanted to handle certain exceptions in certain ways, your code would be nonportable between database vendors.

Let's take two common SQLException s: deadlocking and cursor not available. First, we will create concrete exceptions that extend SQLException:

@ApplicationException(rollback=true)
public class DatabaseDeadlockException extends java.sql.SQLException {
   public DatabaseDeadlockException(Exception cause) {
      Super(cause);
   }
}
@ApplicationException(rollback=true)
public class DatabaseCursorNotAvailable extends java.sql.SQLException {
   public DatabaseCursorNotAvailable(Exception cause) {
      super(cause);
   }
}

With these exceptions, we have abstracted away the dependency on the error number to determine the actual database error that occurred. Our client code can use these exceptions in a portable way and not be concerned with the underlying database vendor. But before we can use these exceptions, we need to write the interceptor class that does the exception handling:

public class MySQLExceptionHandler {
   @AroundInvoke
   public Object handleException(InvocationContext ctx) Exception {
      try {
         return ctx.proceed( );
   } catch (SQLException sql) {
       int ernum = sql.getErrorCode( );
       switch(ernum) {
       case 32343:
          throw new DatabaseDeadlockException(sql);
       case 22211:
          throw new DatabaseCursorNotAvailable(sql);
       ...
       default:
          throw new RollbackAlwaysOnException(sql);
       }
   }
}

The @AroundInvoke method simply catches any SQLException thrown by the bean method and converts it to an appropriate exception type that you can catch in your client code. Of course, there would be one exception-handler interceptor class per database vendor. Here's how your application code could then take advantage of this interceptor behavior:

// application client code
{
   try {
      ejbref.invokeSomeDatabaseOperation( );
   } catch (DatabaseDeadlockException deadlock) {
      // handle this specific error case in a special way
   }
}

So, combining the exception handler interceptor with EJB invocations allows you to have specific code that handles specific database errors like deadlock, without having to worry that your code is not portable between vendors.