Announcement Announcement Module
Collapse
No announcement yet.
Catching back button. Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • Catching back button.

    All,

    I'm currently evaluating spring webflow in my current (financial) application. I wonder if there is any way to catch back button in SWF? I've read Keith's blog stating that _currentStateId could help possibly detect back/forward buttons and other flow violations. I can imagine that if user hits back button and then submit this to SWF we can, using submitted _curentStateId and state id stored in the session (which, in that case are different), detect that something went wrong. But I wonder how can I detect this situation, and (since i consider this behavior as exceptional) take some action (eg. throw exception, restart flow etc.).

    Artur

  • #2
    Artur,

    You can detect several situations:

    - that no matching transition was found for an event that occured in the current state. This can happen if the user hits back while in a subflow and in the process skips back up to a page rendered by a view state in a suspended parent flow. In this case you can catch NoMatchingTransitionException and rollback by rentering the current state, or go to an error page with a flow restart or "continue" capability. To prevent the possibility for this, you can use continuations.

    - that a client referenced an executing flow that could not be found -- a NoSuchFlowExecution exception is thrown in this case. This can occur if the flow ends, caching is turned on, you're not using continuations, and the user tries to go back. The simplest thing to do here is to disable caching in the FlowController (the default now.) For catching this exception, if the client also provides the _flowId in the request along with the _flowExecutionId it would be possible to catch this with enough information to start a new flow execution.

    More information is available here: http://opensource.atlassian.com/conf...sked+Questions

    Comment


    • #3
      Another option you have is using a continuations based flow execution storage strategy. That way all back/refresh button related problems go away. Check the "sellitem" sample app for an example.

      Erwin

      Comment


      • #4
        I have looked at the sellitem sample app (without running it) and am still confused about how to make it work. In particular, the comment above the bean declaration for FlowController in the dispatcher-servlet.xml file that says, "Note that there is no default top-level flow configured. The flow id of the flow to start should be passed in using the "_flowId" request parameter" leaves me mystified.

        Can I configure a top-level flow? I can't find where sellitem is passing in the "_flowId".
        :cry:

        Comment


        • #5
          The flow is launched from the index.jsp page:

          [code[]
          <A href="pos.htm?_flowId=sellItem">Sell Item</A>
          [/code]

          See the flowLauncher example for a more comprehensive illustration of how to launch flows.

          Comment


          • #6
            Originally posted by Keith Donald
            Artur,

            - that a client referenced an executing flow that could not be found -- a NoSuchFlowExecution exception is thrown in this case. This can occur if the flow ends, caching is turned on, you're not using continuations, and the user tries to go back. The simplest thing to do here is to disable caching in the FlowController (the default now.) For catching this exception, if the client also provides the _flowId in the request along with the _flowExecutionId it would be possible to catch this with enough information to start a new flow execution.
            I have similar problem. The flow ends and the user clicks the back button.
            The requirement is to display the 1st page with an error message in this case.
            So how can I catch the NoSuchFlowExecution? It is thrown before any my action get executed.
            Currently my work-round is to overwrite the public ViewDescriptor onEvent(Event event, FlowExecutionListener listener) method.
            Is there any better solution?

            Code:
            public class WizardFlowExecutionManager extends ServletFlowExecutionManager &#123;
                
                public static final String IS_FLOW_FINISHED_ATTRIBUTE = "_isFlowFinished";
            
                public ViewDescriptor onEvent&#40;Event event, FlowExecutionListener listener&#41;
                        throws Exception &#123;
                    FlowExecution flowExecution;
                    ViewDescriptor viewDescriptor;
                    String id = getFlowExecutionId&#40;event&#41;;
                    if &#40;id == null&#41; &#123;
                        // start a new flow execution
                        flowExecution = createFlowExecution&#40;getFlow&#40;event&#41;&#41;;
                        if &#40;listener != null&#41; &#123;
                            flowExecution.getListeners&#40;&#41;.add&#40;listener&#41;;
                        &#125;
                        viewDescriptor = flowExecution.start&#40;event&#41;;
                    &#125; else &#123;
                        // client is participating in an existing flow execution,
                        // retrieve information about it
                        try &#123;
                            flowExecution = getStorage&#40;&#41;.load&#40;id, event&#41;;
                            // rehydrate the execution if neccessary &#40;if it had been
                            // serialized out&#41;
                            flowExecution.rehydrate&#40;getFlowLocator&#40;&#41;, getListeners&#40;&#41;,
                                    getTransactionSynchronizer&#40;&#41;&#41;;
                            if &#40;listener != null&#41; &#123;
                                flowExecution.getListeners&#40;&#41;.add&#40;listener&#41;;
                            &#125;
                            // signal the event within the current state
                            Assert
                                    .hasText&#40;
                                            event.getId&#40;&#41;,
                                            "No event id could be obtained -- "
                                                    + "make sure the submitting view or other client provides it as input"&#41;;
                            // see if the eventId was set to a static marker placeholder
                            // because
                            // of a client configuration error
                            if &#40;event.getId&#40;&#41;.equals&#40;getNotSetEventIdParameterMarker&#40;&#41;&#41;&#41; &#123;
                                throw new IllegalArgumentException&#40;
                                        "The received event id was the 'not set' marker '"
                                                + getNotSetEventIdParameterMarker&#40;&#41;
                                                + "' -- this is likely a view &#40;jsp, etc&#41; configuration error --"
                                                + "the event id parameter must be set to a valid event"&#41;;
                            &#125;
                            viewDescriptor = flowExecution.signalEvent&#40;event&#41;;
                        &#125; catch &#40;NoSuchFlowExecutionException e&#41; &#123;
                            // start a new flow execution
                            flowExecution = createFlowExecution&#40;getFlow&#40;event&#41;&#41;;
                            if &#40;listener != null&#41; &#123;
                                flowExecution.getListeners&#40;&#41;.add&#40;listener&#41;;
                            &#125;
                            viewDescriptor = flowExecution.start&#40;event&#41;;
                            // announce that the flow is already finished.
                            viewDescriptor.addObject&#40;IS_FLOW_FINISHED_ATTRIBUTE, Boolean.TRUE&#41;;
                        &#125;
                    &#125;
                    if &#40;flowExecution.isActive&#40;&#41;&#41; &#123;
                        // save the flow execution for future use
                        id = getStorage&#40;&#41;.save&#40;id, flowExecution, event&#41;;
                    &#125; else &#123;
                        // event execution resulted in the entire flow execution ending,
                        // cleanup
                        if &#40;id != null&#41; &#123;
                            getStorage&#40;&#41;.remove&#40;id, event&#41;;
                        &#125;
                    &#125;
                    if &#40;listener != null&#41; &#123;
                        flowExecution.getListeners&#40;&#41;.remove&#40;listener&#41;;
                    &#125;
                    if &#40;logger.isDebugEnabled&#40;&#41;&#41; &#123;
                        logger
                                .debug&#40;"Returning selected view descriptor "
                                        + viewDescriptor&#41;;
                    &#125;
                    return prepareViewDescriptor&#40;viewDescriptor, id, flowExecution&#41;;
                &#125;
            &#125;

            Comment


            • #7
              Why are you subclassing FlowExecutionManager? You should definitely not do this for that.

              Just have a handler exeception resolver map your NoSuchFlowExecution exception to an error page. Consider passing around the _flowId from the views to support a flow start capability from that error page as well.

              Comment


              • #8
                Originally posted by Keith Donald
                Consider passing around the _flowId from the views to support a flow start capability from that error page as well.
                What's the recommended way to get the flowId into the view? As you know, the FlowExecutionManager.prepareViewDescriptor method does not insert the flow id into the view descriptor. Should it just be hard coded in the view?

                Thanks!

                -Alex

                Comment


                • #9
                  Originally posted by akw
                  Originally posted by Keith Donald
                  Consider passing around the _flowId from the views to support a flow start capability from that error page as well.
                  What's the recommended way to get the flowId into the view? As you know, the FlowExecutionManager.prepareViewDescriptor method does not insert the flow id into the view descriptor. Should it just be hard coded in the view?

                  Thanks!

                  -Alex
                  I suppose I could use 'flowExecution.activeFlow.id' but that seems prone to break if accessors are renamed, etc. Would you consider adding the 'flowId' attribute to the view model?

                  Comment

                  Working...
                  X