I'm considering the following FlowExecutionManager as a way to guarantee only one execution of a flow per http session and to 'rejoin' a previously abandoned flow without specifying the flow execution id. Flows are designated as singletons by adding the 'singleton' attribute in the flow definition.
SWF team, is this a bad idea? I suppose a flow marked as a singleton precludes launching a flow as a child of itself. Can you think of any other gotchas that would make using this problematic? Thanks!
Code:import org.springframework.webflow.Event; import org.springframework.webflow.Flow; import org.springframework.webflow.execution.FlowExecution; import org.springframework.webflow.execution.FlowExecutionStorage; import org.springframework.webflow.execution.FlowExecutionStorageException; import org.springframework.webflow.execution.NoSuchFlowExecutionException; import org.springframework.webflow.execution.servlet.ServletEvent; import org.springframework.webflow.execution.servlet.ServletFlowExecutionManager; import java.io.Serializable; /** * Enables singleton behavior for flows with the <code>singleton</code> attribute * set to "true". * <p/> * Specifically, when a singleton flow is requested by a client the HTTP session will * be examined for a pre-existing flow execution id for the requested flow. If one is found * then the client will rejoin that flow execution. * <p/> * The default behavior still applies to non-singleton flows. Each request for a flow * that does not specify a flow execution id will generate a new flow execution. * * @author Alex Wolfe */ public class SingletonFlowExecutionManager extends ServletFlowExecutionManager { private static final String SINGLETON_ANNOTATION = "singleton"; /** * Get the flow execution id. If the execution id is provided by the <code>event</code> * then use it. Otherwise if the flow is annotated as a singleton, examine the session * to determine if an execution of the flow has already been stored. If it has, then we * want to reuse the stored execution, and thus its id is returned. * <p/> * If no flow execution was requested and the flow is not a singleton (or if no singleton * excution id was found in the session), then return <code>null</code>. * * @param event The source http event * @return Either the requested flow execution id, the singleton flow execution id, or <code> * null</code> if no flow execution id is available */ protected String getFlowExecutionId(Event event) { String flowExecutionId = super.getFlowExecutionId(event); return flowExecutionId == null ? getFlowExecutionIdFromSession(event, getFlow(event)) : flowExecutionId; } /** * Examine the HTTP Session for a pre-existing flow execution id for the specified * singleton flow. If the flow isn't a singleton, then simply return null. * Flow execution ids are stored in the session, keyed by the corresponding flow id * for all singleton flows. * * @param requestingEvent The external http event * @param flow The flow * @return The flow execution id for the specified singleton flow, if it the specified flow * is a singleton, otherwise return </code>null</code> */ protected String getFlowExecutionIdFromSession(Event requestingEvent, Flow flow) { if (isSingleton(flow)) return (String) ServletEvent.getSession(requestingEvent, false).getAttribute(flow.getId()); return null; } /** * If the specified <code>flow</code> is a singleton flow, then set the <code>flowExecutionId</code> * in the http session keyed by flow id. Otherwise, do nothing. * * @param requestingEvent The external http event * @param flow The flow * @param flowExecutionId The flow execution id to set * @return The unmodified flowExecutionId */ protected Serializable setFlowExecutionIdInSession(Event requestingEvent, Flow flow, Serializable flowExecutionId) { if (isSingleton(flow)) ServletEvent.getSession(requestingEvent, false).setAttribute(flow.getId(), flowExecutionId); return flowExecutionId; } /** * Is the specified flow a singleton flow? Meaning, do all requests from a single client for * this flow use the same flow execution? * * @param flow The flow * @return <code>true</code> if the flow is a singleton, otherwise <code>false</code> */ protected boolean isSingleton(Flow flow) { return flow.containsAttribute(SINGLETON_ANNOTATION) && flow.getAttribute(SINGLETON_ANNOTATION).equals("true"); } /** * Set the <code>FlowExecutionStorage</code> strategy after anonymously wrapping * the specified <code>storage</code> such that all calls to * <code>FlowExecutionStorage.save(Serializable, FlowExecution, Event)</code> map * root flow id to flow execution id in the http session. * <p/> * This mapping is later examined to rejoin previously started flow executions using * nothing more than the flow id. * * @param storage The <code>FlowExecutionStorage</code> to be wrapped and set. */ public void setStorage(final FlowExecutionStorage storage) { super.setStorage(new FlowExecutionStorage() { public FlowExecution load(Serializable id, Event requestingEvent) throws NoSuchFlowExecutionException, FlowExecutionStorageException { return storage.load(id, requestingEvent); } public Serializable save(Serializable id, FlowExecution flowExecution, Event requestingEvent) throws FlowExecutionStorageException { return setFlowExecutionIdInSession( requestingEvent, flowExecution.getRootFlow(), storage.save(id, flowExecution, requestingEvent) ); } public void remove(Serializable id, Event requestingEvent) throws FlowExecutionStorageException { storage.remove(id, requestingEvent); } }); } }


Reply With Quote