Hi,

As part of this example we will be discussion about usage and functionality of the WildFly specific “Container interceptors” for our EJBs. There is another kind of interceptor known as “Java EE interceptors” which are expected to run after the container has done necessary invocation processing which involves security context propagation, transaction management and other such duties. But these these Java EE interceptors come too late into the picture.

In General, The interceptor’s are executed before the actual EJB’s method execution. When the interceptor method executes, it is passed as an InvocationContext object. This object provides information relating to the state of the interceptor and the target.

The WildFly allows us to use the Java EE interceptors (which are just POJO classes with lifecycle callback annotations) to be used as container interceptors. So that we do not need any wildfly/jboss specific dependency or libraries to use them. These container specific interceptors can be configured *ONLY* via deployment descriptors. Where as the EE Specific interceptors can be configured using @javax.interceptor.Interceptors annotation as well.

Goal of this demo

Lets start developing a simple EJB Based application which can help us in understanding what is the actual purpose of Container specific EJB Interceptors. Where exactly it can be used. In this example our interceptor does two following things:

1. Our interceptor intercepts the helloWorld(String) method parameter and then Alters the parameter value by appending some “XYZ” data to it.
2. This interceptor also calculates the overall ejb method invocation time taken to process the request.

WildFly 10 Setup:

Create a user in the “ApplicationRealm” of WildFly 10 standalone container by simply running the following command:

cd wildfly-10.0.0.CR3-SNAPSHOT/bin
./add-user.sh -a ejbuser ejbuser@1

Because of the “-a” flag the user will be created in the “ApplicationRealm” of WildFly10 with username “ejbuser” and password as “ejbuser@1”.

Developing the application:

Step-1). Create the following directory structure where we will be putting our Java and XML files. The “src/EAR_Data” directory will contain all the EJB java code and the deployment descriptors needed to build the EAR application. Similarly the “src/client” will be used to place the EJB Client artifacts.

mkdir -p EJB_Container_Interceptor_In_WildFly/src/EAR_Data 
mkdir -p EJB_Container_Interceptor_In_WildFly/src/client

Step-2). We will first see the interceptor class code “EJB_Container_Interceptor_In_WildFly/src/EAR_Data/InterceptorOne.java”, Which will use the “@javax.interceptor.AroundInvoke” annotation which should be invoked prior to the EJB method execution.

package interceptors;
public class InterceptorOne {

    @javax.interceptor.AroundInvoke
    private Object iAmAround(final javax.interceptor.InvocationContext invocationContext) throws Exception {
        String invokedMethod = invocationContext.getMethod().getName();
        System.out.println("\n\t[InterceptorOne]** iAmAround(final InvocationContext invocationContext) invoked");
        System.out.println("\t[InterceptorOne]** iAmAround, The EJB Method which will be processed is: " + invokedMethod);
        
        long startTime = System.currentTimeMillis();
        String originalParam = (String)invocationContext.getParameters()[0];
        
        // Altering the param value which was passed to the EJB method by the user While during invocation.
        String alteredParam = originalParam+"XYZ";
        invocationContext.setParameters(new String[]{alteredParam});
 
        try {
              return this.getClass().getName() + " " + invocationContext.proceed();
        } catch(Exception ex) {
            throw ex;
        }
        finally {
            long totalTime = System.currentTimeMillis() - startTime;
            System.out.println("\n\n\t[InterceptorOne]** iAmAround, Total Time taken for method execution : " + invokedMethod + " is " + totalTime + "-ms");
        }
    }
}

Step-3). We can have as many interceptors so lets create another one “EJB_Container_Interceptor_In_WildFly/src/EAR_Data/InterceptorTwo.java”.

package interceptors;
public class InterceptorTwo {
    @javax.interceptor.AroundInvoke
    private Object iAmAround(final javax.interceptor.InvocationContext invocationContext) throws Exception {
        System.out.println("\n\t[InterceptorTwo] iAmAround(final InvocationContext invocationContext) invoked");
        return this.getClass().getName() + " " + invocationContext.proceed();
    }
}

