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

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

From: arjan tijms <arjan.tijms_at_gmail.com>
Date: Mon, 17 Aug 2015 23:41:48 +0200

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