Blog

We believe there is something unique at every business that will ignite the fuse of innovation.

Preface

One of the traits of a well-designed application is the efficient handling of cross-cutting concerns. By efficient I mean the application architecture/framework to handle bulk of the crosscutting concerns (such as security, transaction management, logging etc.) and let the individual services concentrate on the business logic. In this blog entry, I discuss one such cross-cutting concern and a way to handle it at the framework level.

Problem Statement

In a typical transactional application that involves persisting data to the database, I often come across the need to persist some metadata information irrespective of the underlying business service. For example, every DB table in the system might have a column called LAST_UPDATED_BY that needs to be updated whenever the particular row gets updated. This is a simple cross cutting need that could be and must be handled at the application framework level.

Tool Set

I will use the following 3 constructs to solve the above problem:

  1. ThreadLocal — This class allows for variables to be stored at the thread scope. I have no intention of getting into the pros and cons of using ThreadLocal. I will simply say it is very powerful and must be used very carefully.
  2. EJB Interceptor — Used to intercept calls to business methods and lifecycle events of bean instances.
  3. @PrePersist /@PreUpdate Annotations — Used to mark an entity bean's method as a callback before the entity is created or updated.

Solution

Please note that, for better readability, I have skipped the error/exception handling code here.

First, let's create a class to hold the ThreadLocal variable to save the current user name:

public class CurrentContext implements Serializable {
 
  private static ThreadLocalString> currentUserName = new ThreadLocalString>();
 
  public static ThreadLocalString> getCurrentUserName() {
    return currentUserName;
  }
}

Second, let's create the interceptor class:

public class CrossCuttingInterceptor {
 
  @Resource
  private SessionContext sessionContext;
 
  @AroundInvoke
  public Object handleCrossCuttingConcerns(InvocationContext ctx) {
 
    String userName = sessionContext.getCallerPrincipal().toString();
 
    try {
      CurrentContext.getCurrentUserName().set(userName);
      return ctx.proceed();
    } finally {
      // very important, not doing this may result in corrupt thread
      CurrentContext.getCurrentUserName().set(null);
    }
  }
}

Now, let's configure the interceptor class in ejb-jar.xml file:

<>version="3.0">
  <>>
    <>>
      <>>*>
      <>>com.xyz.CrossCuttingInterceptor>
    >
  >
>

And finally, lets annotate a callback method in a superclass that all entity beans will extend.

@MappedSuperClass
public class EntitySuperClass implements Serializable {
 
  @Id private Long id;
 
  ...
  ...
 
  @Column private String updatedBy;
 
  @PrePersist @PreUpdate
  private void setUserName() {
    this.updatedBy = CurrentContext.getCurrentUserName().get();
  }
}

That is it. Any time a method is invoked on a session bean, the CrossCuttingInterceptor will intercept the call and set the current user name in the ThreadLocal variable. The control will then pass to the session bean for any business logic to be executed and eventually the user name will be automatically saved in the database irrespective of the entity being created or updated. And the finally block in the interceptor class will clean up the context. We do this once and all the 200 entities in the system will be handled automatically and more importantly, consistently.

Typically, a call to a session bean may invoke other session beans. In those instances, we don/t want the same user name to be set and unset every time. That could easily be avoided by beefing up the code in the interceptor class to create and cleanup the context only when necessary.

And remember, once the execution is completed, the thread will be returned to the thread pool to be reused. So don/t forget the clean up code and make sure it fires at all times.

This approach could be easily expanded to handle other crosscutting concerns such as consistent error handling.