Is a 'singleton' flow a bad idea?
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);
}
});
}
}