Step-4). Now lets create a Simple Stateless Bean “EJB_Container_Interceptor_In_WildFly/src/EAR_Data/CommonServiceBean.java” and it’s remote Interface “EJB_Container_Interceptor_In_WildFly/src/EAR_Data/CommonService.java” as following:

package ejb.service.one;
import javax.ejb.Local;

public interface CommonServiceOne {
     public String helloWorld(String name);
}

And

package ejb.service;
import javax.ejb.*;
import javax.naming.*;

@Stateless  
@Remote(CommonService.class)   
public class CommonServiceBean implements CommonService {
     public String helloWorld(String name) {
		   System.out.println("\n\n\t[CommonServiceBean] helloWorld(String "+name+") invoked.");
		   return "[CommonServiceBean] returned Hello "+name;
	    }
 }

Step-5). As we discussed that the container specific interceptors can be defined only via the deployable descriptors hence we will create “EJB_Container_Interceptor_In_WildFly/src/EAR_Data/jboss-ejb3.xml” file, During build it should be placed inside the “$EJB_JAR/META-INF” directory:

<jboss xmlns="http://www.jboss.com/xml/ns/javaee"
       xmlns:jee="http://java.sun.com/xml/ns/javaee"
       xmlns:ci ="urn:container-interceptors:1.0">
    <jee:assembly-descriptor>
        <ci:container-interceptors>
            <!-- Class level container-interceptor -->
            <jee:interceptor-binding>
                <ejb-name>*</ejb-name> 
                <interceptor-class>interceptors.InterceptorOne</interceptor-class>
                <interceptor-class>interceptors.InterceptorTwo</interceptor-class>
            </jee:interceptor-binding>
        </ci:container-interceptors>
    </jee:assembly-descriptor>
</jboss>

NOTE: We can also configure the “interceptor-order” in which the interceptor should be invoked. See https://docs.jboss.org/author/display/WFLY10/Container+interceptors

Step-6). Lets write the Client code which will use the “http-remoting” protocol and the http port 8080 of WildFly to invoke the EJB deployed to it. Write a client code as following: “EJB_Container_Interceptor_In_WildFly/src/client/Client.java”

package client;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.util.Properties;
import ejb.service.CommonService;

public class Client {
      public static void main(String ar[]) throws Exception {
          Context context=null;
          try {
                Properties props = new Properties();
                props.put(Context.INITIAL_CONTEXT_FACTORY, "org.jboss.naming.remote.client.InitialContextFactory");
                props.put(Context.PROVIDER_URL, "http-remoting://localhost:8080");   // NOTICE: "http-remoting" and port "8080"
                props.put(Context.SECURITY_PRINCIPAL, "ejbuser");                    // ApplicationRealm user
                props.put(Context.SECURITY_CREDENTIALS, "ejbuser@1");
                props.put("jboss.naming.client.ejb.context", true);
                context = new InitialContext(props);	
	            System.out.println("\n\tGot initial Context: "+context);		
           } catch (Exception e) {
                e.printStackTrace();
           }

            // Lookup Format will be 
            // <app-name>/<module-name>/<distinct-name>/<bean-name>!<fully-qualified-classname-of-the-remote-interface>
   
          CommonService remote=(CommonService)context.lookup("testEAR/testEJB/CommonServiceBean!ejb.service.CommonService");
          System.out.println("\n\t remote.helloWorld(\"RedHat!!!\") = "+remote.helloWorld("MiddlewareMagic!!!"));
          // Notice our interceptor will change the Parameter "MiddlewareMagic!!!" to ""MiddlewareMagic!!!"XYZ"
       }
  }

Step-7). Finally lets write the ANT based “EJB_Container_Interceptor_In_WildFly/build.xml” file which will compile all the sources and pack them inside the EJB Ear and will deploy it to the WildFly10 and run the client.

