ONJava.com -- The Independent Source for Enterprise Java
oreilly.comSafari Books Online.Conferences.

advertisement

AddThis Social Bookmark Button

JBoss: An In-Depth Look at the Interceptor Stack

by Andreas Schaefer
07/24/2002

A year ago, I wrote an article about JBoss 2.x for the 2001 O'Reilly Open Source Conference. There, I showed how JBoss enables you to create a new DataSource without bouncing the server -- just through the HTML management front-end. The only drawback was that you had to load the JDBC driver when the JBoss server was started.

In JBoss 3, this is history; you can deploy and undeploy libraries at runtime, including the classes they contain. For example, assume the running JBoss server needs to provide Postgres connections (new client or new project) on a high-availablity server, but the JDBC driver was not available or is out of date. Now you have to do the following:

  1. Copy the Postgres JDBC-Driver library into the JBoss deploy directory.
  2. Take the postgres-server.xml file (from JBoss/docs/examples/jca directory) and adjust the settings to your needs -- JNDI name, user name, password, etc.
  3. Copy this postgres-service.xml file into the deploy directory.

Now you can deploy applications using this DataSource. This also works when you update libraries and/or service.xml files to redeploy them.

Interceptor Stack

In JBoss a few years ago, the challenge was to wrap the Enterprise JavaBeans (EJB) services -- like transaction, security, CMP and more -- around a client's call. But Rickard Oeberg didn't want to create a static construct, but a flexible and, for the user, easy-to-change implementation. So he used an "Interceptor Stack" as the base of the implementation; this not only survived into the current JBoss 3 implementation but also was extended and used more often.

Theory

An Interceptor Stack is a stack of stateless components in which every call proceeds through the stack from first to last, until finally the target is called. After the target is finished with its method, the call will unwind through the stack in reverse order.

Because Interceptors are stateless, information about the state of the call then must be added to the context of the calling thread. So all of the information is piggybacked on the thread context, and each Interceptor can read, add, change, or remove data from the thread context. Figure 1 is a simple diagram of an Interceptor Stack embedded in a container like, in JBoss, the EJB Container.


Figure 1. Diagram of Interceptor stack.

Note that the last Interceptor is the "Container Interceptor" and is added to the stack by the container itself. Its only task is to return the call to the container, thus the user does not have to indicate in an Interceptor that it is the last one in the stack.

The code of an Interceptor looks like this:

   public Object invoke( final Invocation mi )
      throws Exception
   {
      Object lReturn;
      
      // PERFORM the INBOUND Tasks and when done go to the next
      //    If the call has to be ended throw the appropriate exception
      
      // Call next Interceptor in the stack
      lReturn = getNext().invoke( mi );
      
      // PERFORM the OUTBOUND Tasks and when done return method
      //    If the call shouldn't be complete throw and exception
      
      return lReturn;
      
   }

As you see, the Interceptor contains four parts:

  1. Perform inbound operations. Whenever the Interceptor has to cancel the call, it will throw an exception.
  2. Call the next Interceptor in the stack and catch the return value.
  3. Perform outbound operations. It can throw an exception if necessary as well.
  4. When no exception is thrown, it will return the call with an appropriate value that does not have to be the value returned by the previous Interceptor.

Implementation in the JBoss EJB Container

As mentioned earlier, the reason to use the Interceptor Stack is to implement EJB services like transaction or security. To illustrate this a little bit more, we want to discuss the Container Managed Transaction (CMT) and its implementation.

Assume a Web client calls a CMT EJB and the transaction attribute of the called method is set to Required; that means that when no transaction is in place, a transaction is created, and when one is in place, the call becomes part of the existing transaction. Therefore, the Transaction Interceptor of this EJB has to do the following:

  1. Inbound: Check if a transaction is set on the thread context. If not, create a new transaction and add it to the thread context as a current transaction; otherwise, mark it as an existing transaction on the thread context.
  2. Call next Interceptor.
  3. Outbound: Check if the transaction is marked for rollback or a system exception is thrown; if yes, then roll back the transaction and throw a TransactionRolledbackException. If the transaction is not marked as existing, the transaction is committed; otherwise, do nothing.
  4. Return.

When the transaction attribute is set to Mandatory, meaning that the caller has to provide a transaction, it looks like this:

  1. Inbound: Check if a transaction exists; if not, throw an exception.
  2. Call next Interceptor.
  3. Outbound: Check if the transaction is marked for rollback or a system exception is thrown; if yes, roll back the transaction and throw a TransactionRolledbackException.
  4. Return.

A little bit more interesting is the case when the transaction attribute is set to RequiresNew, which means that whether or not a transaction exists, a new one is created. Now the Transaction Interceptor has to do the following:

  1. Inbound: If there is already a transaction, then add it to the stack of previous transactions. Now create a new transaction and add it as current transaction to the thread context, meaning it suspends the old transaction.
  2. Call next Interceptor.
  3. Outbound: Check if the transaction is marked for rollback or a system exception is thrown and if yes, roll back the transaction and throw a TransactionRolledBackException.
    Otherwise, commit the current transaction and replace the current transaction with the previous transaction in the stack, if available. This means that the old transaction is restored.
  4. Return.

