Announcement Announcement Module
Collapse
No announcement yet.
Is a 'singleton' flow a bad idea? Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • 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 &#123;
        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 &#40;or if no singleton
         * excution id was found in the session&#41;, 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&#40;Event event&#41; &#123;
            String flowExecutionId = super.getFlowExecutionId&#40;event&#41;;
            return flowExecutionId == null ? getFlowExecutionIdFromSession&#40;event, getFlow&#40;event&#41;&#41; &#58; flowExecutionId;
        &#125;
    
        /**
         * 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&#40;Event requestingEvent, Flow flow&#41; &#123;
            if &#40;isSingleton&#40;flow&#41;&#41;
                return &#40;String&#41; ServletEvent.getSession&#40;requestingEvent, false&#41;.getAttribute&#40;flow.getId&#40;&#41;&#41;;
            return null;
        &#125;
    
        /**
         * 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&#40;Event requestingEvent, Flow flow, Serializable flowExecutionId&#41; &#123;
            if &#40;isSingleton&#40;flow&#41;&#41;
                ServletEvent.getSession&#40;requestingEvent, false&#41;.setAttribute&#40;flow.getId&#40;&#41;, flowExecutionId&#41;;
            return flowExecutionId;
        &#125;
    
        /**
         * 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&#40;Flow flow&#41; &#123;
            return flow.containsAttribute&#40;SINGLETON_ANNOTATION&#41; &&
                    flow.getAttribute&#40;SINGLETON_ANNOTATION&#41;.equals&#40;"true"&#41;;
        &#125;
    
        /**
         * Set the <code>FlowExecutionStorage</code> strategy after anonymously wrapping
         * the specified <code>storage</code> such that all calls to
         * <code>FlowExecutionStorage.save&#40;Serializable, FlowExecution, Event&#41;</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&#40;final FlowExecutionStorage storage&#41; &#123;
            super.setStorage&#40;new FlowExecutionStorage&#40;&#41; &#123;
                public FlowExecution load&#40;Serializable id, Event requestingEvent&#41; 
                        throws NoSuchFlowExecutionException, FlowExecutionStorageException &#123;
                    return storage.load&#40;id, requestingEvent&#41;;
                &#125;
    
                public Serializable save&#40;Serializable id, FlowExecution flowExecution, Event requestingEvent&#41; 
                        throws FlowExecutionStorageException &#123;
                    return setFlowExecutionIdInSession&#40;
                            requestingEvent,
                            flowExecution.getRootFlow&#40;&#41;,
                            storage.save&#40;id, flowExecution, requestingEvent&#41;
                    &#41;;
                &#125;
    
                public void remove&#40;Serializable id, Event requestingEvent&#41; throws FlowExecutionStorageException &#123;
                    storage.remove&#40;id, requestingEvent&#41;;
                &#125;
            &#125;&#41;;
        &#125;
    &#125;

  • #2
    Should work fine I guess.
    Keep in mind that in technical terms the 'Flow' (e.g. the definition of the flow) is a singleton anyway. What you're trying to do is make sure there is only a single FlowExecution per HTTP session.
    I don't see why such a 'singleton' flow can't be a child of itself. That does not preclude having a 'singleton FlowExecution per HTTP session'.

    Erwin

    Comment


    • #3
      Originally posted by klr8
      I don't see why such a 'singleton' flow can't be a child of itself. That does not preclude having a 'singleton FlowExecution per HTTP session'.
      I suppose you're right. Consider the following simple singleton flow with states A, B, & C:

      A -> B -> C

      Let's say state B causes this same flow to be launched as a sub-flow. Since the flow execution is reused, won't we effectively be starting the sub-flow
      state B? Or if we restart the sub-flow at state A, then when we rejoin the parent-flow the state of the parent flow could be indeterminate. Unless I'm missing something, things get really tricky when nesting flows with only one flow execution.

      Comment


      • #4
        The scenario you describe is fully supported inside a single FlowExecution. Check the PhoneBook sample: the detail flow is used as a subflow by the search flow, but the detail flow can use the detail flow itself as a subflow, allowing you to call into details to any depth and come back up to the call stack without any problems.

        Erwin

        Comment


        • #5
          Originally posted by klr8
          The scenario you describe is fully supported inside a single FlowExecution. Check the PhoneBook sample: the detail flow is used as a subflow by the search flow, but the detail flow can use the detail flow itself as a subflow, allowing you to call into details to any depth and come back up to the call stack without any problems.

          Erwin
          Ah. Of course. Launching a subflow does not create a new flow execution.

          So essentially, this code guarantees that there will only be one flow execution for a given root flow id, which might be useful unless the app is defined as one big flow.

          :?

          Comment

          Working...
          X