Create an application-wide user session for J2EE

Span components from Web applications and J2EE clients to EJB components

Sun Microsystems invented the servlet before the other J2EE components with the intent of providing a high-performance alternative to the then ubiquitous CGI script. After the servlet came Enterprise JavaBeans (EJB) and Java Message Service (JMS), then JavaServer Pages (JSP). And eventually the Web application was formed in the shape of a WAR (Web Archive) file. This specialized JAR (Java Archive) constitutes a complete application with static and dynamic content, supporting libraries, and configuration settings. After deployment, the server adds container services, such as endpoints, security, and pooling, allowing the application to operate independently.

Enter J2EE and the EAR (Enterprise Archive) file, a bundle of deployment units wired together to form an enterprise application. The J2EE specification gives directions as to how individual components interact with respect to naming, security, transactions, and the like. Yet, while much work has been put into the individual J2EE component specifications, less attention has been given to providing services across component types. One missing facility is a user session that spans components from Web applications and J2EE clients to EJB components.

Often, application developers need such a facility when implementing other services not currently specified by J2EE. An application, for instance, that needs to provide a security audit mechanism would benefit from having an application-specific user identity ready at hand. Along the same lines, a custom security service implementing instance-based authorization could cache a set of dynamically granted roles in the session and require these as a parameter when performing programmatic security checks.

Figure 1 illustrates how different component types could need access to a shared session, in this case, containing user and role information.

Figure 1. The need for an enterprise-wide session

Authorization and audit are examples of services that cut across components and therefore must be made available to business logic, standalone clients, and Web applications alike. This article provides a possible session implementation that spans these three component types by utilizing a range of tools in tandem with existing J2EE infrastructure.

You will find all the code needed to provide the enterprise-wide session in Resources, along with a sample J2EE application that uses it. All source code has been extracted from a larger open source framework named Rampart, developed for Copenhagen County, Denmark. The entire framework should be available for download during 2005.

A sample architecture

With the plethora of free frameworks, design patterns, and best practices around, there are numerous ways to design J2EE applications these days. For each tier, you have an abundance of options: should you choose JavaServer Faces or Velocity over Struts, employ a full-fledged EJB layer, or go with a lightweight IOC (Inversion of Control) container such as Pico or Spring? Would it be better to structure EIS (enterprise information system) access around Java Data Objects or Hibernate, or fall back to plain SQL with Java Database Connectivity? Whatever your choices, any extension to your application's overall architecture must fit with the constraints posed by the other frameworks you employ.

To make the design of an application session practical then, we must get the high-level architecture in place first. This article assumes a business logic layer comprised of stateless session beans working on top of an O/R (object-relational) framework, say Hibernate, and two client tiers. Application users access the application via the standalone J2EE client, while certain information is exposed on the corporate intranet through the Web layer. Figure 2 gives a high-level conceptual overview for this architecture.

Figure 2. Sample application high-level architecture

An application-wide session can also be applied to other architectures with minor modifications.

A look at the application session

The Servlet specification's HttpServletRequest provides an abstraction that allows servlets and JSP pages to gain access to meta information about the current HTTP request. From the HttpServletRequest, you can get to the HttpSession, which allows user-specific information to exist across requests. These classes provide good sources of inspiration to the structure of our ApplicationSession: from the HttpServletRequest, you can retrieve the HttpSession via a call to getSession(). In our implementation, you can get to the ApplicationSession from a RequestContext instance via getApplicationSession(...):

 public interface RequestContext extends Serializable {
   String getId();
   String[] getIds();
   ApplicationSession getApplicationSession();
   void setApplicationSession(ApplicationSession applicationSession);
}

Whenever a user interaction causes an event in the UI layer, a new RequestContext is created and given a reference to the current user session, if available. The request acts as a container for the session as it makes its way through the logical layers. Infrastructure is needed to create RequestContext objects, something I will touch on later. Our ApplicationSession is defined by the following interface:

 public interface ApplicationSession extends Serializable {
   void setAttribute(Serializable key, Serializable value);
   Serializable getAttribute(Serializable key);
   List keySet();
}

The ApplicationSession implementation, then, is nothing more than a map of keys to values that can be set and retrieved again. Note that because components in a call chain may exist in different VMs, the transitive closure of RequestContext must be Serializable. Most application servers use call-by-reference semantics for inter-component calls in the same VM, thus allowing all layers to update the session. However, for remote components, setAttribute(...) won't persist across calls as the method is invoked on a remote copy of the original session.

