Sun Java System Application Server

Samples Index

The Annotation-Override-ear Sample Application


Interceptors

EJB 3.0 specification defines interceptors that are used to interpose on business method invocations and life cycle events that occur on an Enterprise Bean instance. An interceptor class is just like any other Java class. It need not extend any special class nor does it need to implement any well-known interface. An interceptor class defines (exactly) one business method interceptor method by annotating a method with @AroundInvoke annotation (or its deployment descriptor equivalent). The method must take javax.interceptor.InvocationContext as an argument and must return java.lang.Object.

More than one interceptor can be chained to interpose a single business method. @Interceptors is an annotation that can be used to indicate the list of interceptors to be invoked before any business methods are invoked on this bean. The order in which the interceptors are invoked before the business method is invoked is same as the order in which they are specified using the @Interceptors annotation (or in the deployment descriptor). Interceptors can be specified at the ejb-jar level (in the deployment descritptor only), at bean class level, or for an individual bean method and can be overridden using the deployment descriptors.

Default interceptors may be defined to apply to all the components within the ejb-jar. The deployment descriptor is used to define default interceptors and their relative ordering. Default interceptors are automatically applied to all components defined in the ejb-jar. The default interceptors are invoked before any other interceptors for a bean.

If multiple interceptors are specified then they are invoked in the following order.

At runtime, before a business method is invoked OR a lifecycle event occurs, the container creates an instance of InvocationContext and passes the same instance of InvocationContext to each interceptor method. The InvocationContext  interface a number of methods to examine method name, method params etc. It also provides a method called getContextData() that returns a Map. Since the same invocationContext instance is passed for each interceptor for the duration of the business method or lifecycle event, it is possible for an interceptor to save information in the context data property of the InvocationContext that can be subsequently retrieved in other interceptors. This allows the interceptors to pass contextual data between them. The contextual data is not sharable across separate business method invocations or lifecycle callback events.
 

What does the sample do?

The sample demonstrates how the interceptors are invoked can be altered using the deployment descriptor. For this, the sample defines three interceptors. One is defined in the bean class itself, while the other two interceptors are defined in two other classes.  Of the two, one is a default interceptor that gets invoked for every business method for all the beans in the module. The second interceptor is invoked for all the business methods for the session bean.

To easily demonstrate the concept, each interceptor adds its name to a list, which in turn is added to a HashMap that is available using the invocationContext.getContextData() method. Finally, the interceptor method that is defined in the bean itself gets this list and puts in a HashMap that is defined in the bean itself. Note that we use a list to preserve the interceptor ordering. The bean itself provides a business method that returns the interceptor name list for a given method name.

Business interface

The Stateless Session bean has a Remote business interface with three business methods.

import javax.ejb.Remote;

@Remote
public interface StatelessSession {
public String initUpperCase(String val)
throws BadArgumentException;
public String initLowerCase(String val)
throws BadArgumentException;
public boolean isOddNumber(int val)
throws BadArgumentException;
public List<String> getInterceptorNamesFor(String methodName);
}

Note that unlike prior versions of EJB, the Remote interface is not required to extend java.rmi.Remote and its business methods are not required to throw java.rmi.RemoteException. Here, BadArgumentException is an application exception.

The business interface is designated as a remote business interface through the @javax.ejb.Remote annotation.

Stateless session bean class

Here is the bean implementation:

@Stateless
@Interceptors({ArgumentsChecker.class})
public class StatelessSessionBean
implements StatelessSession

private static Map<String, List<String>> interceptorNamesForMethod
= new HashMap<String, List<String>>()

private static final String KEY = "interceptorNameList";

public String initUpperCase(String val) {
String first = val.substring(0, 1);
return first.toUpperCase() + val.substring(1);
}

public String initLowerCase(String val) {
String first = val.substring(0, 1);
return first.toLowerCase() + val.substring(1);
}

public boolean isOddNumber(int val) {
return (val % 0) != 0;
}

