users@javaserverfaces-spec-public.java.net

[jsr344-experts mirror] [jsr344-experts] Re: Attention Pivotal/SpringSource folks: Faces Flows and SWF

From: Leonardo Uribe <lu4242_at_gmail.com>
Date: Thu, 16 Jan 2014 22:40:32 -0500

Hi

Just FYI, recently an user made a comment about a use case he has for
Faces Flow in MyFaces:

Subject: JSF 2.2 Exit Flow

"... I have an application that consists of a dropdown menu bar with
many menu items. Each menu item will initiate a new faces flow. This
is working correctly, however, once I am inside a flow, I would like
any click on a menu item to exit the current flow, and then start a
new one. There are dozens of menu items in the application, so I was
wondering if there is any way to have the current flow automatically
exit when a menu item is clicked without having to define a
flow-return for each menu item? ..."

What he is looking for is precisely what we are discussing here.

1. Global navigation should still work even if the user enters into a flow.
2. A navigation to a page outside a flow in some cases should trigger
an exit from the flow automatically, but in others that's not
necessary true.

As I said before, there is a difference between what the API was
designed for and what the API should be capable of. According to the
original intention, a case like the one described before is invalid,
but the users don't care about that. The only thing they want is
things works smoothly, or in other words have the ability to combine
everything that is available in JSF without restrictions of any kind.
I'm considering to provide an implementation specific workaround for
MyFaces, at least in the terms discussed here, because this is
something users will find and eventually this needs to be fixed at
some point in the spec.

regards,

Leonardo Uribe

