Adding contextual data to EJB method

Sometimes you need to pass some additional / contextual data to the called EJB method. Because it’s a contextual data you don’t want to end in changing signatures of all your EJB methods just to add a single or few such parameters. It is possible to add context data using the SessionContext object.

Take a look at the following code example:

package com.piotrnowicki;

import javax.annotation.Resource;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;
import javax.interceptor.AroundInvoke;
import javax.interceptor.Interceptors;
import javax.interceptor.InvocationContext;

@Stateless
public class MyService {

   /**
    * EJB session context - allows to get to the context data.
    */
   @Resource
   SessionContext ctx;

   /**
    * Method invoked by the client.
    * 
    * Explicit interceptor; it could be also configured in ejb-jar.xml 
    * or it could be annotated at the class level (which means that 
    * every class method will be intercepted.)
    * 
    * @param id
    *           just an exemplary parameter
    */
   @Interceptors(MyInterceptor.class)
   public void myMethod(String id) {
      String ctxParamKey = ContextData.MY_PARAM.getKey();

      // Simple as that - access context's data (map) value.
      Object ctxParam = ctx.getContextData().get(ctxParamKey);

      print("[myMethod] Method parameter: " + id);
      print("[myMethod] Context parameter: " + ctxParam);
   }

   /**
    * Just a minor helper method to print message on the console.
    * 
    * @param msg
    *           message to be printed.
    */
   private void print(String msg) {
      System.out.println(msg);
   }

   /**
    * Simple enumeration to be less error-prone when using String based
    * identifiers as map key.
    *
    */
   public static enum ContextData {
      MY_PARAM("myParamKey");

      private String key;

      private ContextData(String key) {
         this.key = key;
      }

      public String getKey() {
         return key;
      }
   }

   /**
    * Interceptor which will set our context data before calling the
    * real EJB method.
    */
   public static class MyInterceptor {

      /**
       * Interceptor's SessionContext is the same as EJB's.
       */
      @Resource
      SessionContext ejbCtx;

      @AroundInvoke
      public Object intercept(InvocationContext ctx) throws Exception {
         String ctxParamKey = ContextData.MY_PARAM.getKey();

         // Just put some data into context map.
         ctx.getContextData().put(ctxParamKey, "Context hello!");

         // You can use EJB's context - i.e. check user's principal
         // ejbCtx.getCallerPrincipal();

         return ctx.proceed();
      }
   }
}

The result of this code execution is:

INFO: [myMethod] Method parameter: 2
INFO: [myMethod] Context parameter: Context hello!

A request made to MyService#myMethod(-) will be intercepted by MyInterceptor which will add some contextual data (ctx.getContextData().put(ctxParamKey, "Context hello!")) and invoke the actual EJB.

The EJB (just as interceptor did – with help of SessionContext) will recover the value saved by the interceptor and can use it right away.

If the parameter wasn’t set, the null value will be returned. So if you’d comment this code: ctx.getContextData().put(ctxParamKey, "Context hello!") you can expect following results:

INFO: [myMethod] Method parameter: 2
INFO: [myMethod] Context parameter: null

In this way you can pass the contextual data to your EJB methods without changing their signature. Just remember not to overdose with this feature, as it makes testing and auditing more difficult.