Friday, September 10, 2010

Spring 3 session level Model Attributes with Multi Tab browsers

I wanted to share a solution that I came up with using Spring 3 with SessionAttributes and multiple tabs within the same session. Basically the issue is that if you open the first tab and do a get
to load the command object into the session and then open a second tab and do a get on the same controller with a different command object then the first tab's command object has been replaced by the second tab's command object and if you submit the form on the first tab then it will update the second tab's command object with the data from the first command object!

Spring by default uses the class DefaultSessionAttributeStore to store and retrieve the command objects from the session based upon the session attribute name only. This is what causes the command objects to be stomped on by multiple requests within the same session.

This is a known issue and a JIRA ticket has been created that will address this in version 3.1M1.

To address this issue until the fix is incorporated into Spring, I created the following solution:

I created the a class that implements the SessionAttributeStore interface. This class stores and retrieves the session level command objects based upon a unique (conversation id) that is automatically generated when the command object is stored on the session. So when you do a get request with a controller that has session attributes and you assign your command object to the model map, the storeAttribute method will be invoked and it will generate a unique conversation id (System.currTimeInMillis()) and append this to the attribute name (your command object name) and store it on the session as well as set the conversation id as a request attribute so that the view that is displayed can post back the conversation that it will be involved in. So all you have to do is add a hidden input field in your form that will post back the conversation id that it is in and the controller will issue a retrieveAttribute method and based upon the incoming conversation id will retrieve the correct command object from the session. I created a simple tag library that will create a hidden input field for you.

It is pretty simple to setup, basically you do the following:

In your dispatcher-servlet.xml file you define the class that you want to do the storing and retrieving of command objects from the session

<bean id="sessionConversationAttributeStore" class="com.marty.support.SessionConversationAttributeStore">
    <property name="numConversationsToKeep" value="10"/>
</bean>


Then you tell the AnnotationMethodHandlerAdapter class that you want to use a custom sessionAttributeStore:

<bean class="org.springframework.web.servlet.mvc.annotation.AnnotationMethodHandlerAdapter">
    <property name="webBindingInitializer">
        <bean class="org.springframework.web.bind.support.ConfigurableWebBindingInitializer">
            <property name="conversionService" ref="conversionService" />
        </bean>
    </property>
    <property name="sessionAttributeStore">
        <ref bean="sessionConversationAttributeStore"/>
    </property>     
</bean>


Finally you add the following tag library call inside your forms that use session model attributes:

<sessionConversation:insertSessionConversationId attributeName="<your command name here>"/>


Here is a link to a sample application that shows how to use the custom SessionAttributeStore along with the source code for the SessionConversationAttributeStore class and the SessionConversationIdTag tag library.


Hopefully someone else will find this helpful.

Marty