2014/1/7 Leonardo Uribe <lu4242_at_gmail.com>:
> Hi
>
> LU> In few words, after reading the answer, I can see there is a
> LU> difference between what the API was designed for and what should the
> LU> API be capable of. Let's see if we can clarify the different points
> LU> of view.
>
> LU> The first problem we have and the reason why @FlowScoped does not
> LU> look like a full conversation scope (Hantsy's item 4) is there is no
> LU> clarification about the relationship between flows.
>
> LU> In few words, there are two cases:
>
> LU> - Two or more independent flows are running at the same time.
>
> LU> - Flow A call flow B, but when the flow A ends, since A was called
> LU> from B, B should end too.
>
> EB> But from the perspective that flows should be analogous to method
> EB> invocations this does not make sense.
>
> EB> public void foo() { bar(); }
>
> EB> public void bar() { println("bar"); }
>
> EB> It's not possible for foo() to return without bar() returning first.
>
> LU> The fact is this: global (explicit or implicit) navigation still
> LU> works while you are inside a flow, which means even if we model a
> LU> flow as something analogous to a method, the user can take different
> LU> routes that will lead to the mentioned situation. In other words,
> LU> enter into a flow does not disable other forms of navigation in an
> LU> application.
>
> EB> The spec has the following intents.
>
> EB> 1. The only way to get into a flow is through its start node. Any
> EB> attempt to enter a flow will cause its start node to be first traversed.
>
> EB> 2. Once you are in a flow, there are exactly three ways to leave it.
>
> EB> a. traverse one of the flow's return nodes.
>
> EB> b. traverse one of the flows flow-call nodes.
>
> EB> c. abandon the flow. This has the side effect of also abandoning all
> EB> flows higher up on the call stack.
>
> I think the problem is there is no definition about the boundaries of the
> flow. A flow defines entry and exit points, but there is no way to know if
> a view belongs to a flow or not, and in that way there is no way to know
> if the navigation to a view is going outside the flow, or to know if a
> navigation to a view inside a flow is valid or not.
>
> The intention of the spec is still valid. It is also clear that some flows
> should restrict the valid navigation cases once inside, but the point is
> there are other flows where that is not necessary true, and you really want
> the opposite. In other words, there are flow definitions that only want
> to define entry and exit points, so the context is activated and deactivated
> properly, but once inside do things like navigate to views not directly
> associated with the flow but still preserve the context.
>
> LU> I think it is up to the user to allow or deny jump outside a flow and then
> LU> go back. The fact that a flow can be modeled with defined entry and exit
> LU> points, and that some flows does not allow to jump outside, does not means
> LU> that all flows should be bound by that rule.
>
> EB> While the 2.2 spec does not prohibit this sort of thing, Rossen observed
> EB> the following:
>
> RS> One observation is with regards to access to flow scoped data. When
> RS> using the provided buttons, everything works as it should, i.e. flow
> RS> scoped data is created at the point of entering the flow and
> RS> destroyed when the flow is exited. However, it is easy to bypass the
> RS> entry and exit points. For example I can go directly to a page
> RS> associated with a flow without entering the flow, and if the page
> RS> tries to access flow data, the result would be
> RS> ContextNotActiveException.
>
> EB> I replied:
>
> EB> One could argue that such an exception is the right response. The
> EB> context is indeed not active. However, if you are not using any
> EB> flow scoped data, such an exception would not be thrown, and it
> EB> would give the impression that the navigation is supported.
>
> EB> I think we can add some language to the spec to detect these "jump
> EB> in" cases.
>
> EB> I continue to believe we should prohibit the jump in and jump out cases.
>
> In my opinion, this is something that should be defined as a config parameter
> of the flow. In other words, it should be a parameter or something that says:
> "... once the user enter into this flow, it is not valid to visit views
> outside the flow or all views belonging to this flow cannot be accessed
> directly from outside ...". Maybe something to define that the flow navigation
> is "strict" or "lax" (by default lax, because the current spec does not define
> how to identify if a view belongs to a flow or not). If a flow is strict it
> should be some way to define when a view belongs to a flow, and by default
> identify that all views starting with the flow name belongs to that view
> (/<flow-name>/*). Something like flow-view-mapping?.
>
> LU> In conclusion we have found two important points that needs to be handled
> LU> in a flow:
>
> LU> - It could be good to inhibit a part of the navigation after enter into
> LU> a flow.
>
> EB> Yes, I agree.
>
> LU> - Some flows should not support back button, or if back button is pressed
> LU> it should throw ViewExpiredException or something like that.
>
> EB> How about generalizing this? We could imagine some kind of
> EB> configuration syntax that allows us to say, throw an exception when page
> EB> X is reached via the back button.
>
> EB> [...]
>
> I think we can do it restricting the views that are valid after enter into
> a flow. But it could be great to have an "abort" finalizer method or something
> that allow us to recognize when the current flow is being terminated without
> use a return node, and the finalization is caused by a navigation outside the
> flow or by the finalization of a parent flow.
>
> LU> Take a look at the example named jsf22-flows. It is quite easy to modify
> LU> it and do some tests. Try enter into a flow, then to another without exit
> LU> from the first flow and if you use Mojarra you'll see how some links
> LU> are disabled.
>
> EB> I'm not sure if that's a bug or not, but I want to make it so when
> EB> you're in the inner flow, you can not access data from the calling
> EB> flow unless it is specifically passed in via parameters.
>
> LU> I think do that goes against simplicity. Suppose this: Flow A has a
> LU> bean called FlowABean. Flow B has a bean called FlowBBean. Flow A
> LU> call Flow B, so if the user wants to get some information from A,
> LU> he/she writes something like this:
>
> LU> @Named
> LU> FlowScoped("B")
> LU> public class FlowBBean
> LU> {
>
> LU> @Inject
> LU> private FlowABean flowABean;
>
> LU> /* .... */
> LU> }
>
> LU> Shoud that be valid? Yes, why not? If Flow A has an explicit call
> LU> node that calls Flow B, it means Flow B is a subflow of Flow A, so
> LU> the context from Flow A should be accesible from all beans in Flow
> LU> B. Is it useful? Yes, of course. Too useful to ignore it. There are
> LU> situations where pass parameters can be important or necessary, but
> LU> there are others where flow reusal is not that important and a
> LU> simple injection will do it just fine. The important thing here is
> LU> use Faces Flow to define boundaries and contexts around those
> LU> boundaries. Note I'm not using Faces Flow here to pack all views
> LU> related to a flow in a single location.
>
> LU> Other typical use case is if the user wants to create a link to enter into
> LU> a flow, but the flow is already active. In theory, the flow must be
> LU> restarted, but sometimes you want to encode the link to just move
> LU> to the start page.
>
> EB> If you want to enter a flow that is already active, from where are you
> EB> entering the flow? If you are in another flow, then you must return
> EB> from that flow to get back to the previous flow. If you are not in a
> EB> flow, then the previous flow can't be active. I don't understand this one.
>
> LU> The idea is:
>
> LU> 1. Enter into Flow A
> LU> 2. Navigate to another page using normal/implicit navigation.
> LU> 3. Go back to the point the user enter into Flow A
>
> EB> I assume by "the point the user enters into Flow A" you mean, "the page
> EB> that has the button that, when clicked, causes entry to Flow A". Let's
> EB> call that page, the "doormat page" because you have to stand on the door
> EB> mat in front of the door if you want to enter it. If I'm understanding
> EB> you correctly, the current spec intent is to treat the act of navigating
> EB> from inside Flow A to the doormat page as abandoning the flow. If
> EB> navigating from within a flow to the doormat page does not cause
> EB> abandoning the flow, it is a bug.
>
> The problem is according to the spec the viewId is not checked according to
> the context, and in that sense a navigation from a page in the flow to the
> doormat page cannot be detected. How can you detect that if there is no way
> to recognize if a view belongs to a flow or not?. To fix this, it should be
> added a method in FlowHandler to deal with that logic and the navigation
> algorithm and section 2.2.1 (restore view) should be updated to take it
> into account.
>
> What I did in this part for MyFaces was to detect on navigation handling
> if the flow is active and if that so, trigger an exit from the flow and
> then re-enter to the same flow, so the user will not notice anything, only
> that things works as it should instead of a dirty exception.
>
> LU> 4. Click again on the button or link.
>
> LU> It can be done with the current spec, and if it can be done it
> LU> should work.
>
> EB> If I'm understanding you correctly, this is a bug.
>
> Yes, it is a bug. If the flow is in "strict" mode, it should never
> happen because the exit condition is activated before display the doormat
> page. If the flow is in "lax" mode, it should reenter into the flow. But
> in some cases you don't want to destroy the context, so it should be
> possible to enter into the starting page of the flow without destroy the
> context. With the current spec I have to add a method like this:
>
> public String goToCustomerFlow()
> {
> FacesContext facesContext = FacesContext.getCurrentInstance();
> if (facesContext.getApplication().getFlowHandler().isActive(
> facesContext, "", "customer"))
> {
> // go to customer flow start page without create it.
> return "/customer/customer";
> }
> else
> {
> //Invoke through call node
> return "callCustomer";
> }
> }
>
> It can be done with EL, but a shorter syntax is welcomed.
>
> regards,
>
> Leonardo
>
>
> 2014/1/7 Edward Burns <edward.burns_at_oracle.com>:
>>>>>>> On Mon, 6 Jan 2014 16:06:45 -0500, Leonardo Uribe <lu4242_at_gmail.com> said:
>>
>> LU> In few words, after reading the answer, I can see there is a
>> LU> difference between what the API was designed for and what should the
>> LU> API be capable of. Let's see if we can clarify the different points
>> LU> of view.
>>
>> LU> The first problem we have and the reason why @FlowScoped does not
>> LU> look like a full conversation scope (Hantsy's item 4) is there is no
>> LU> clarification about the relationship between flows.
>>
>> LU> In few words, there are two cases:
>>
>> LU> - Two or more independent flows are running at the same time.
>>
>> LU> - Flow A call flow B, but when the flow A ends, since A was called
>> LU> from B, B should end too.
>>
>> EB> But from the perspective that flows should be analogous to method
>> EB> invocations this does not make sense.
>>
>> EB> public void foo() { bar(); }
>>
>> EB> public void bar() { println("bar"); }
>>
>> EB> It's not possible for foo() to return without bar() returning first.
>>
>> LU> The fact is this: global (explicit or implicit) navigation still
>> LU> works while you are inside a flow, which means even if we model a
>> LU> flow as something analogous to a method, the user can take different
>> LU> routes that will lead to the mentioned situation. In other words,
>> LU> enter into a flow does not disable other forms of navigation in an
>> LU> application.
>>
>> The spec has the following intents.
>>
>> 1. The only way to get into a flow is through its start node. Any
>> attempt to enter a flow will cause its start node to be first traversed.
>>
>> 2. Once you are in a flow, there are exactly three ways to leave it.
>>
>> a. traverse one of the flow's return nodes.
>>
>> b. traverse one of the flows flow-call nodes.
>>
>> c. abandon the flow. This has the side effect of also abandoning all
>> flows higher up on the call stack.
>>
>> LU> I think it is up to the user to allow or deny jump outside a flow and then
>> LU> go back. The fact that a flow can be modeled with defined entry and exit
>> LU> points, and that some flows does not allow to jump outside, does not means
>> LU> that all flows should be bound by that rule.
>>
>> While the 2.2 spec does not prohibit this sort of thing, Rossen observed
>> the following:
>>
>> RS> One observation is with regards to access to flow scoped data. When
>> RS> using the provided buttons, everything works as it should, i.e. flow
>> RS> scoped data is created at the point of entering the flow and
>> RS> destroyed when the flow is exited. However, it is easy to bypass the
>> RS> entry and exit points. For example I can go directly to a page
>> RS> associated with a flow without entering the flow, and if the page
>> RS> tries to access flow data, the result would be
>> RS> ContextNotActiveException.
>>
>> I replied:
>>
>> EB> One could argue that such an exception is the right response. The
>> EB> context is indeed not active. However, if you are not using any
>> EB> flow scoped data, such an exception would not be thrown, and it
>> EB> would give the impression that the navigation is supported.
>>
>> EB> I think we can add some language to the spec to detect these "jump
>> EB> in" cases.
>>
>> I continue to believe we should prohibit the jump in and jump out cases.
>>
>> LU> In conclusion we have found two important points that needs to be handled
>> LU> in a flow:
>>
>> LU> - It could be good to inhibit a part of the navigation after enter into
>> LU> a flow.
>>
>> Yes, I agree.
>>
>> LU> - Some flows should not support back button, or if back button is pressed
>> LU> it should throw ViewExpiredException or something like that.
>>
>> How about generalizing this? We could imagine some kind of
>> configuration syntax that allows us to say, throw an exception when page
>> X is reached via the back button.
>>
>> [...]
>>
>> LU> Take a look at the example named jsf22-flows. It is quite easy to modify
>> LU> it and do some tests. Try enter into a flow, then to another without exit
>> LU> from the first flow and if you use Mojarra you'll see how some links
>> LU> are disabled.
>>
>> EB> I'm not sure if that's a bug or not, but I want to make it so when
>> EB> you're in the inner flow, you can not access data from the calling
>> EB> flow unless it is specifically passed in via parameters.
>>
>> LU> I think do that goes against simplicity. Suppose this: Flow A has a
>> LU> bean called FlowABean. Flow B has a bean called FlowBBean. Flow A
>> LU> call Flow B, so if the user wants to get some information from A,
>> LU> he/she writes something like this:
>>
>> LU> @Named
>> LU> FlowScoped("B")
>> LU> public class FlowBBean
>> LU> {
>>
>> LU> @Inject
>> LU> private FlowABean flowABean;
>>
>> LU> /* .... */
>> LU> }
>>
>> LU> Shoud that be valid? Yes, why not? If Flow A has an explicit call
>> LU> node that calls Flow B, it means Flow B is a subflow of Flow A, so
>> LU> the context from Flow A should be accesible from all beans in Flow
>> LU> B. Is it useful? Yes, of course. Too useful to ignore it. There are
>> LU> situations where pass parameters can be important or necessary, but
>> LU> there are others where flow reusal is not that important and a
>> LU> simple injection will do it just fine. The important thing here is
>> LU> use Faces Flow to define boundaries and contexts around those
>> LU> boundaries. Note I'm not using Faces Flow here to pack all views
>> LU> related to a flow in a single location.
>>
>> LU> Other typical use case is if the user wants to create a link to enter into
>> LU> a flow, but the flow is already active. In theory, the flow must be
>> LU> restarted, but sometimes you want to encode the link to just move
>> LU> to the start page.
>>
>> EB> If you want to enter a flow that is already active, from where are you
>> EB> entering the flow? If you are in another flow, then you must return
>> EB> from that flow to get back to the previous flow. If you are not in a
>> EB> flow, then the previous flow can't be active. I don't understand this one.
>>
>> LU> The idea is:
>>
>> LU> 1. Enter into Flow A
>> LU> 2. Navigate to another page using normal/implicit navigation.
>> LU> 3. Go back to the point the user enter into Flow A
>>
>> I assume by "the point the user enters into Flow A" you mean, "the page
>> that has the button that, when clicked, causes entry to Flow A". Let's
>> call that page, the "doormat page" because you have to stand on the door
>> mat in front of the door if you want to enter it. If I'm understanding
>> you correctly, the current spec intent is to treat the act of navigating
>> from inside Flow A to the doormat page as abandoning the flow. If
>> navigating from within a flow to the doormat page does not cause
>> abandoning the flow, it is a bug.
>>
>> LU> 4. Click again on the button or link.
>>
>> LU> It can be done with the current spec, and if it can be done it
>> LU> should work.
>>
>> If I'm understanding you correctly, this is a bug.
>>
>> Ed