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

[jsr372-experts] Re: [jsr372-experts mirror] Re: _at_FlowScoped not entirely consistent?

From: Leonardo Uribe <leonardo.uribe_at_irian.at>
Date: Mon, 31 Aug 2015 23:21:11 -0500

Hi

I would like to comment some "implementation details" done inside MyFaces
Faces Flow that could help in the discussion:

- Flow Bean instantiation is lazy.

- If the Flow Bean is annotated with @FlowScoped("flow1") the bean is
associated to flow1 so only if flow1 or a parent flow containing flow1
ends, the bean is destroyed.

- MyFaces uses a complex structure to decide when a flow is part of another
flow or not, to decide when to destroy one or many nested flows. This is
done automatically.

- That means there is an structure that isolate the flow beans per client
window id and flow id.

- CDI is the one that set the rules for resolve beans, so there is no way
to make distinction between 2 beans with same name in different flows (no
way to define the valid context in this case).

- In this case:

  @FlowScoped("flowA")
  public class FlowABean

  @FlowScoped("flowB")
  public class FlowBBean

If flowB is called from flowA, and the user is in flowB, in MyFaces the
user can see both beans.

- In MyFaces you can provide a custom Flow implementation implementing this
interface:

public abstract class FacesFlowProvider
{
    public abstract Iterator<Flow> getAnnotatedFlows(FacesContext
facesContext);

    public abstract void doAfterEnterFlow(FacesContext context, Flow flow);

    public abstract void doBeforeExitFlow(FacesContext context, Flow flow);

    public abstract Map<Object, Object> getCurrentFlowScope(FacesContext
context);

    public void refreshClientWindow(FacesContext context)
    {
    }
}

This interface provide the necessary hooks to decouple @FlowScoped from JSF.

regards,

Leonardo Uribe

2015-08-17 16:41 GMT-05:00 arjan tijms <arjan.tijms_at_gmail.com>:

