Announcement Announcement Module
Collapse
No announcement yet.
recovery of flow state after exception Page Title Module
Move Remove Collapse
X
Conversation Detail Module
Collapse
  • Filter
  • Time
  • Show
Clear All
new posts

  • recovery of flow state after exception

    Hi,
    imagine when in a flow without continuations a user uses the back button.
    Afterwards he produces an event he gets this nice exception page:

    Code:
    HTTP ERROR: 500
    No transition found for event 'submit' in state 'formView' of flow 'birthDate' -- valid transitional criteria are set[eventX] -- likely programmer error, check the set of TransitionCriteria for this state
    RequestURI=/birthdate/birthDate.do
    Would it be conceptual possible to redirect the user instead to an error page to the view from which the flow would expect an event (and put a message in the request a la: don't use the back button my friend)?

    Hm. writing this I recognize it may be possible to write a StateConditionTester which vetoes when a illegal transition is going to be made so that the expected view is shown (just thinking. Can't try this in the moment). What remains is the question how to identify the state to know if a transition is allowed. Using _currentStateID is not an option because it's too easy to manipulate. Is it possible to code the current state into the flowExecutionId, even with sessioncontinuation switched on?

    What do you think. Would this be a possibility to deal with the back button (yes in a somehow restricted way) and avoid sessioncontinuation?
    May be this is already doable and I'm writing for /dev/null?

    Thanks,
    Markus

  • #2
    To date we've taken the position it's the clients job to figure out how to respond to a flow navigation exception - web flow doesn't try to.

    This is easy in Spring MVC with the dispatcher servlet -- simply map the exception to a error page. I imagine Struts has something similiar.

    I don't think the FlowExecutionListener is the right place for this--it's not allowed to manipulate the flow directly, only listen to it.

    Comment


    • #3
      So more concretely, take a look at Spring MVCs HandlerExceptionResolver hierarchy.

      Erwin

      Comment


      • #4
        more precise: if a user jumps out of the flow I can't send him back. The only possibility is an more or less specific error page with the answer: please restart the flow and don't use back button again.

        Is it theoratically possible to resume the flow at the last known position?

        Comment


        • #5
          Not really, unless you use continuations, in which case it becomes transparent and automatic.

          Instead of going to an error page you could let your HandlerExceptionResolver relaunch the flow (maybe with an error message displayed somewhere).

          Erwin

          Comment


          • #6
            That's really a pitty. I think there are a lot of applications which do not necessarily need the full power of continuations or can't use it because they don't want that the user to jump around in the flow. Imagine you have a flow with 20 steps and the user used the back button (accidentaly) in step 18 and is forced to start again.
            I think it would be a very cool feature to resume the flow in this situation.

            Comment


            • #7
              There are a lot of "sneaky issues" when trying to handle back-button use and browser refreshes with web flows. So far the only elegant system we know of is continuations.

              For more information, also check this document: http://opensource.atlassian.com/conf...ght+for+You%3F

              Erwin

              Comment


              • #8
                Ok. SWF is on my list of things to play with (maybe later today), so I'm talking out my arse. But...

                Could you build something into SWF like SMVC's HandlerExceptionResolver + WF? Basically the ability to configure an implicit error transition on every state in the event of an uncaught exception. The controller for this error state could be defined in whatever class is currently managing SWF's flows, possibly even reusing SMVC HandlerExceptionResolvers.

                This way errors are a supported part of SWF. The error state would likely be a terminal state, but I guess doesn't need to be based on the smarts of the controller (HandlerExceptionResolver). It would be nice if in addition to explicit targets that this state could include an implicit special/reserved, 'back to where you where' target.

                Maybe continuations are the only way to do this. I honestly don't know enough about either continuations or SWF to say. Just throwing the idea out.

                Comment


                • #9
                  @ev9d9:
                  Yes continuations are very elegant and they are suited for a lot of situations.
                  But not for all. Though I don't know too much about the internals of the flow I can't imagine that it's so difficult to resume a flow.
                  May be I can shortly describe where I want to use SWF and why I need this resume:

                  I have one request in the flow the user can do up to 3 times (it costs money). This means I have to use a counter in my webflow. I can't use continuations because this will restore the counter back to a former value if the user uses the back button. And I can't throw the user out of the flow to the start because he should not start again with 3 trials. I must resume the webflow with the actual number of counts if the user uses the back button.

                  Yes I think this could be solved with the _currentStateId but this would be against the online security rules of the customer which states that the html pages should not contain fields which indicate the state of the application (the encrypted flowId is ok anyway).

                  And yes I could use a temporary entry in the database to store the sessionID and the actual count but this is really something I would not like to do. The usual place for this is in the session (or webflow).

                  As you see this is an extremly controlled flow which must not allow proper back button use but it must be ensured that the flow ends and can not be terminated that easily by the accidentaly use of the back button.

                  I hope I describe my problem clear enough. Maybe I can't use SWF here but I think this problem isn't that uncommon. And I think this advanced handling of exceptions would be a great plus for SWF.

                  Regards,
                  Markus

                  Comment


                  • #10
                    I tried to find the right place to intercept if a state transition could not be found. it seems to me like FlowExecutionStack.signalEvent is the right place.
                    I tried to add a try/catch around the onevent call:

                    Code:
                     ViewDescriptor viewDescriptor;
                      try {
                       viewDescriptor = state.onEvent(event, context);
                      } catch (NoMatchingTransitionException e) {
                       if (logger.isDebugEnabled()) {
                        logger.debug("Fallback to previous state");
                       }
                       viewDescriptor = state.enter(context);
                      }
                    This gives me a nice
                    Code:
                    Required attribute 'org.springframework.validation.BindException.#formObject' is not present in flow scope; attributes present are = map['#formObject' -> org.springframework.samples.birthdate.BirthDate@1b00766, 'txToken' -> '60C74F93-8F0E-EFEC-B100-76A98F8AC768', 'birthDate' -> org.springframework.samples.birthdate.BirthDate@1b00766]
                    RequestURI=/birthdate/birthDate.do
                    I'm not sure what I should do with this. Obvious there is an object (which must be the most recent one) in the flowscope but not binded?
                    Can I do this in the signalEvent? Yup, when this is added the whole thing would for sure look like a hack. But I really need the "go back to last page" functionality in my app as I can not kick the user back to the beginning of the flow if he had used the browser buttons (and I can not use continuations as I must prevent the user from "free" browsing in the flow)

                    Thanks,
                    Markus

                    Comment


                    • #11
                      This errors saying the "bind exeception" (the errors instance) is not in flow scope -- the form object is there, but not the errors instance. You could try setting the Errors scope to "flow" I guess--by default it is request scope, and created during form action setupForm or bindAndValidate...

                      What state are you reentering? A ViewState? I would recommend overriding onEvent behaivor there and not in TransitionableState.

                      Where is this exception thrown? I imagine by the Struts FlowAction when it attempts to adapt the form object to the Struts action form... right now it requires a Errors instance exist in request scope or flow scope or it will fail. I guess we could make that optional.

                      Comment


                      • #12
                        What state are you reentering? A ViewState? I would recommend overriding onEvent behaivor there and not in TransitionableState.
                        Yes. I try to re-enter into the view state that is assumed by the flow.
                        This is to synchronize the view with the flow (and not vice versa the flow with the view).
                        It sounds like a good idea to overwrite the method on the viewstate. So it is ensured that this handling does not take place in a nonviewstate.

                        To the struts exception, the stack trace is:
                        Code:
                        java.lang.IllegalStateException: Required attribute 'org.springframework.validation.BindException.#formObject' is not present in flow scope; attributes present are = map['#formObject' -> org.springframework.samples.birthdate.BirthDate@3ba4f1, 'txToken' -> '01DD34FF-54E6-A4CC-32C6-33D810DC6DAD', 'birthDate' -> org.springframework.samples.birthdate.BirthDate@3ba4f1]
                                at org.springframework.web.flow.Scope.getRequiredAttribute(Scope.java:95)
                                at org.springframework.web.flow.Scope.getRequiredAttribute(Scope.java:110)
                                at org.springframework.web.flow.action.FormObjectAccessor.getFormErrors(FormObjectAccessor.java:159)
                                at org.springframework.web.flow.action.FormObjectAccessor.getFormErrors(FormObjectAccessor.java:146)
                                at org.springframework.web.flow.action.FormObjectAccessor.getFormErrors(FormObjectAccessor.java:134)
                                at org.springframework.web.flow.struts.FlowAction$1.requestProcessed(FlowAction.java:221)
                                at org.springframework.web.flow.InternalRequestContext$4.handle(InternalRequestContext.java:249)
                                at org.springframework.core.closure.support.Block.call(Block.java:36)
                                at org.springframework.core.closure.support.AbstractProcessTemplateWorkflow.run(AbstractProcessTemplateWorkflow.java:26)
                                at org.springframework.web.flow.InternalRequestContext.fireRequestProcessed(InternalRequestContext.java:247)
                                at org.springframework.web.flow.FlowExecutionStack.signalEvent(FlowExecutionStack.java:286)
                                at org.springframework.web.flow.execution.FlowExecutionManager.handle(FlowExecutionManager.java:244)
                                at org.springframework.web.flow.struts.FlowAction.doExecuteAction(FlowAction.java:143)
                                at org.springframework.web.struts.TemplateAction.execute(TemplateAction.java:177)
                                at org.apache.struts.action.RequestProcessor.processActionPerform(RequestProcessor.java:421)
                                at org.springframework.web.struts.BindingRequestProcessor.process(BindingRequestProcessor.java:112)
                                at org.apache.struts.action.ActionServlet.process(ActionServlet.java:1164)
                                at org.apache.struts.action.ActionServlet.doPost(ActionServlet.java:415)
                                at javax.servlet.http.HttpServlet.service(HttpServlet.java:616)
                                at javax.servlet.http.HttpServlet.service(HttpServlet.java:689)
                        Mhm, the optimum would be to recreate the form errors via validation so they are again visible in the view. Even better if one error could be added like: "please don't use the back button"
                        Any chance to get so far?

                        Thanks,
                        Markus

                        Comment


                        • #13
                          >hm, the optimum would be to recreate the form errors via validation so >they are again visible in the view. Even better if one error could be >added like: "please don't use the back button"

                          Well, right now, by default validation errors are generated on a per request basis, and since your ViewState has no idea what Action to call to perform the right validation, that won't work. A ViewState implementation that encapsulates pluggable form setup and bindAndValidate actions might be an option here.

                          In any case, I think we will want to reconsider how the FormObjectAccessor exports a form object and associated errors collection. Right now most people are storing the form object in flow scope, and the errors instance in request scope by default. However, if you choose also to store Errors in flow scope, you now have two copies of the form object in a serializable structure--less than ideal. In addition, BindException is not really designed to support multiple requests.

                          Basically, what I'm saying is we need a FormModel type object wrapping a single object for use in flows that consist of multiple pages, with proper adaption of a Errors interface for each page to support page-specific error reporting using standard spring tags and/or struts adapters. This is probably not a PR3 thing though--but with it, we could do neat stuff like calculate the enabled state of a wizard from the enabled state of all the pages--to drive automatic enablement/disablement of "finish", "next", or "back" buttons, for example.

                          Comment


                          • #14
                            I think you are right. Mhm. Sad. I had already finished the custom viewstate (except the additional error but even this seems manageable).
                            Just out of curiousity: what would be the best way to plug a custom viewstate in a (XML)flowdescription?

                            But all these are only desperate attempts to solve my original problem: A wizard which guaranties that a specific step is only executed max. three times and which does not kick the user out of the flow if he uses the browser buttons.

                            What's with a httpSessionContinuation strategy which stores selected attributes (like the max counter) not in the flow but in a seperate storage. E.g. database or better as an independent HTTP session entry.
                            May be this could done by a flowlistener. After a request the counter is copied from the flow scope into the durable location and at the beginning of the next request the counter is copied again into the flowscope by the filter. This would allow the user to navigate but also allows a certain amount of control. As I can't try this now, do you think this is doable?

                            Thanks,
                            Markus

                            Comment

                            Working...
                            X