Sun Java System Application Server |
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.
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.
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.
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
}
}
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>
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
Follow these instructions to build, deploy, and run this sample application.
app_dir
is the sample application base
directory: samples_install_dir/javaee5/enterprise/annotation-override-interceptor-ear
app_dir.
app_dir>
ant all
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
app_dir> ant
clean
Follow these instructions to build, deploy, and run this sample application using NetBeans IDE.
samples_install_dir/javaee5/enterprise/annotation-override-interceptor-ear
as the project.annotation-override-interceptor-ear
and select Run Project
which will build, deploy and run the project. Sample output is given below. Interceptors invoked for initUpperCase(): NullChecker, ArgumentsChecker, StatelessSessionBean} Interceptors invoked for isOddNumber(): NullChecker, StatelessSessionBean} run-annotation-override-interceptor-appclient: BUILD SUCCESSFUL (total time: 20 seconds)
If you have problems when running the application, refer to troubleshooting document.
Copyright © 2006 Sun Microsystems, Inc. All rights reserved.