The RequestContext exists only for the duration of a single user interaction. It holds the ApplicationSession and a unique ID, which can be used for correlating log entries with a specific user request. The ApplicationSession, on the other hand, is a server-side placeholder for client-specific state. It comes to life once the RequestContext.getSession() method is called.

The framework presented in this article comes with default implementations of the RequestContext and ApplicationSession interfaces named DefaultRequestContext and DefaultApplicationSession, respectively. The default instance of ApplicationSession simply wraps a java.util.HashMap:

 

public class DefaultApplicationSession implements ApplicationSession {

private Map sessionState; private Date created;

public DefaultApplicationSession() { sessionState = new HashMap(); created = new Date(); }

public void setAttribute(Serializable key, Serializable value) { sessionState.put(key, value); }

public Serializable getAttribute(Serializable key) { return (Serializable)sessionState.get(key); }

public List keySet() { return Arrays.asList(sessionState.keySet().toArray()); } }

Having defined the structure for request and session contexts, our next task is to provide the infrastructure for managing their creation and destruction.

Request and session life cycles

Standalone J2EE clients typically interact with the server through a user interface, most often based on Swing. All user interaction in the Swing framework revolves around the ActionListener interface.

Whenever you push a button or select an entry in a list or combo box, an ActionListener fires. Therefore, to ensure a RequestContext is made available to the application logic before that logic is actually run, we need to wrap all ActionListener instances. Assuming the preexistence of a JButton called executeBtn, we want to add the code shown below in boldface:

 

executeBtn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { try { ApplicationSession applicationSession = StaticApplicationSessionHolder.getApplicationSession(); RequestContext requestContext = requestContextFactory.create(); requestContext.setApplicationSession(applicationSession); //Your code goes here

} finally {

requestContextFactory.delete();

} } );

The RequestContextFactory puts the ApplicationSession into the new RequestContext and stores the whole thing in a ThreadLocal for subsequent access from all application code.

In Swing applications, it is quite common to have many anonymous classes spread over different packages, implementing the ActionListener interface. While you could implement a specialized ActionListener for creating and deleting the RequestContext, your application would always have to use this paradigm and you would have to refactor any existing code accordingly.

A less intrusive approach is to create an aspect for this purpose and apply it to all classes that implement ActionListener. This way, no changes to the application are needed, and selectively applying the code to specific packages proves to be easy. Our framework uses AspectWerkz as the AOP (aspect-oriented programming) framework of choice. Shown below is the aspect, which adds the RequestContext code to ActionListener instances:

 

public class Context implements AroundAdvice {

private RequestContextFactory requestContextFactory;

public Context() { requestContextFactory = ConfigHelper.getRequestContextFactory(); }

public Object invoke(JoinPoint joinPoint) throws Throwable { Object result = null; try { ApplicationSession applicationSession = StaticApplicationSessionHolder.getApplicationSession(); RequestContext requestContext = requestContextFactory.create(); requestContext.setApplicationSession(applicationSession);

result = joinPoint.proceed();

} finally {

requestContextFactory.delete();

} return result; } }

The AroundAdvice is matched by a point cut named all in the AspectWerkz configuration file, aop.xml:

 <aspect class="dk.rhos.fw.rampart.common.request.Context" deployment-model="perJVM">
   <pointcut name="all" 
      expression="execution(public void java.awt.event.ActionListener+.actionPerformed(java.awt.event.ActionEvent))" />
   <advice name="invoke(JoinPoint joinPoint)" type="around" bind-to="all" />
</aspect>

The point cut matches whenever the system executes the actionPerformed() method in any implementation of the ActionListener interface. In other words, the point cut will surround any handlers you may install for pushing a button, selecting an item in a list box, etc.

For Web applications, we don't need to use aspect-oriented programming. Via the javax.servlet.Filter interface, common code can be applied across URLs. We need to only write a class that implements Filter and conceptually looks like the Context aspect above, then configure selected URLs to use the filter in the web.xml file.

Storing the session data

So far so good: We have a session and a way to get to it, but where does it live between user invocations?

J2EE application clients can make do with a simple cache, which holds the reference to the ApplicationSession in a static variable. This is the approach taken by the Context aspect, which ensures that the session can always be retrieved via a call to StaticApplicationSessionHolder.getSession(). It is assumed, of course, that each application client never serves more than one user at a time and that the classloading scheme is simple.

For Web applications, the approach must slightly differ, as one instance must serve many masters and, hence, must cache a session per user. Guess what: The HttpSession was made for exactly that, and it is the obvious facility to use here. In the code below, notice how the doFilter() method from the Filter interface is almost identical to the implementation of Context.invoke(), except for the holder used for storing the session. In Context, we use a static variable, and in RequestContextFilter, we use the HttpSession:

 

