the 'output' tag is meant to be used as a container to transfer pojos between subflows flows and the parent flow callers. However, the following enhancement request:
https://jira.spring.io/browse/SWF-1561
expanded the role of the 'output' tag to allow the transfer of flash variables from the 'end-state' of a webflow to a spring MVC controller's flashMap.
Unfortunately, this enhancement did NOT include what you want to achieve which is passing variables via an externalRedirect from flow A -> new Flow A. So as with any 3rd party lib that doesn't have the desired functionality... we're going to have to 'hack' it.
1. First you need to have your FlowHandlerAdapter configured like this to utilize the enhancement above:
the "saveOutputToFlashScopeOnRedirect" set to true is the important part for this discussion (set to false by default).
<bean class="org.springframework.webflow.mvc.servlet.FlowHandlerAdapter">
<property name="flowExecutor" ref="flowExecutor"/>
<property name="saveOutputToFlashScopeOnRedirect" value="true"/>
</bean>
2. You will need to create (extend) and configure a FlowExecutionListenerAdapter.
import java.util.Map;
import java.util.Map.Entry;
import javax.servlet.http.HttpServletRequest;
import org.springframework.web.servlet.support.RequestContextUtils;
import org.springframework.webflow.core.collection.MutableAttributeMap;
import org.springframework.webflow.core.collection.SharedAttributeMap;
import org.springframework.webflow.definition.FlowDefinition;
import org.springframework.webflow.execution.FlowExecutionListenerAdapter;
import org.springframework.webflow.execution.RequestContext;
public class TestFlowExecutionListenerAdapter extends FlowExecutionListenerAdapter{
@Override
public void sessionCreating(RequestContext context, FlowDefinition definition) {
MutableAttributeMap<Object> flashScopeWebFlow = context.getFlashScope();
MutableAttributeMap<Object> flashMapWebFlow = context.getFlowScope();
SharedAttributeMap<Object> sessionMap = context.getExternalContext().getSessionMap();
MutableAttributeMap<Object> requestMap = context.getExternalContext().getRequestMap();
HttpServletRequest request = (HttpServletRequest)context.getExternalContext().getNativeRequest();
Map<String, ?> flashMapMvc = RequestContextUtils.getInputFlashMap(request);
if(flashMapMvc != null)
putAllFlashMapToFlashScope(flashMapMvc,flashScopeWebFlow);
System.out.println("here");
}
public static void putAllFlashMapToFlashScope(Map<String, ?> map, MutableAttributeMap<Object> mutableAttributeMap) {
for( Entry<String, ?> entry : map.entrySet()) {
mutableAttributeMap.put(entry.getKey(), entry.getValue());
}
}
}
And init the bean like this:
<webflow:flow-executor id="flowExecutor" flow-registry="flowRegistry">
<webflow:flow-execution-listeners>
<webflow:listener ref="testFlowExecutionListenerAdapter" />
</webflow:flow-execution-listeners>
</webflow:flow-executor>
<bean id="testFlowExecutionListenerAdapter" class="com.foo.bar.flowexeclisteners.TestFlowExecutionListenerAdapter"/>
Note: Change "com.foo.bar.flowexeclisteners" to your actual package path.
3. The FlowExecutionListenerAdapter above allows to to monitor life cycle events of Spring Webflow framework by @Overriding certain methods. In our case, I chose to @Override the sessionCreating() method but you can use from many different methods to meet your use case or to increase efficiency.
http://docs.spring.io/spring-webflow/docs/current/api/org/springframework/webflow/execution/FlowExecutionListenerAdapter.html
So given the above configuration we can now begin passing back variables in 2 ways. One via sessionMap or Two via the 'output' tag which is now configured to be stored in the flashMap (of Spring MVC not flashScope). Here is an example 'end-state' demonstrating this.
<end-state id="home" view="externalRedirect:/books/">
<on-entry>
<evaluate expression="externalContext.sessionMap.put('author', 'William Brian Jennings')"/>
</on-entry>
<output name="responseMsg" value="'Added author to the sessionMap'"/>
</end-state>
4. When this 'end-state' is triggered (if you place a break point inside our FlowExecutionListenerAdapter) you will see that flashMapMvc will hold your 'responseMsg' variable and that 'author' variable will be in the sessionMap. The static method 'putAllFlashMapToFlashScope' will automatically make your 'responseMsg' avaliable for consumption by your view but for the sessionMap you will need to explicitly extract your variable out in your receiving flow like so:
<set name="flowScope.author" value="externalContext.sessionMap.get('author')"/>
A few notes here:
The hacky part of all this is we have to convert the flashMapMvc to flashScopeWebFlow because they are not compatible (2 different containers). I used a static method 'putAllFlashMapToFlashScope()' within the FlowExecutionListenerAdapater class to demonstrate this. I don't think this is a good practice but I just did it here for brevity so you can see what exactly what the issues are and how to solve them.
sessionMap variables will stay during the entire session and across all flows. If you use this map becareful of this point.
I included other (unused) maps in the FlowExecutionListenerAdapter to demonstrate what you have access to.
Obviously this is a hack solution and what really needs to happen is an enhancement request to achieve what your trying to do (flow A ends) -> pass output back to -> (new flow A) via external redirect.
Because of issues like this I've personally stopped using WebFlow for all use cases and have limited it only to simple cases (i.e page A -> Page B -> page C) and now I use Spring MVC exclusively for all other use cases.
Sorry for the long answer but this is not a trivial issue :)