Overview
Spring Web Flow allows you to manage real conversational state on the web.In general, for a given user you allow one flow (i.e. conversation) at a time. This flow may launch sub-flows, but technically, your user is still running one flow at a time.
Let’s address a more complex scenario where you would like to let your user start several independent flows in parallel and allow your user to switch from one flow to another.
Nothing prevents you from starting multiple flows. You just have to provide one or several links pointing to a flow start. Your user may click on them several times and launch several flows in parallel. But,how do you allow the user to switch from one flow to another?
The first trivial solution is to start each flow in a separate window. A more user-friendly solution is to provide a flow menu, listing all the active flows.
This feature is not built-in in Spring Web Flow, but the Spring Web Flow API is flexible enough to let us implement it.
Note that the code shown below was generated by SpringFuse
Here is the Java code
You first need to create a FlowExecutionListener to capture for each running flow a menu entry, that is a URL and a Label.
package com.jaxio.example.web.flow;
/**
* Captures a relevant URL and Label to be displayed in a flow Menu.
*/
public class FlowMenuExecutionListener extends FlowExecutionListenerAdapter {
@Autowired
private FlowMenu flowMenu;
@Override
public void viewRendering(RequestContext context, View view, StateDefinition viewState) {
// we ignore non view state and popups
if (!viewState.isViewState() || ((ViewState) viewState).getPopup()) {
return;
}
flowMenu.updateFlowMenuItem(context.getFlowExecutionUrl(), getFlowMenuItemLabel(context));
}
@Override
public void stateEntering(RequestContext context, StateDefinition state) throws EnterStateVetoException {
if (state instanceof EndState) {
flowMenu.removeFlowMenuItem(context.getFlowExecutionUrl());
}
}
@Override
public void sessionEnding(RequestContext context, FlowSession session, String outcome, MutableAttributeMap output) {
flowMenu.removeFlowMenuItem(context.getFlowExecutionUrl());
}
private String getFlowMenuItemLabel(RequestContext context) {
return (String) context.getFlowScope().get("menuLabel");
}
}
The code above relies on the following bean below:
package com.jaxio.example.web.flow;
// this bean has a session scope (see conf file)
/**
* A session scope bean (see conf file) that holds the
* user's active.
*/
public class FlowMenu implements Serializable {
static final private long serialVersionUID = 1L;
private Map<String, FlowMenuItem> menu = new LinkedHashMap<String, FlowMenuItem>();
/**
* Create or update a menu entry.
*/
public void updateFlowMenuItem(String flowExecutionUrl, String label) {
menu.put(getFlowExecutionId(flowExecutionUrl), new FlowMenuItem(flowExecutionUrl, label));
}
/**
* Remove a menu entry.
*/
public void removeFlowMenuItem(String flowExecutionUrl) {
menu.remove(getFlowExecutionId(flowExecutionUrl));
}
/**
* Extract string that represent the flow execution id, for example:
* http://localhost:8080/flow/myflow?execution=e12s2 becomes
* http://localhost:8080/flow/myflow?execution=e12
*/
private String getFlowExecutionId(String flowExecutionUrl) {
return flowExecutionUrl.substring(0, flowExecutionUrl.lastIndexOf('s'));
}
/**
* In flow end-state, instead of redirecting to a fixed page,
* you can call this method to redirect the user
* to a flow that is not yet ended.
*/
public String getEndStateRedirect() {
if (menu.isEmpty()) {
return defaultExternalRedirect;
} else {
return "serverRelative:" + menu.values().iterator().next().getUrl();
}
}
/**
* Called from the view in charge of displaying the menu.
*/
public List<FlowMenuItem> getFlowMenuAsList() {
return new ArrayList<FlowMenuItem>(menu.values());
}
/**
* Holds the label/url.
*/
public class FlowMenuItem implements Serializable {
static final private long serialVersionUID = 1L;
String url;
String label;
public FlowMenuItem(String url, String label) {
this.url = url;
this.label = label != null ? label : url;
}
public String getUrl() {
return url;
}
public String getLabel() {
return label;
}
@Override
public String toString() {
return url + ":" + label;
}
}
//---------------------------------------------------
// Configuration
//---------------------------------------------------
private String defaultExternalRedirect = "contextRelative:index.action";
public void setDefaultExternalRedirect(String defaultExternalRedirect) {
this.defaultExternalRedirect = defaultExternalRedirect;
}
Note that the listener is a singleton while the flowMenu bean must be a session bean. You must declare your flowMenu bean in your configuration file as a scoped-proxy.Please read Spring documentation for more information.
Beside that, if you are familiar with spring Web flow and Spring MVC, the configuration is trivial.
Here is how the configuration file should look like.
How do we get the label?
The label string must be set in the flow as a flowScope variable and be named ‘menuLabel’.You can set it inside an on-start tag and update it at will in view-state if you want your label to vary during the flow execution. Here is an example (again from a project generated with SpringFuse):
Redirection on flow ending
Would not it be nice to redirect the user to one of the other active flows when he/she ends a flow?This is already implemented in the flowMenu bean, all you have to do is set the view attribute of your end-state as illustrated below:
How to access the menu bean from the view?
Now you need to access the menu from the view, in a JSP you can do this:
import com.jaxio.example.web.flow.FlowMenu;
/**
* This interceptor is responsible for binding useful beans on the ModelMap so they can be used
* from the view.
*/
@Service
public class BeanInViewInterceptor implements HandlerInterceptor {
@Autowired
private FlowMenu flowMenu;
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws ServletException {
// Note: using the modelAndView in the postHandle would not work
// view returned by Spring Web Flow
request.setAttribute("flowMenu", flowMenu);
// proceed
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) {
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
}
}
Don't forget to declare this interceptor... in your interceptor chain.
0 comments:
Post a Comment