public class RequestContextFilter implements Filter {

public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { ApplicationSession session = HttpSessionApplicationSessionHolder.getApplicationSession( (HttpServletRequest)servletRequest); RequestContext requestContext = requestContextFactory.create(); requestContext.setApplicationSession(session);

try { filterChain.doFilter(servletRequest, servletResponse); } finally {

requestContextFactory.delete();

} } ... }

At this point, we have established how J2EE clients and Web applications are manipulated to provide request and session contexts. Now let's move on to the business logic tier.

Passing the session onto the EJB layer

As of J2EE 1.3, EJB components no longer must be exposed to the outside via a remote interface; they can be purely local to components in the same VM. Applications that merely have a Web tier with EJB components and Web applications collocated in the same server instance typically use the same thread for the entire request. This means that no extra work is required at the EJB tier as the RequestContext is already attached to the current thread through the RequestContextFactory.

This statement doesn't hold true, however, for architectures where a remote client needs to connect to EJB components or where asynchronous messaging is involved. In these scenarios, the RequestContext must be passed along with the method invocation. Our framework deals with the remote case, but does not address passing the RequestContext via JMS.

Figure 3 uses stars to mark the locations where a context must be propagated over RMI (remote method invocation), created, or revived at the client and EJB tiers:

Figure 3. RequestContext creation and revival

There are a number of ways the RequestContext can be passed along with an RMI call. A possible approach simply requires the RequestContext to be the first or last parameter for all business methods. Through AOP, the RequestContext is then caught in bean implementations, attached to the thread, which has been allocated for the EJB request, and control is handed to the business logic. However, this approach forces business methods to be aware of the RequestContext instead of it being transparent.

To hide the RequestContext, we must pass it at a level lower than the business logic; the transport layer seems the most logical place. RMI supports both CORBA IIOP and the Java native JRMP (Java Remote Method Protocol) for transport.

IIOP allows the addition of context information using so-called request interceptors, but unfortunately, EJB containers are not required to expose these interceptors to applications. Depending on your choice of application server, then, this approach may or may not be fruitful. JRMP adds no such support, and as RMI calls might as well take place over this protocol, the issue is best addressed at the RMI level. An excellent discussion on this problem is available in the JavaWorld article "Service-Context Propagation over RMI (January 2005), from which this article's solution is inspired. I encourage you to read Wenbo Zhu's original article.

Dynamic dispatch

The magic of transparent remote context propagation lies in the use of a dynamic dispatch mechanism, which routes all EJB calls to a single, dedicated method, exec(). This method takes as arguments the actual EJB method to call, any parameters, and the current RequestContext object. Having installed the context into the current thread, exec() uses the Java Reflection API to delegate to the actual business method, as shown in Figure 4. From there, things proceed in a normal fashion.

Figure 4. Remote RequestContext propagation with dynamic dispatch

A client that does not know of RequestContext objects may still call business methods directly using the EJB component's remote interface, provided those methods are able to handle such requests. Unfortunately, clients who do wish to pass the RequestContext must now call exec() rather than the actual business method. This is where the Business Delegate design pattern comes into play.

The Business Delegate pattern

Business Delegate is a design pattern in the Sun Blueprints Core J2EE Patterns Catalog and serves to hide the implementation details of a service layer from a calling client. In our framework, the business delegates allow clients to choose from a remote or local version of the underlying EJB component without any changes to actual method invocations. This functionality is achieved by having two different private implementations of the same business interface: one for local calls and the other for remote calls. The class diagram in Figure 5 shows this relationship.

Figure 5. Business Delegate classes and interfaces. Click on thumbnail to view full-sized image.

When the client and EJB live in the same VM, the local interface is used, and, hence, the RequestContext is already attached to the current thread. For the non-local case, the business delegate wraps the remote interface in a dynamic proxy. Instead of calling the associated business remote method directly, the proxy invokes exec(). During this operation, the method name and parameters of the original method are passed along with the current RequestContext and ApplicationSession.

The dynamic proxy is supplied via the RequestContextPropagationInterceptor class, whose main method, invoke(), is shown in the code below. The interceptor variable represents the actual remote interface returned by a previous JNDI (Java Naming and Directory Interface) lookup:

 

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object result;

RequestContextFactory requestContextFactory = ConfigHelper.getRequestContextFactory(); RequestContext requestContext = requestContextFactory.getRequestContext();

if (args == null || args.length == 0) { result = interceptor.exec(method.getName(), args, new String[]{}, requestContext); } else { String[] argTypes = RequestContextUtil.getNames(method.getParameterTypes()); result = interceptor.exec(method.getName(), args, argTypes, requestContext); } return result; }

