Accessing a flow execution from outside Spring Web Flow

In some situations it is usefull to get access to a Spring Web Flow flow execution from outside of Spring Web Flow itself. This tip illustrates how that can be accomplished.
25/10/2006 - By Erwin Vervaet (erwin@ervacon.com) - Spring Web Flow version 1.0

Spring Web Flow stores information related to a flow execution, like the contents of flow scope, in a FlowExecution object. When inside the flow execution, for instance in an Action implementation, you can easily access the information contained in the flow execution via the RequestContext. For instance, you could write code like this:

public class LoggingAction extends AbstractAction {

	@Override
	protected Event doExecute(RequestContext context) throws Exception {
		System.out.println(context.getActiveFlow().getId());
		System.out.println(context.getFlowScope().get("searchCriteria"));
		return success();
	}

}

In some situations, it is also usefull to get access to the information in a flow execution from outside the flow execution, typically even from outside Spring Web Flow. An example might be a Servlet Filter that needs to log some information contained in an ongoing flow execution. How do you get access to the FlowExecution object in this case? Before we can answer that question, we need to understand that active flow executions are stored inbetween requests using a FlowExecutionRepository. The next request that arrives for an active flow execution needs to include the unique flow execution key of the active flow execution in the repository. So with the help of a FlowExecutionRepository and the flow execution key stored in the request, we'll be able to get hold of a FlowExecution object.

Let's see how this translates into practice by adding a LoggingFilter to the phonebook sample application that comes with Spring Web Flow. We'll subclass Spring's OncePerRequestFilter for convenience:

public class LoggingFilter extends OncePerRequestFilter {
	
	@Override
	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
			throws ServletException, IOException {

When configuring a FlowController, we need to pass in the FlowExecutor responsible for flow execution management. The flow executor needs a FlowExecutionRepository to be able to store ongoing flow executions. This is configured explicitly in the dispatcher servlet application context:

<bean name="/phonebook.htm" class="org.springframework.webflow.executor.mvc.FlowController">
	<property name="flowExecutor" ref="flowExecutor"/>
	<property name="cacheSeconds" value="5"/>
</bean>
	
<flow:executor id="flowExecutor" registry-ref="flowRegistry" repository-type="continuation"/>

So if we could obtain the FlowController instance and get the repository used by it's flow executor, we should be set! The Spring DispatcherServlet stores its application context in the ServletContext. We can retreive it like so:

ApplicationContext context = 
	(ApplicationContext)getServletContext().getAttribute(
		DispatcherServlet.SERVLET_CONTEXT_PREFIX + "phonebook");

The next step is to get the FlowController from this context and get it's flow executor and argument handler (which can extract the flow execution key from the request). We'll have to downcast the FlowExecutor to FlowExecutorImpl to get access to the repository:

FlowController controller = (FlowController)context.getBean("/phonebook.htm");
FlowExecutionRepository repository = ((FlowExecutorImpl)controller.getFlowExecutor()).getExecutionRepository();
FlowExecutorArgumentHandler handler = controller.getArgumentHandler();

Now we just have to bring everything together. First we create a ServletExternalContext wrapping the request and response. We can pass that to the argument handler to obtain the flow execution key from the request. Finally we can obtain the actual flow execution from the repository using the key. There is only one small extra complexity: we have to set the ExternalContext that we created on a ThreadLocal to make sure it is accessible to all parts of the system. This can be done with the ExternalContextHolder class. The resulting code looks like this:

ExternalContext externalContext = new ServletExternalContext(getServletContext(), request, response);		
		
if (handler.isFlowExecutionKeyPresent(externalContext)) {
	try {
		System.out.println("<<<<");
		ExternalContextHolder.setExternalContext(externalContext);
		FlowExecutionKey flowExecutionKey =
			repository.parseFlowExecutionKey(handler.extractFlowExecutionKey(externalContext));
		FlowExecution flowExecution = repository.getFlowExecution(flowExecutionKey);
				
		System.out.println(flowExecution.getActiveSession().getDefinition().getId());
		System.out.println(flowExecution.getActiveSession().getScope().get("searchCriteria"));
	}
	catch (Exception e) {
		e.printStackTrace();
	}
	finally {
		ExternalContextHolder.setExternalContext(null);
		System.out.println(">>>>");
	}
}

The only thing left to do is continue the filter chain and deploy the filter in the phonebook sample application by declaring it in web.xml:

		chain.doFilter(request, response);
	}
}
<filter>
	<filter-name>logging</filter-name>
	<filter-class>org.springframework.webflow.samples.phonebook.filter.LoggingFilter</filter-class>
</filter>
<filter-mapping>
	<filter-name>logging</filter-name>
	<servlet-name>phonebook</servlet-name>
</filter-mapping>

If we now startup the application again, we see nice logging output like this:

<<<<
search-flow
org.springframework.webflow.samples.phonebook.SearchCriteria@5f3bad
>>>>
<<<<
detail-flow
null
>>>>

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.