Controlling conversational state management

Spring Web Flow automatically manages conversational state, i.e. data associated with the conversations users have with your web application. This tip shows you how to control Spring Web Flow's state management.
04/11/2006 - By Erwin Vervaet (erwin@ervacon.com) - Spring Web Flow version 1.0

One of the major benefits of Spring Web Flow (SWF) is that it automatically manages the state associated with the conversations users have with your web application. SWF ensures that data in each of the defined scopes (request, flash, flow and conversation) is available for the lifetime of the scope and will be cleaned up when possible.

The FlowExecutionRepository

At a technical level, SWF stores all this data in a FlowExecution object. Since some of the data, e.g. the data stored in flow scope, needs to be maintained over several requests into the application, SWF needs to save the FlowExecution object when one request ends and load it again on the next request for that particular flow execution. This job is the responsibility of the FlowExecutionRepository.

Let's look at a bit of configuration code to make this more tangible. The following piece of XML is a typical configuration of a SWF FlowController and it's associated FlowExecutor. As you can see, the FlowExecutor is configured with a particular type of FlowExecutionRepository.

<bean name="/flows.html" class="org.springframework.webflow.executor.mvc.FlowController">
	<property name="flowExecutor" ref="flowExecutor"/>
</bean>

<bean id="flowExecutor" class="org.springframework.webflow.config.FlowExecutorFactoryBean">
	<property name="definitionLocator" ref="flowRegistry"/>
	<property name="repositoryType" value="CONTINUATION"/>
</bean>

We get a similar thing when using the SWF config XSD. Note the repository-type attribute of the <flow:executor> element.

<bean name="/flows.html" class="org.springframework.webflow.executor.mvc.FlowController">
	<property name="flowExecutor" ref="flowExecutor"/>
</bean>
	
<flow:executor id="flowExecutor" registry-ref="flowRegistry" repository-type="continuation"/>

SWF includes the following FlowExecutionRepository implementations.

The ConversationManager

All of these flow execution repository implementations delegate actual data storage and management responsibilities to a ConversationManager. The client repository type currently uses a no-op conversation manager that effectively does nothing. The simple and continuation repository types use the SessionBindingConversationManager.

The SessionBindingConversationManager is the only conversation manager implementation that ships with SWF. It simply stores conversational state in the HTTP session. When the HTTP session expires, all flow executions (conversations) within it will also expire.

Controlling resource usage

Since the simple and continuation repository types store data in the HTTP session maintained by the servlet engine, we should carefully control server-side resource usage. Several parameters should be taken into account.

  1. The first thing to configure properly is the HTTP session timeout. As explained above, SWF conversational state is effectively stored in the HTTP session, so it is important to expire idle sessions after a reasonable amount of time to free up server resources. HTTP session timeout is configured in the web.xml deployment descriptor of your web application. Here is an example from a Servlet 2.4 webapp:

    <session-config>
    	<session-timeout>10</session-timeout>
    </session-config>
    

    Note that the provided value is in minutes.

  2. The next thing to consider is the maximum number of conversations allowed in a single HTTP session. Typically a user can only be involved in a single conversation with the application at a particular point in time. However, there are also situations where you could have multiple concurrent conversations between a single user and the application.

    The SessionBindingConversationManager has a maxConversations property that controls this. Here is an example configuration:

    <bean id="conversationManager"
    	class="org.springframework.webflow.conversation.impl.SessionBindingConversationManager">
    	<property name="maxConversations" value="1"/>
    </bean>
    

    The default value for maxConversations is -1, meaning that there is no limit to the number of concurrently active conversations in a single HTTP session. If you do specify a maximum, the conversation manager will automatically end the oldest conversation when a new one starts to ensure that the maximum is not exceeded.

  3. The final setting to control is the maximum number of continuation snapshots allowed for a single flow execution. In other words, how many physical copies are possible for a single logical flow execution. This setting is of course only relevant for the continuation flow execution repository type.

    The maxContinuations property of the ContinuationFlowExecutionRepository controls this. The default value is -1, meaning there is no limit.

    <bean id="repository"
    	class="org.springframework.webflow.execution.repository.continuation.ContinuationFlowExecutionRepository">
    	...
    	<property name="maxContinuations" value="30"/>
    </bean>
    

    As mentioned above, the continuation repository type will create a new continuation snapshot of the flow execution for each request coming into the flow execution. This provides maximum freedom to the users using your application since they can press 'Back' or 'Forward' any number of times and get a correct response. The reason being that all of the flow execution continuation snapshots for the active conversation are maintained server-side, so you can always go back to them an continue where you left of.

    An obvious danger with this is of course that a single user can use a large amount of server resources because he (inadvertently) causes a lot of continuation snapshots to be created, for instance by going back a lot. A malicious user could even use this to do a denial of server (DoS) attack, causing an OutOfMemoryError.

    The ContinuationFlowExecutionRepository will remove the oldest continuation snapshot when a new one is created that would exceed the maximum count. You should set the maxContinuations value high enough to not interfere with the user experience of a normal user, but low enough to avoid excessive resource consumption for a single user. Values like 30 are ideal in most situations.