private Object intercept(InvocationContext invCtx)
throws Exception {

Map<String, Object> ctxData = invCtx.getContextData();
List<String> interceptorNameList = (List<String>) ctxData.get(KEY);
if (interceptorNameList == null) {
interceptorNameList = new ArrayList<String>();
ctxData.put(KEY, interceptorNameList);
}

//Add this interceptor also to the list of interceptors invoked!!
interceptorNameList.add("StatelessSessionBean");

//Cache the interceptor name list in a map that can be queried later
String methodName = invCtx.getMethod().getName();
synchronized (interceptorNamesForMethod) {
interceptorNamesForMethod.put(methodName, interceptorNameList);
}

return invCtx.proceed();
}

public List<String> getInterceptorNamesFor(String methodName) {
return interceptorNamesForMethod.get(methodName);
}
}

The annotation @javax.ejb.Stateless defines the component and designates this class as the bean class for a stateless session bean.

The initUpperCase() and initLowerCase business methods convert the first character in the parameter to upper / lower case. The isOddNumber returns true if the passed value is an odd number. Note that the business methods do not check if the argument is null OR if the parameter starts with an alphabetic character. It could have been handled in the business method itself, but we use the EJB 3.0 interceptor facility to do the parameter validation checks.

Note that since ArgumentChecker interceptor is defined at the type level, it is fired before every business method is invoked.

As shown in the code, the interceptor method defined in the bean simply adds the bean class name to the list (that are already populated by other interceptors). The method then adds this list to a static HashMap. This HashMap is queried in the getInterceptorNamesFor() business method.

Interceptor classes

The bean lists (using @Interceptor annotation) the interceptors to be fired before its business methods are called. Here the bean lists one interceptor: ArgumentsChecker. The name of the interceptor classes can be anything. The exact method to be invoked on these interceptors is designated using the @AroundInvoke annotation. The @AroundInvoke annotation is defined in javax.interceptor package.


import java.lang.reflect.Method;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

public class ArgumentsChecker {

@AroundInvoke
public Object checkArgument(InvocationContext ctx) throws Exception {

Map<String, Object> ctxData = ctx.getContextData();
List<String> interceptorNameList = (List<String>)
ctxData.get("interceptorNameList");
if (interceptorNameList == null) {
interceptorNameList = new ArrayList<String>();
ctxData.put("interceptorNameList", interceptorNameList);
}

//Now add this interceptor name to the list
interceptorNameList.add("ArgumentsChecker");

Method method = ctx.getMethod();

Object objParam = ctx.getParameters()[0];
if (! (objParam instanceof String)) {
throw new BadArgumentException("Illegal argument type: " + objParam);
}
String param = (String) (ctx.getParameters()[0]);
// Note that param cannot be null because
// it has been validated by the previous (default) interceptor
char c = param.charAt(0);
if (!Character.isLetter(c)) {
// An interceptor can throw any runtime exception or
// application exceptions that are allowed in the
// throws clause of the business method
throw new BadArgumentException("Illegal argument: " + param);
}

// Proceed to call next interceptor OR business method
return ctx.proceed();

}
}

Before a business method is invoked, the container invokes the interceptor method (annotated with @AroundInvoke) of each interceptor. In our sample, before the bean's initUpperCase() method is invoked, the checkIfNull() and checkArgument() methods are invoked, in that order.

The InvocationContext (defined in javax.interceptor package) provides various methods to examine as well as modify the parameter values passed to the business method.

The proceed() method in InvocationContext causes the next interceptor method in the chain to be invoked, or when called from the last AroundInvoke interceptor method, the bean's business method. Interceptor methods must always call InvocationContext.proceed(), or no subsequent interceptor methods or bean business method or life cycle callback methods will be invoked.

In our sample, the interceptor methods use the InvocationContext.getMethod() and InvocationContext.getParameters() method to examine the parameter passed to the initUpperCase() business method.  ArgumentChecker throws BadArgumentException if the first character in the string is not a letter. Note that an interceptor can throw any runtime exception or application exceptions that are allowed in the throws clause of the business method. If the interceptors find the parameter value to be valid, it calls InvocationContext.proceed(), thereby invoking the next interceptor OR the business method.

Default interceptors