> Hi,
>
> On Mon, Aug 17, 2015 at 7:43 PM, Edward Burns <edward.burns_at_oracle.com>
> wrote:
> >>>>>> On Thu, 13 Aug 2015 09:27:30 +0200, arjan tijms <
> arjan.tijms_at_gmail.com> said:
> >
> > AT> If they only get created upon access, then isn't it the same as with
> > AT> every scope that doesn't require an ID?
> >
> > It's the same as any scoped CDI bean. For example, @SessionScoped beans.
>
> True, but for @SessionScoped beans there's no spec text that states
> that a @SessionScoped bean has to be created as soon as a session is
> created. Similarly, for @RequestScoped beans there's no spec text that
> says a bean has to be created as soon as a request is created.
>
> But for flows, the documentation on FlowHandler says:
>
> "Managed beans annotated with the CDI annotation FlowScoped must be
> instantiated upon a user agent's entry into the named scope, and must
> be made available for garbage collection when the user agent leaves
> the flow."
>
> Specifically the part "must be instantiated upon a user agent's entry
> into the named scope"
>
> This by itself is a nice feature. We have implemented something
> similar in OmniFaces for @RequestScoped and @ViewScoped (see
> http://showcase.omnifaces.org/cdi/Eager)
>
> But if my understanding of that text is correct (please correct me if
> I misinterpreted it), but then
>
> 1. This does not happen in either Mojarra or MyFaces
> 2. This is a feature that's useful for multiple types of scopes. It's
> somewhat like the eager attribute on the JSF (not CDI)
> @ApplicationScoped.
>
>
> > For example, let's say the same EL reference is located on a page
> > outside of any flow:
> >
> > somePage.xhtml
> >
> > #{foo.name}
> >
> > This will not resolve.
>
> True, but is the ID used for that?
>
> In this case is the simple fact that the scope is not active already
> the reason it will not resolve?
>
>
>
> > Now, let's say we have the same EL in a page in a different flow "foo".
> >
> > flow foo
> >
> > somePage.xhtml
> >
> > #{foo.name}
> >
> > This will still not resolve because the page is in the foo flow.
>
> Well, actually if you just look at the implementation code in Mojarra
> I think it *can* resolve. But then Mojarra will throw an exception
> after checking if the IDs match. So the ID is not *technically* needed
> to resolve it. The relevant implementation in
> com.sun.faces.flow.FlowCDIContext.get now (slightly condensed) does
> the following:
>
> Map<String, Object> flowScopedBeanMap =
> mapHelper.getFlowScopedBeanMapForCurrentFlow();
>
> String passivationCapableId = ((PassivationCapable)contextual).getId();
> result = (T) flowScopedBeanMap.get(passivationCapableId);
>
> if (null == result) {
>
> FlowBeanInfo fbi = flowIds.get(contextual);
> if (!flowHandler.isActive(facesContext, fbi.definingDocumentId, fbi.id))
> {
> throw new ContextNotActiveException("Request to activate bean
> in flow '" + fbi + "', but that flow is not active.");
> }
>
> result = contextual.create(creational);
> }
>
> So what happens here is that the code looks in the bean map for the
> current flow, and if it's not there it checks if the flow ID is equal
> to the ID of the current flow. If it's not, it throws the exception.
>
> But the ID is not really needed to resolve or disambiguate. If you
> remove the check then "contextual.create(creational);" will just
> create the bean and everything works.
>
> So maybe this is just a matter of miscommunication, but what I meant
> is that technically the flow ID is not needed to resolve the bean. It
> already naturally resolves to the bean instance corresponding to the
> passivation ID (roughly, the fully qualified class name of the bean,
> e.g. in Weld it would be something like
> "WELD%ManagedBean%cdi|cdi|com.example.FooBean|null|false").
>
> Now explicitly restricting a bean to be used only with some (named)
> instance of a scope is a nice feature by itself, but one that could be
> useful for other scopes as well. In fact, for OmniFaces we were
> considering an annotation that could work for any scope, e.g.
>
> @RequestScoped
> @Restricted("/view.xhtml")
> public class Bean {}
>
> // Bean can only be used from /view.xhtml
>
>
> > AT> With that the bean "foo" is scoped to whatever the current active
> flow
> > AT> is. Technically this is already what happens.
> >
> > But if you do that, then how can you differentiate multiple beans to be
> > in different flows.
>
> I don't really understand this part. Do you mean to differentiate between:
>
> @FlowScoped("A")
> @Named("foo")
> public class Foo {}
>
> vs
>
> @FlowScoped("B")
> @Named("foo")
> public class Bar {}
>
>
> OR
>
> package a;
>
> @FlowScoped("A")
> public class Foo {}
>
>
> vs
>
> package b;
>
> @FlowScoped("B")
> public class Foo {}
>
> ?
>
> The FlowCDIContext or an other extension does nothing to disambiguate
> that case, but to be sure I tested it and both cases cause an
> DeploymentException (Bean name is ambiguous).
>
> But maybe something else was meant here?
>
>
> > AT> Which leads to the question, why is the check there in
> > AT> the first place?
> >
> > Because the spec says that flow scoped beans must be declared to be in a
> > specific flow.
>
> I understand that ;) The definition of the annotation and the
> corresponding JavaDoc (therefor spec) require it, but what I meant was
> more why this requirement was put in the spec in the first place.
> Sorry if this question was not clearly worded above.
>
>
> >
> > AT> #{flowScope} is a Map<Object, Object> that's effectively flow scoped
> > AT> too, but this one does not define a flow id. It simply resolves to
> the
> > AT> "current" flow.
> >
> > It seems like you are requesting a new feature: give me a way to have
> > @FlowScoped beans that are active for any flow. Is that what you're
> > looking for?
>
> Well, at first I was more seeking for an explanation, but I think it
> would indeed be a good next step to ask for that feature. Just like an
> @RequestScoped bean is active for any request, so could an @FlowScoped
> (without an id specified) bean be active for any flow.
>
> > Right. Don't conflate the "#{flowScope} implicit object" concept with
> the
> > "FlowScoped beans in a flow concept".
>
> It's not necessarily that. The issue here is that to be able to inject
> the flow scoped map, a proxy has to be created, and that proxy needs
> to delegate to the current flow. The way for CDI to be able to do that
> is via a scope, and the current @FlowScoped implementation does
> exactly that.
>
> Implementation wise this is fairly trivial. A CDI Bean<T> (dynamic
> producer) using @FlowScoped isn't detected by
> com.sun.faces.flow.FlowCDIExtension.processBean. So when the scope
> (FlowCDIContext) is being consulted, but there's no corresponding
> FlowBeanInfo available, we know it can only came from a Bean<T>.
>
> Thanks for the explanations above, I think it's getting more clear now ;)
>
> Kind regards,
> Arjan Tijms
>