For a Bean Managed Transaction EJB (which cannot be an Entity Bean) transactions are handled this way:

  1. Inbound: Suspend the current transaction (if existing) by moving it to the stack of previous transactions. Then create a new transaction and make it available to the context of the EJB.
  2. Call next Interceptor.
  3. Outbound: If this is a Stateless Session Bean or Message Driven Bean, check if the transaction in the context is started and not committed or rolled back. If yes, throw an exception because this is not allowed. Otherwise, restore the previous transaction.
  4. Return.

Container Configuration

One of the goals of the Interceptor Stack is to give the administrator the flexibility to change the Interceptor Stack instead of using a fixed and restrictive configuration.

The layout of the Interceptor Stack is done in the Container Configuration that is set in the standardjboss.xml and the jboss.xml deployment descriptor of the EJB application. There are three types of container configurations:

  • Default configurations, set in the standardjboss.xml, available server-wide, and used when no specific configuration is set in the jboss.xml deployment descriptor.
  • Configurations defined in standardjboss.xml, available server-wide, but must be requested by each EJB in the jboss.xml deployment descriptor.
  • Configurations defined in the jboss.xml deployment descriptor, available application-wide and must be requested by each EJB as well.

Container configurations contain more than just the list of Interceptors; therefore, the following listing is only a part of the default configuration for CMP 2.x Entity Bean:

<container-configuration>
   <container-name>Standard CMP 2.x EntityBean</container-name>
   <call-logging>false</call-logging>
   <container-invoker>org.jboss.proxy.ejb.ProxyFactory</container-invoker>
   <container-interceptors>
      <interceptor>org.jboss.ejb.plugins.LogInterceptor</interceptor>
      <interceptor>org.jboss.ejb.plugins.SecurityInterceptor</interceptor>
      <interceptor>org.jboss.ejb.plugins.TxInterceptorCMT</interceptor>
      <interceptor metricsEnabled = "true">
         org.jboss.ejb.plugins.MetricsInterceptor
      </interceptor>
      <interceptor>org.jboss.ejb.plugins.EntityCreationInterceptor</interceptor>
      <interceptor>org.jboss.ejb.plugins.EntityLockInterceptor</interceptor>
      <interceptor>org.jboss.ejb.plugins.EntityInstanceInterceptor</interceptor>
      <interceptor>
         org.jboss.resource.connectionmanager.CachedConnectionInterceptor
      </interceptor>
      <interceptor>org.jboss.ejb.plugins.EntitySynchronizationInterceptor</interceptor>
      <interceptor>
         org.jboss.ejb.plugins.cmp.jdbc.JDBCRelationInterceptor
      </interceptor>
   </container-interceptors>
...
</container-configuration>

When an Entity Bean is deployed using this container configuration, the first interceptor is the Log, then the SecurityInterceptor, then the JDBCRelation, and last but not least, the ContainerInterceptor. You can change the list and also the order of the Interceptors.

Assume we are an ISP offering to host customers' EJB applications with a fee per call, let us say two cents per call. All we have to do is add our HitLoggingInterceptor as the first in the list of container Interceptors on each container configuration in standardjboss.xml, and prevent the client from providing its own container configuration in its jboss.xml. The HitLoggingInterceptor just logs the hit on an EJB in a database.

Now we want to go a step further and charge the client a cent for an incoming call and another cent when the call successfully returns to the caller. This means the HitLoggingInterceptor has to log the call on the inbound as well as on the outbound (when an exception is thrown, the outbound hit is not logged).

Extending the Concept

Because the concept is so successful in the JBoss EJB Container, JBoss is using it on other parts of the application server. On the client side, the Interceptor Stack is used to shift some of the code with the smart proxy from the server to the client to reduce the number of network calls, and also to make the server lookup methods pluggable for clusters.

The concept is also going into the JBoss JMX implementation to add security, transactions, and other services. Thus it is possible to define MBean-level security, like in J2EE. It would also be possible to make MBeans transactional, to roll back a series of changes to various MBeans.

Conclusion

Even thought the Interceptor Stack is a pretty simple concept, it allows the developer and the administrator to add additional services to the called target, which are performed before and after the call to the target is made.

With Interceptors, it would also be possible to create new types of EJBs by adding the appropriate services as Interceptors. The Interceptors then can change the methods to be called; therefore, you could create an advanced framework like you have for CMP Entity Beans. The Interceptor could also call several methods on the target, like ejbCreate() and ejbPostCreate().

Andreas Schaefer is a system architect for J2EE at SeeBeyond Inc., where he leads application server development.


Return to ONJava.com.