30 August 2007

EJB External Interceptors

Came across a situation quite recently which had a requirement for the invocation of various methods calls of an EJB to be able to logged for future auditing purposes.

The simplest answer is to use EJB3 and its Interceptor functionality in the guise of an @AroundInvoke method which gets called on every method invocation of the bean class.

Now this sounds OK if you are already using EJB3 and have access to the bean source code to modify it and add the interceptor method and the annotation.

If you don't have access to the original bean source code, you can still do it by using an external interceptor class. In this technique, you create an class external which contains interceptor method(s) and use the ejb-jar.xml file to declare the external interceptor class and bind it to the bean(s) and methods you want to apply it to.

By way of example, here's an external interceptor class:
package sab.demo.interceptors;

import javax.interceptor.InvocationContext;

public class AuditInterceptor {
public AuditInterceptor() {
}

/**
* Interceptor method which prints method call
*/
public Object logMethodCall(InvocationContext ic) throws Exception {

// The InvocationContext contains the context of the intercepted call

System.out.printf("ExternalInterceptor\n\tMethod: %s\n",
ic.getMethod().getName());

// Print out the parameter values if they exist
if(ic.getParameters().length!=0) {
System.out.printf("\tParameters: ");
boolean first = true;
String sep = ", ";
for(Object o : ic.getParameters()) {
System.out.printf(" %s", o.toString());
if(!first) {
System.out.printf("%c", sep);
} else {
first = false;
}
}
}
System.out.println();

// Carry on
return ic.proceed();
}
}
To apply this to an existing bean class, create a partial ejb-jar.xml file with the entries pertinent to the interceptor class:
<?xml version="1.0" encoding="windows-1252" ?>
<ejb-jar xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/ejb-jar_3_0.xsd"
xmlns="http://java.sun.com/xml/ns/javaee" version="3.0">
<interceptors>
<interceptor>
<interceptor-class>sab.demo.interceptors.AuditInterceptor</interceptor-class>
<around-invoke>
<method-name>logMethodCall</method-name>
</around-invoke>
</interceptor>
</interceptors>
<assembly-descriptor>
<interceptor-binding>
<ejb-name>SomeBusiness</ejb-name>
<interceptor-class>sab.demo.interceptors.AuditInterceptor</interceptor-class>
<method>
<method-name>*</method-name>
</method>
</interceptor-binding>
</assembly-descriptor>
</ejb-jar>
When the bean is called from a client, the external interceptor is invoked and the method calls are printed to stdout:
ExternalInterceptor
Method: someBusinessMethod
Parameters: 1188441131015
The external interceptor class can be packaged within the EJB-JAR file, or alternatively, it can be packaged in a separate JAR file and included in the EAR file as a library. Using JEE5, the library can be referenced using the new <libraries ... > tag and putting the JAR file into the specified directory in the EAR file.

<application version="5">
<library-directory>libraries</library-directory>
<module>
<ejb>ejb.modulejar</ejb>
</module>
</application>
What about EJB 2.1?

Turns out this works for EJB 2.1 applications as well with some minor tweaks to the existing descriptor file.

In this case, you only really need to alter the existing ejb-jar.xml so the version is specified as "3.0" and add the interceptors tags. Whereupon OC4J (10.1.3.x) will run the bean as EJB 3.0, and apply the interceptors as specified.

Here's an example of an EJB 2.1 being converted to EJB 3.0 and the interceptors added.


<?xml version = '1.0' encoding = 'windows-1252'?>
<!--
<ejb-jar
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/ejb-jar_2_1.xsd"
xmlns="http://java.sun.com/xml/ns/j2ee"
version="2.1" >
-->

<ejb-jar version="3.0">
<interceptors>
<interceptor>
<interceptor-class>sab.demo.interceptors.AuditInterceptor</interceptor-class>
<around-invoke>
<method-name>logMethodCall</method-name>
</around-invoke>
</interceptor>
</interceptors>
<enterprise-beans>
<session>
<description>Session Bean ( Stateless )</description>
<display-name>SomeBusiness</display-name>
<ejb-name>SomeBusiness</ejb-name>
<home>sab.demo.ejb21.SomeBusinessHome</home>
<remote>sab.demo.ejb21.SomeBusiness</remote>
<local-home>sab.demo.ejb21.SomeBusinessLocalHome</local-home>
<local>sab.demo.ejb21.SomeBusinessLocal</local>
<ejb-class>sab.demo.ejb21.SomeBusinessBean</ejb-class>
<session-type>Stateless</session-type>
<transaction-type>Container</transaction-type>
</session>
</enterprise-beans>
<assembly-descriptor>
<container-transaction>
<method>
<ejb-name>SomeBusiness</ejb-name>
<method-name>*</method-name>
</method>
<trans-attribute>Required</trans-attribute>
</container-transaction>
<interceptor-binding>
<ejb-name>SomeBusiness</ejb-name>
<interceptor-class>sab.demo.interceptors.AuditInterceptor</interceptor-class>
<method>
<method-name>*</method-name>
</method>
</interceptor-binding>
</assembly-descriptor>
</ejb-jar>


In this case, if you don't want to change the application.xml file to be versioned at JEE 5.0 and use its libraries inclusion facility, then to include the library containing the external interceptor class you can use the <library> element of the orion-application.xml file to specify the library to load:
<orion-application xsi="http://www.w3.org/2001/XMLSchema-instance" nonamespaceschemalocation="http://xmlns.oracle.com/oracleas/schema/orion-application-10_0.xsd">
<library path="InterceptorLibrary.jar"/>
</orion-application>