Introduction

Interceptors are objects that sit between the implementation of a method and the interface via which that method is called. is An Interceptor is a component through which a call to a method will pass. The method invocation will first pass from Caller to the Interceptor and then from the Interceptor to the Target method and then back through the Interceptor to the Caller.

An Interceptor Chain or Stack is a series of interceptors through which an invocation will pass on way down to the method. After the method completes the invocation will pass back through the chain of Interceptors in the reverse order of which they were called.

Figure 1 displays such a situation. The Caller invokes a method, it passes through several Interceptors before invoking the Target method and then it passes back through all the Interceptors to the Caller.

An Interceptor can be called simultaneously by multiple threads and by multiple clients. Thus information pertaining to the particular call needs to be stored either in ThreadLocal variables (if it does not need to be shared) or in the InvocationContext (if it may need to be accessed by other Interceptors). The InvocationContext is where all the information relating to a particular call is stored. The Context may also give access to information in different scopes (such as per object or per session).

Basic Example

A basic Interceptor is shown in figure 2. It gets the time before and after the call and displays the duration of the call to standard output. It demonstrates the basic format of an Interceptor. Usually an Interceptor will execute some operations before a call is made on Target method and after a call is made.

public class MyTimingInterceptor
  implements Interceptor
{
  public Object invoke( Invocation invocation,
                        InvocationContext ctx,
                        InvocationChain chain )
  {
    final long start = System.currentTimeInMillis();
    final Object result = chain.invokeNext( invocation, ctx );
    final long end = System.currentTimeInMillis();
    System.out.println( "Invocation duration: " + (end - start) );

    return result;
  }
}
        

Context Using Example

Another example is shown in figure 2. It sets the ContextClassLoader prior to calling the method and then resets it to original value before returning to caller. Note that this assumes that the Target method is in the same thread as the Interceptor which will be the case unless a Interceptor later in the chain changes threads. This Interceptor also demonstrates that values can be retrieved from the InvocationContext. The specific values that are available to an Interceptor are determined by the host application server. In the case of Phoenix see X.

public class MyClassLoaderInterceptor
  implements Interceptor
{
  public Object invoke( Invocation invocation,
                        InvocationContext ctx,
                        InvocationChain chain )
  {
    //Retrieve the ClassLoader object from context.
    //Note that the set of keys and values in context is
    //container dependent. See Container documentation for relevent
    //set of attributes that are valid
    final ClassLoader classLoader =
          (ClassLoader)ctx.get( "classLoader" );
    final ClassLoader oldClassLoader =
	  Thread.currentThread().getContextClassLoader();
    Thread.currentThread().setContextClassLoader( classLoader );

    final Object result = chain.invokeNext( invocation, ctx );

    Thread.currentThread().setContextClassLoader( oldClassLoader );

    return result;
  }
}
          

Constructing Interceptor Chains

There are numerous policies via which Interceptor chains could be created. One such mechanism is to construct an interceptor chain based on particular objects Attributes . Other policies include constructing chains in preconfigured arrangments or by using configuration files such as;

<interceptor-chains>

<intercetor-chain name="MyInterceptorChain">

  <!-- Log the call for debugging purposes -->
  <interceptor type="org.apache.avalon.LogInterceptor"/>

  <!-- Make sure the call is authorized to execute method
       and that correct principle has been setup. -->
  <interceptor type="org.apache.avalon.AuthorizeInterceptor"/>

  <!-- Charge caller for use of the service -->
  <interceptor type="org.apache.avalon.AccountingInterceptor">
    <!-- configuration passed to the Interceptor. It costs
         2c per call -->
    <cost>0.02</cost>
  </interceptor>

  <!-- Make sure the ThreadContext data (like ContextClassLoader)
       is setup properly -->
  <interceptor type="org.apache.avalon.ThreadContextInterceptor"/>

 </intercetor-chain>

</interceptor-chains>
            

Example Interceptors

Transaction : Manage transaction state in a way similar to EJB declarative transaction "attributes". May have mandatory, incompatible etc and can result in commit or rollback on failure etc.

Security : Make sure the caller has the right permissions, the caller has principle correctly setup and the method is invoked as correct subject.

Audit : Record who, when, what and where a method is called.

Application Isolation : Make sure caller context does not interfere with context of called method. This includes managing things like thread names, ContextClassLoader etc.

Stale References : Make sure stale references are not used. ie If an object has been disposed of make sure that no one trys to call the object again.

Pool Objects : Objects may be pooled with a particular policy. ie The target object may be retrieved from a pool prior to method invocation and then returned to pool after invocation.

Passivate/Activate Objects : Objects may be passivated (serialized to disk) if not used recently and then activated (deserialized from disk) when needed.

Lazy Creation : Make sure objects are created and properly initialized before they can be accessed.

Binding Objects : Bind objects into a name service or registry (ie JMX, JNDI, LDAP, RMI registry etc) the first time they are accessed.

Remoting Objects : Make sure object is remoted via RMI, SOAP, AltRMI.

Sub-Component Activator : Make sure that the first time a component is accessed, that all it's sub-components are activated.