<project name="EJB_Interceptor_Demo_WildFly" default="all">
<property name="jboss.home" value="/Users/jsensharma/NotBackedUp/Installed/wildfly-10.0.0.CR3-SNAPSHOT" />
<property name="jboss.module.dir" value="${jboss.home}/modules" />
<property name="basedir" value="." />
<property name="tmp.dir" value="tmp" />
<property name="src.dir" value="src" />
<property name="output.dir" value="build" />
<property name="ear.name" value="testEAR.ear" />
<property name="ejb.jar.name" value="testEJB.jar" />
<property name="war.name" value="LocalWebApp.war" />
<property name="client.jar.name" value="remoteEJBClient.jar" />

        <path id="jboss.classpath">
           <fileset dir="${jboss.module.dir}/system/layers/base/javax/">
               <include name="**/*.jar"/>
           </fileset>  
           <fileset dir="${tmp.dir}">
               <include name="${ejb.jar.name}" />
           </fileset> 
        </path>

        <!-- Client Needs the following Jar to be present in the CLASSPATH including -->
        <path id="jboss.new.client.classpath">
           <fileset dir="${jboss.home}/bin/client">
               <include name="jboss-client.jar" />
           </fileset>  
        </path>
	 
        <target name="all" depends="message" />

        <target name="build_ear">
           <delete dir="${tmp.dir}" />
           <mkdir dir="${tmp.dir}/META-INF" />
           <javac srcdir="${src.dir}/EAR_Data" destdir="${tmp.dir}"  includes="Common*.java,Interceptor*.java" classpathref="jboss.classpath"/>
           <copy file="${src.dir}/EAR_Data/CommonServiceBean.java" todir="${tmp.dir}/ejb/service"/>
           <copy file="${src.dir}/EAR_Data/CommonService.java" todir="${tmp.dir}/ejb/service"/>
           <copy file="${src.dir}/EAR_Data/CommonServiceOneBean.java" todir="${tmp.dir}/ejb/service/one"/>
           <copy file="${src.dir}/EAR_Data/CommonServiceOne.java" todir="${tmp.dir}/ejb/service/one"/>
           <copy file="${src.dir}/EAR_Data/CommonServiceTwoBean.java" todir="${tmp.dir}/ejb/service/two"/>
           <copy file="${src.dir}/EAR_Data/CommonServiceTwo.java" todir="${tmp.dir}/ejb/service/two"/>
           <copy file="${src.dir}/EAR_Data/InterceptorOne.java" todir="${tmp.dir}/interceptors"/>
           <copy file="${src.dir}/EAR_Data/InterceptorTwo.java" todir="${tmp.dir}/interceptors"/>
           <copy file="${src.dir}/EAR_Data/InterceptorThree.java" todir="${tmp.dir}/interceptors"/>

           <copy file="${src.dir}/EAR_Data/jboss-ejb3.xml" todir="${tmp.dir}/META-INF"/>
           <jar jarfile="${tmp.dir}/${ejb.jar.name}" basedir="${tmp.dir}" compress="true" />
           <delete includeEmptyDirs="true">
              <fileset dir="${tmp.dir}/META-INF"/>
              <fileset dir="${tmp.dir}/ejb"/>
              <fileset dir="${tmp.dir}/interceptors"/>
           </delete> 

           <mkdir dir="${tmp.dir}/META-INF"/>
           <copy todir="${tmp.dir}/META-INF">
                <fileset dir="${src.dir}/EAR_Data/">
                  <include name="application.xml"/> 
                </fileset>
           </copy>           
           
           <jar jarfile="${tmp.dir}/${ear.name}" basedir="${tmp.dir}" compress="true" />
           <delete includeEmptyDirs="true">
              <fileset dir="${tmp.dir}/META-INF"/>
           </delete> 
           <delete file="${tmp.dir}/${ejb.jar.name}"/>

           <copy file="${tmp.dir}/${ear.name}" tofile="${output.dir}/${ear.name}"/>
           <delete file="${tmp.dir}/${ear.name}"/>
        </target>


        <target name="message" depends="build_ear">
            <echo message="*******************  ******************* *********************" />  
            <copy file="${output.dir}/${ear.name}" tofile="${jboss.home}/standalone/deployments/${ear.name}"/>
            <echo message="********** ${output.dir}/${ear.name} Build and Deployed Successfully **********" />  
            <echo message="Deploy the above EARs on your desired servers using CLI script or using Manaagement Console" />  
            <echo message="*******************  ******************* *********************" />  
        </target>


        <target name="run">
           <delete dir="${tmp.dir}" />
           <mkdir dir="${tmp.dir}" />
           <javac srcdir="${src.dir}/client" destdir="${tmp.dir}"  includes="CommonService.java,Client.java" classpathref="jboss.classpath"/> 
           <copy file="${src.dir}/client/CommonService.java" todir="${tmp.dir}/ejb/service"/>
           <copy file="${src.dir}/client/Client.java" todir="${tmp.dir}/client"/>        
           <jar jarfile="${output.dir}/${client.jar.name}" basedir="${tmp.dir}" compress="true" />
           <delete dir="${tmp.dir}"/>

           <java classname="client.Client" fork="true">
               <classpath>
                  <pathelement location="${output.dir}/${client.jar.name}"/>
                  <path refid="jboss.new.client.classpath"/>
               </classpath>
           </java>
        </target>        