Besides setting these configuration parameters, you should also be careful not to store unnecessarily large data structures in long lived scope types, typically flow scope.

Example

Let's put everything we've discussed so far together and configure a sample application with some reasonable values for the maxConversations and maxContinuations properties. As you will see, SWF 1.0 only allows these properties to be configured when using the classic Spring bean configuration format. We won't be able to use the <flow:executor> convenience element of the config XSD. This will be improved in version 1.0.1 (SWF-210).

<bean id="flowExecutor" class="org.springframework.webflow.executor.FlowExecutorImpl">
	<constructor-arg ref="flowRegistry"/>
	<constructor-arg>
		<bean class="org.springframework.webflow.engine.impl.FlowExecutionImplFactory">
			<property name="executionAttributesMap" ref="executionAttributes"/>
		</bean>
	</constructor-arg>
	<constructor-arg ref="repository"/>
</bean>

<bean id="repository"
	class="org.springframework.webflow.execution.repository.continuation.ContinuationFlowExecutionRepository">
	<constructor-arg>
		<bean class="org.springframework.webflow.engine.impl.FlowExecutionImplStateRestorer">
			<constructor-arg ref="flowRegistry"/>
			<property name="executionAttributesMap" ref="executionAttributes"/>
		</bean>
	</constructor-arg>
	<constructor-arg ref="conversationManager"/>
	<property name="maxContinuations" value="30"/>
</bean>

<bean id="conversationManager"
	class="org.springframework.webflow.conversation.impl.SessionBindingConversationManager">
	<property name="maxConversations" value="1"/>
</bean>

<bean id="executionAttributes" class="org.springframework.beans.factory.config.MapFactoryBean">
	<property name="sourceMap">
		<map>
			<entry key="alwaysRedirectOnPause">
				<value type="java.lang.Boolean">true</value>
			</entry>
		</map>
	</property>
</bean>

Instead of all this XML craziness, it's probably better to subclass the FlowExecutorFactoryBean and override the createExecutionRepository method like this:

public class SampleFlowExecutorFactoryBean extends FlowExecutorFactoryBean {
	
	private int maxContinuations;
	
	public void setMaxContinuations(int maxContinuations) {
		this.maxContinuations = maxContinuations;
	}
	
	@Override
	protected FlowExecutionRepository createExecutionRepository(
			RepositoryType repositoryType, FlowExecutionStateRestorer executionStateRestorer,
			ConversationManager conversationManager) {
		if (repositoryType == RepositoryType.CONTINUATION) {
			ContinuationFlowExecutionRepository repo =
				new ContinuationFlowExecutionRepository(executionStateRestorer, conversationManager);
			repo.setMaxContinuations(maxContinuations);
			return repo;
		}
		else {
			return super.createExecutionRepository(repositoryType, executionStateRestorer,
					conversationManager);
		}
	}
}

That way we end up with the following more manageable XML configuration:

<bean id="flowExecutor" class="com.ervacon.webflow.sample...SampleFlowExecutorFactoryBean">
	<property name="definitionLocator" ref="flowRegistry"/>
	<property name="conversationManager" ref="conversationManager"/>
	<property name="repositoryType" value="CONTINUATION"/>
	<property name="maxContinuations" value="30"/>
</bean>

<bean id="conversationManager"
	class="org.springframework.webflow.conversation.impl.SessionBindingConversationManager">
	<property name="maxConversations" value="1"/>
</bean>

10/01/2007 UPDATE: As already mentioned above, Spring Web Flow 1.0.1 makes configuration of the maxConversations and maxContinuations values much easier. With version 1.0.1 we can configure the flow executor as follows:

<flow:executor id="flowExecutor" registry-ref="flowRegistry">
	<flow:repository type="continuation" max-conversations="1" max-continuations="30"/>
</flow:executor>

Discussion

Discuss this tip with fellow Spring Web Flow users on the Spring Web Flow forum.

For more Spring Web Flow related information, visit the Ervacon Spring Web Flow Portal and read Working with Spring Web Flow.

© Copyright Ervacon 2011. All Rights Reserved. Contact us.
All trademarks and copyrights on this page are owned by their respective owners.