You might have noticed that ArgumentChecker will fail badly for two cases. First, it assumes that the argument that is passed to a business method is not null. Second, it typecasts the parameter to a java.lang.String and then checks if the first character is an alphabetic character.  Clearly, the argument passed to the isOddNumber() method is not a String.

We can fix the first problem by adding another interceptor that just checks if the argument passed to any business method is not null.  We will use the NullChecker interceptor for exactly this purpose. To fix the second problem, we will specify in the deployment descriptor that ArgumentsChecker must NOT be invoked for isOddNumber() method.

import java.lang.reflect.Method;
import javax.interceptor.AroundInvoke;
import javax.interceptor.InvocationContext;

public class NullChecker {

@AroundInvoke
public Object checkIfNull(InvocationContext ctx)
throws Exception {
Method method = ctx.getMethod();
if (method.getName().equals("initUpperCase")) {
String param = (String) (ctx.getParameters()[0]);
if (param == null) {
throw new BadArgumentException("Illegal argument: null");
}
}

// An interceptor can throw any runtime exception or
// application exceptions that are allowed in the
// throws clause of the business method

return ctx.proceed(); // Proceed to the next interceptor
}
}

Deployment descriptor

We will use ejb-jar.xml to specify the default interceptor that gets invoked for all business methods for every bean defined in the module. This is done by using the <interceptor-binding> xml element in the deployment descriptor. The important thing to note here is that the <ejb-name> must be * meaning that it applies to all enterprise beans defined in this jar. The name of the default interceptor itself is specified using the <interceptor-class> element. Here is the snippet for specifying the default interceptor:

        <interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>
                enterprise.interceptor_stateless_xml_ejb.NullChecker
            </interceptor-class>
        </interceptor-binding>


To indicate that the ArgumentsChecker interceptor must not be fired for the isOddNumber() method, we specify the <exclude-class-interceptors> element along with the <method> element. Here is the snippet:

        <interceptor-binding>
            <ejb-name>StatelessSessionBean</ejb-name>
        <exclude-class-interceptors>
true
</exclude-class-interceptors>
        <method>
        <method-name>isAnOddNumber</method-name>
    </method>
        </interceptor-binding>

Vendor specific deployment configuration

You do not need to define any vendor specific deployment descriptors (for example, sun-ejb-jar.xml and sun-application-client.xml) for this example. The JNDI name for the Remote Stateless Session bean will default to: enterprise.interceptor_stateless_ejb.StatelessSession#enterprise.interceptor_stateless_ejb.StatelessSession

Building, Deploying, and Running the Application

Follow these instructions to build, deploy, and run this sample application.

  1. Setup your build environment and Configure the application server with which the build system has to work by following the common build instructions.
  2. app_dir is the sample application base directory: samples_install_dir/javaee5/enterprise/annotation-override-interceptor-ear
  3. Change directory to app_dir.
  4. Build, Deploy and Run the sample application using the target "all":

    app_dir> ant all

  5. You can accomplish the same thing by issuing separate Ant commands:

    app_dir> ant default compiles and packages the application

    app_dir> ant deploy deploys it to application server

    app_dir> ant run runs the test application client

  6. Use the target clean to remove the temporary directories like build and dist.

    app_dir> ant clean

Building, Deploying, and Running the Application in NetBeans IDE

Follow these instructions to build, deploy, and run this sample application using NetBeans IDE.

  1. Refer to common build instructions. for setting up NetBeans IDE and the application server with which the IDE will use.
  2. In NetBeans IDE, select File->OpenProject and select samples_install_dir/javaee5/enterprise/annotation-override-interceptor-ear as the project.
  3. Right click on annotation-override-interceptor-ear and select Run Project which will build, deploy and run the project. Sample output is given below.
  4.   Interceptors invoked for initUpperCase(): NullChecker, ArgumentsChecker, StatelessSessionBean}
      Interceptors invoked for isOddNumber(): NullChecker, StatelessSessionBean}
      run-annotation-override-interceptor-appclient:
      BUILD SUCCESSFUL (total time: 20 seconds)
      

Troubleshooting

If you have problems when running the application, refer to troubleshooting document.


Copyright © 2006 Sun Microsystems, Inc. All rights reserved.