</project>

Step-8). Lets build the project and deploy the EAR and run the client, Open a terminal and do the following:

export ANT_HOME=/PATH/TO/Support_Tools/apache_ant_1.9.2 
export PATH=$JAVA_HOME/bin:$ANT_HOME/bin:$PATH:

cd EJB_Container_Interceptor_In_WildFly

ant

ant run

You need to just make sure that Only one line you need to change in the “build.xml” before running the ant command is to change the following line to point to your own WildFly:

<property name="jboss.home" value="/PATH/TO/wildfly-10.0.0.CR3-SNAPSHOT" />

Output on the server side after deployment and access of the EJB method:
Step-9).


15:28:42,476 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-1) WFLYSRV0027: Starting deployment of "testEAR.ear" (runtime-name: "testEAR.ear")
15:28:42,482 INFO  [org.jboss.as.server.deployment] (MSC service thread 1-1) WFLYSRV0207: Starting subdeployment (runtime-name: "testEJB.jar")
15:28:42,495 INFO  [org.jboss.weld.deployer] (MSC service thread 1-4) WFLYWELD0003: Processing weld deployment testEAR.ear
15:28:42,501 INFO  [org.jboss.weld.deployer] (MSC service thread 1-8) WFLYWELD0003: Processing weld deployment testEJB.jar
15:28:42,502 INFO  [org.jboss.as.ejb3.deployment] (MSC service thread 1-8) WFLYEJB0473: JNDI bindings for session bean named 'CommonServiceTwoBean' in deployment unit 'subdeployment "testEJB.jar" of deployment "testEAR.ear"' are as follows:

	java:global/testEAR/testEJB/CommonServiceTwoBean!ejb.service.two.CommonServiceTwo
	java:app/testEJB/CommonServiceTwoBean!ejb.service.two.CommonServiceTwo
	java:module/CommonServiceTwoBean!ejb.service.two.CommonServiceTwo
	java:jboss/exported/testEAR/testEJB/CommonServiceTwoBean!ejb.service.two.CommonServiceTwo
	java:global/testEAR/testEJB/CommonServiceTwoBean
	java:app/testEJB/CommonServiceTwoBean
	java:module/CommonServiceTwoBean

