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.

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