jsr344-experts@javaserverfaces-spec-public.java.net

[jsr344-experts] Re: [730-Flows] Return node handling

From: Leonardo Uribe <lu4242_at_gmail.com>
Date: Tue, 12 Mar 2013 22:50:55 -0500

Hi

2013/3/12 Edward Burns <edward.burns_at_oracle.com>:
>>>>>> On Mon, 04 Mar 2013 14:22:46 -0700, David Schneider <david.schneider_at_oracle.com> said:
>
> DS> On 02/28/2013 07:41 AM, Leonardo Uribe wrote:
>>>> In section 7.4.2.1, Requirements for Explicit Navigation in Faces Flow
> EB> Call Nodes other than ViewNodes, here is the new text regarding return
> EB> nodes.
>
> EB> If the node is a ReturnNode obtain its navigation case and call
> EB> FlowHandler.setReturnMode(true). This enables the navigation to
> EB> proceed with respect to the calling flow's navigation rules, or the
> EB> application's navigation rules if there is no calling flow. Start the
> EB> navigation algorithm over using it as the basis but pass the value of
> EB> the symbolic constant javax.faces.flow.FlowHandler.NULL_FLOW as the
> EB> value of the toFlowDocumentId argument. If this does not yield a
> EB> navigation case, call FlowHandler.getLastDisplayedViewId(), which will
> EB> return the last displayed view id of the calling flow, or null if
> EB> there is no such flow. In a finally block, when the re-invocation of
> EB> the navigation algorithms completes, call
> EB> FlowHandler.setReturnMode(false).
>>>> >
>
> LU> I don't get these lines. In theory flowDocumentId to use should be the
> LU> parent flow flowDocumentId. So, if the node is a ReturnNode, it should
> LU> take the outcome, and try to resolve it under the parent flow and if
> LU> it resolves to something else the parent flowDocumentId should be
> LU> used. If it founds another ReturnNode it should do the same until
> LU> the top. The rules that are on top (not bound to any flow and set
> LU> globally) only should be consulted if no parent flow is found, respecting
> LU> encapsulation principle.
>
> DS> I believe I agree. When exiting a flow the flow being exited should be
> DS> "popped" from the flow call stack and navigation should proceed in the
> DS> context of the calling flow, with navigation originating from that
> DS> flow's call node, and using that flow's navigation rules.
>
> Yes, this is what I have.
>

Unfortunately, the solution proposed is limited to work in just 1 or 2 nesting
levels, because structurally it can't deal with the problem. Why? because
the outer flow has its own navigation rules and the algorithm should detect
that and apply the algorithm using those rules and so on to the top.

In theory there are two methods:

handleNavigation()
getNavigationCase()

remember that getNavigationCase() "calculates" the navigation but does not
perform it. The navigation algorithm has been always a two-step algorithm:

1. calculate the navigation case.
2. peform the navigation.

I understand that if only handleNavigation() exists, the solution can work,
because the "context" can be affected while the calculation steps are done,
but unfortunately this is not the case.

Who calls getNavigationCase()? OutcomeTarget renderers (h:button and h:link).

> DS> The only time navigation should use the null flow is when the whole
> DS> call stack has been popped.
>
> The NULL_FLOW value for the toFlowDocumentId argument is just a flag
> that means we are in the midst of a flow return and the popped flow is
> the one that should be used as the context of the navigation algorithm.
>
> DS> The existence of the return mode attribute in the interface contract
> DS> seems to suggest we don't have the separation of responsibilities of the
> DS> classes well thought out. It looks like we have a somewhat obtuse way
> DS> of performing a push and pop of a window's flow stack using
> DS> FlowHandler.transition(), FlowHandler.getCurrentFlow(), and
> DS> FlowHandler.setReturnMode(). Maybe it would be more clear if the
> DS> operations on the FlowHandler interface were described as stack
> DS> operations such as push, pop, and peek.
>
> As I said before, I don't want to expose the stack data structure to the
> API to allow for greater flexibility in implementation choice.
>
>>>>>> On Sun, 10 Mar 2013 18:10:55 -0500, Leonardo Uribe <lu4242_at_gmail.com> said:
>
> LU> Thanks David for the clarification. So in this case it is better that the
> LU> "global rules" only apply if the stack is clear. I agree. What's important
> LU> here is ensure that the developer can nest 2, 3 or many more flows and
> LU> in all cases it will work correctly. The API no matter how is done should
> LU> reflect that.
>
> Yes, of course the ability to do this nesting is essential, and we have
> a working testcase that shows this ability.
>

Try this:

    <faces-flow-definition id="flow3">
        <flow-return id="exit3">
            <navigation-case>
                <from-outcome>exit2</from-outcome>
            </navigation-case>
        </flow-return>
    </faces-flow-definition>
    <faces-flow-definition id="flow2">
        <flow-return id="exit2">
            <navigation-case>
                <from-outcome>exit1</from-outcome>
            </navigation-case>
        </flow-return>
    </faces-flow-definition>
    <faces-flow-definition id="flow1">
        <navigation-rule>
            <from-view-id>*</from-view-id>
            <navigation-case>
                <from-outcome>exit1</from-outcome>
                <to-view-id>/flow1/return2.xhtml</to-view-id>
            </navigation-case>
        </navigation-rule>
    </faces-flow-definition>

The stack is

flow3
flow2
flow1

The problem is the navigation rule is under flow1, but the current flow is
flow3, since there is no way to check the stack from NavigationHandler,
the navigation rule cannot be discovered.

> LU> I totally agree with you in this case. Currently we have this:
>
> LU> public abstract Flow getCurrentFlow(FacesContext context)
> LU> public abstract void setReturnMode(FacesContext context,
> LU> boolean returnMode)
> LU> public abstract void transition(FacesContext context,
> LU> Flow sourceFlow,
> LU> Flow targetFlow,
> LU> FlowCallNode outboundCallNode,
> LU> String toViewId)
>
> LU> transition() method is ok, because you can't nest the same flow twice.
> LU> But trying to implement transition() method, I have found that internally
> LU> this method requires a stack to do the job.
>
> Yes, of course one needs a stack inside to do the job.
>
> LU> We need an API that can do
> LU> two things:
>
> LU> 1. Handle transitions or when to go in/out of a flow, and apply operations
> LU> like clear/init flow scope and so on.
>
> LU> 2. Calculate the navigation case taking into account the current context,
> LU> traversing the flow structure but without execute any of the steps to be
> LU> done for the real navigation.
>
> LU> The api we need is something that can do the calculation without affect
> LU> the current state, because that part will be done later in
>
> LU> NavigationHandler.handleNavigation() / FlowHandler.transition()
>
> Yes, and I assert that those operations can be done as an implementation
> detail. The important thing is to specify the behavior of the flow
> nodes with respect to navigation.
>

Theorically, the problem is we need to expose both the behavior of the
flow nodes
and the current context where the flow is being resolved.

regards,

Leonardo Uribe

> Ed