15:28:42,502 INFO  [org.jboss.as.ejb3.deployment] (MSC service thread 1-8) WFLYEJB0473: JNDI bindings for session bean named 'CommonServiceOneBean' in deployment unit 'subdeployment "testEJB.jar" of deployment "testEAR.ear"' are as follows:

	java:global/testEAR/testEJB/CommonServiceOneBean!ejb.service.one.CommonServiceOne
	java:app/testEJB/CommonServiceOneBean!ejb.service.one.CommonServiceOne
	java:module/CommonServiceOneBean!ejb.service.one.CommonServiceOne
	java:jboss/exported/testEAR/testEJB/CommonServiceOneBean!ejb.service.one.CommonServiceOne
	java:global/testEAR/testEJB/CommonServiceOneBean
	java:app/testEJB/CommonServiceOneBean
	java:module/CommonServiceOneBean

15:28:42,502 INFO  [org.jboss.as.ejb3.deployment] (MSC service thread 1-8) WFLYEJB0473: JNDI bindings for session bean named 'CommonServiceBean' in deployment unit 'subdeployment "testEJB.jar" of deployment "testEAR.ear"' are as follows:

	java:global/testEAR/testEJB/CommonServiceBean!ejb.service.CommonService
	java:app/testEJB/CommonServiceBean!ejb.service.CommonService
	java:module/CommonServiceBean!ejb.service.CommonService
	java:jboss/exported/testEAR/testEJB/CommonServiceBean!ejb.service.CommonService
	java:global/testEAR/testEJB/CommonServiceBean
	java:app/testEJB/CommonServiceBean
	java:module/CommonServiceBean

15:28:42,504 INFO  [org.jboss.weld.deployer] (MSC service thread 1-6) WFLYWELD0006: Starting Services for CDI deployment: testEAR.ear
15:28:42,505 INFO  [org.jboss.weld.deployer] (MSC service thread 1-3) WFLYWELD0009: Starting weld service for deployment testEAR.ear
15:28:42,697 INFO  [org.jboss.as.server] (DeploymentScanner-threads - 2) WFLYSRV0016: Replaced deployment "testEAR.ear" with deployment "testEAR.ear"
15:28:42,698 INFO  [org.jboss.as.repository] (DeploymentScanner-threads - 2) WFLYDR0002: Content removed from location /Users/jsensharma/NotBackedUp/Installed/wildfly-10.0.0.CR3-SNAPSHOT/standalone/data/content/fb/9270aa3f5a76f881034149b2b36dc510809f34/content



15:29:58,025 INFO  [org.jboss.ejb.client] (pool-1-thread-1) JBoss EJB Client version 2.1.2.Final
15:29:58,147 INFO  [stdout] (EJB default - 1) 
15:29:58,147 INFO  [stdout] (EJB default - 1) 	[InterceptorOne]** iAmAround(final InvocationContext invocationContext) invoked
15:29:58,148 INFO  [stdout] (EJB default - 1) 	[InterceptorOne]** iAmAround, The EJB Method which will be processed is: helloWorld
15:29:58,148 INFO  [stdout] (EJB default - 1) 
15:29:58,148 INFO  [stdout] (EJB default - 1) 	[InterceptorTwo] iAmAround(final InvocationContext invocationContext) invoked
15:29:58,168 INFO  [stdout] (EJB default - 1) 
15:29:58,168 INFO  [stdout] (EJB default - 1) 
15:29:58,168 INFO  [stdout] (EJB default - 1) 	[CommonServiceBean] helloWorld(String MiddlewareMagic!!!XYZ) invoked.
15:29:58,168 INFO  [stdout] (EJB default - 1) 
15:29:58,169 INFO  [stdout] (EJB default - 1) 
15:29:58,169 INFO  [stdout] (EJB default - 1) 	[InterceptorOne]** iAmAround, Total Time taken for method execution : helloWorld is 20-ms

Source Code: https://github.com/jaysensharma/MiddlewareMagicDemos/tree/master/WildFly/EJB/EJB_Container_Interceptor_In_WildFly

Regards
Jay SenSharma

If you enjoyed this post, please consider leaving a comment or subscribing to the RSS feed to have future articles delivered to your feed reader.