Saturday, April 5, 2008

Struts 2 session replication

Very often the following task occuring: saving and distributing session between web applications. In this post, implemented one of the strategies of saving and distributing session for web framework Struts 2.

Used technologies : EJB3, Struts-2, Tomcat, Jboss

So we have task for linking several web applications with Session Manager. One of the restrictions in this implementation is usage of subdomains of domain e.g. * So we can use domain cookies (this restriction can be taken off with URL rewriting, but in this implementation we use cookies ). Because existed system was build on Struts 2 web framework, we decided to use internal capabilities of this framework. As we will see further, Struts 2 has very exciting futures called Interceptors, and they help us to get rid of crosscutting concernes.
Session Manager is a EJB3 component with simple interface, for simplicity only subset of methods will be shown with descriptive methods signature :

public interface SessionManagerRemote {
void extendActivity(String sessionId) throws SessionExpiredException;
String registerActivity();
void unregister(String sessionId);
Long getSessionDuration(String sessionId);
Map getSession (String sessionId) throws SessionExpiredException;
void putSession (String sessionId,Map sessionObjects)throws SessionExpiredException;

This component will be deployed on J2EE Application Server, e.g. JBOSS. Jboss implementation following:

@org.jboss.annotation.ejb.Service(objectName = "jboss:managementService=SessionManager")
@org.jboss.annotation.ejb.RemoteBinding(jndiBinding = "com/project/management/session/remote")
public class SessionManager implements SessionManagerRemote {

Service annotation is used for Singleton implementation in Jboss runtime.

So, back to main task. We need to replace Struts 2 session implementation with our distributed session, controlled by session manager. But for make such serious session replacement we must understand architecture of Struts 2 framework. First we must detect how we use session in Struts 2 application :

    • OGNL expressions with #session identifier e.g. <s:property:value="#session.PARAMETER_IN_SESSION"/>

    • Actions that implement SessionAware interface :

public class TempAction implements SessionAware {
private Map session;
public void setSession(Map map) {
this.session = map;

So we must create our interceptor that

  1. initialize OGNL value stack with our distributed session

  2. inject our distributed session in Actions that implements SessionAware interface

According to Struts 2 source code, Dispatcher initialize OGNL value stack, and session in particular:

extraContext.put("session", sessionMap);

and ServletConfigInterceptor injects session in Action classes.So we override such behaviour as following :

//initializing session in OGNL value stack
//injecting our session
if (action instanceof SessionAware) {
((SessionAware) action).setSession(distributedSession);

So final configuration is following. struts.xml :

<package name="default" extends="struts-default">
<interceptor name="distributedSession" class="com.interceptors.SessionCheckerInterceptor"/>
<interceptor-stack name="distributedSessionStack">
<interceptor-ref name="basicStack"/>
<interceptor-ref name="distributedSession"/>
<default-interceptor-ref name="distributedSessionStack"/>
<package name="test" extends="default" namespace="/actions">
<action name="logon" class="com.actions.SomeTemporaryAction" method="login">
<result name="success" type="redirectAction">myaccount</result>
<result name="input" type="redirect">/</result>

We call interceptor distributedSession, and it must be in list after basicStack in order to work consistently.
Final version of our Interceptor :

public class SessionCheckerInterceptor extends AbstractInterceptor {
public String intercept(ActionInvocation actionInvocation) throws Exception {
HttpServletRequest request = ServletActionContext.getRequest();
String distributedSession = processCookie(request);
String result = null;
Map sessionBeforeInvokation = JNDICacheableFactory.getSessionManager().getSession(distributedSession);
Object action = actionInvocation.getAction();
if (action instanceof SessionAware) {
((SessionAware) action).setSession(sessionBeforeInvokation);
actionInvocation.getInvocationContext().getContextMap().put("session", sessionBeforeInvokation);
int beforeInvHash = sessionBeforeInvokation.hashCode();

result = actionInvocation.invoke();

Map sessionAfterInvocation = actionInvocation.getInvocationContext().getSession();

int afterInvHash = sessionAfterInvocation.hashCode();

if (afterInvHash != beforeInvHash) {
JNDICacheableFactory.getSessionManager().putSession(distributedSession, sessionAfterInvocation);
return result;

Note: method processCookie omited, in this method cookie processing logic implemented, but if we’d want to realize cross-domain logic we might use URL rewriting for example (in our case we use cookies because we use subdomains of similar domain).

Note: for checking updates in session we compare their hashes, but other implementations are possible. If session changed it is updated.