EJB components that wish to participate in request context propagation must implement the RequestContextInterceptor interface shown below, which has the single method, exec(), needed for the dynamic dispatch:

 public interface RequestContextInterceptor {
   Object exec(String methodName, Object[] arguments, String[] argumentTypes,
      RequestContext requestContext)
      throws RemoteException, InvocationTargetException;
}

The class RequestContextService implements this interface and provides a superclass for EJB components that wish to receive contexts. This implementation pulls RequestContexts off the wire, links them to the current thread, and calls the method specified by the methodName parameter using reflection.

Generated infrastructure

At this point, we have established the basic framework for passing the application-wide session within a unique RequestContext. We have used AOP, servlet filters, and business delegates to hide the underlying plumbing from application developers, but we have also introduced more classes that need to be written. Moreover, developing an EJB component implies writing deployment descriptors, interfaces, and implementation classes, and at this point, you may be asking yourself how you would ever be productive with this framework. Fear not, for there is a way out!

Figure 6. Artifacts that can be generated

Figure 6 shows the artifacts involved in writing our extended EJB component. The white boxes indicate what you must write, and the green boxes show derived artifacts that can be generated. While Java Specification Request 175, the metadata specification, threatens to challenge XDoclet's position, it is the de facto tool for code generation in Java today.

Most of the green artifacts come out of the box with XDoclet, but the business delegate still must be tailored. Our framework contains a module for generating business delegates automatically from EJB bean implementation classes. When run, the XDoclet task will scan through the set of source files, filter out stateless session bean implementation classes, and generate business delegates from them.

The code below shows the demo EJB bean implementation class with content stripped and XDoclet tags highlighted:

 /**
 * @ejb.bean type="Stateless" view-type="both" name="DemoService"
 * @ejb.interface extends="javax.ejb.EJBObject,
   dk.rhos.fw.rampart.common.request.RequestContextInterceptor"
 * @generate.businessdelegate
 */ 
public abstract class DemoServiceBean extends RequestContextService 
   implements SessionBean 
{
   /**
    * @ejb.interface-method
    */
   public RequestContext getRequestContext() {
      ...
   }
   ...
}

The XDoclet tag @ejb.bean is used to generate home, remote, local, and localhome interfaces, while ejb.interface tells XDoclet that the remote interface must extend our RequestContextInterceptor. Finally @generate.businessdelegate is our custom tag used in generating the business delegate, and @ejb.interface-method tells XDoclet to include the getRequestContext() method in remote and local interfaces.

The code

This article is accompanied by a complete framework, which can be used as is, along with a sample application. To build the system, you must first download and install Apache Maven, then change directories into the root folder, and type maven. This will download all dependent libraries and build both the framework and demo application. Artifacts will be copied to your local Maven repository under "rampart" and "rampart-demo."

The framework is composed of the following modules:

  • rampart\modules\rampart-common: Contains common framework code
  • rampart\modules\rampart-util: Optional utilities for Web applications
  • rampart\modules\rampart-doclet: XDoclet extension for generating business delegates

The sample application contains a single EJB component, a single Web application, and a single standalone J2EE client. The application has the following structure:

  • rampart\demoapp\application: Files needed for the EAR
  • rampart\demoapp\client: The standalone J2EE client
  • rampart\demoapp\service: Files needed for the EJB component
  • rampart\demoapp\webclient: The Web application client

Wrap up

J2EE lacks an EAR-wide session facility, but, in this article, we have built one using a wide variety of utilities from the architect's toolbox: design patterns, custom code generators, aspect-oriented programming, and dynamic proxies. The codebase is fairly small, but quite a bit of infrastructure is still involved in hiding the details from application developers and in making development efficient.

An application-wide session is just one of many facilities you may want to add to your application infrastructure before building the actual application logic. Chances are, your system will need a custom security mechanism, personalization, auditing, performance metrics, robust exception handling...the list goes on and on.

These aspects can be handled using the same tools on mechanisms similar to those described in this article. So download the code, try it out, and start hammering out the infrastructure for the extra facilities you need!

Kåre Kjelstrøm is the cofounder of Silverbullets, a Danish consulting company that helps design and architect J2EE applications. He worked at Trifork on the EAS J2EE Application Server and as a J2EE consultant for several customers in the areas of shipping and healthcare. For a couple of years, Kjelstrøm lived in Silicon Valley, where he worked as architect and developer in the areas of e-business process automation, Web services management, and J2EE application server appliances. He holds a B.S. and an M.S. in computer science from the University of Aarhus, Denmark.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more