Thanks for your detailed response Simon. Thanks also to Paul for his
suggestion.
After reading your email, I looked up MYFACES-437. (See [1])
It appears Adam Winer is right in that JSF 1.2 spec demands that f:param
accept only literals and deferred expressions. Of course we are not
using 1.2 right now, but I suppose some team members felt it better to
make the change now and follow the behaviour of the newer spec.
This is fine, but it does represent a rather large change of
functionality for the f:param tag, and this change should be prominently
documented somewhere. Unfortunately, I'm not quite sure where this
"somewhere" should be.
In any case, it appears that I will have to abandon my hack in favor of
a new one. (It seems this case will always require some hack, until JSF
1.2 is used) For now, I am trying the hidden outputText approach. It is
still not elegant, and there may yet be some side-effects, but at least
I have the assurance that the value-binding expression's evaluation
won't be deferred. So far it seems to be working.
There are also some inline responses to Simon's email below.
[1] http://issues.apache.org/jira/browse/MYFACES-437
Thanks again for your help in clarifying this issue guys!
Regards,
Jeff Bischoff
Kenneth L Kurz & Associates, Inc.
Simon Kitching wrote:
Jeff Bischoff wrote:
Greetings colleagues,
I have used in my web pages a pattern that apparently was not safe,
yet it worked fine with all versions of MyFaces until the current
trunk (1.1.5). Basically, when I needed to make sure a certain bean
was instantiated before using non-JSF EL, I preceded the statement
with an f:attribute like so:
<f:attribute name="Content" value="#{publicNavigation.contentPage}" />
<jsp:include page="${publicNavigation.contentPage}" />
Hmm...I guess the question is whether the UIComponent attribute map
contains *objects* or *value-bindings*.
Right, or to put it another way: whether the EL expression is evaluated
immediately or the evaluation is deferred. Seems JSF 1.1 leaves room for
interpretation on this one, but JSF 1.2 does not...
I see in the myfaces-1.1.3 code (class AttributesTag) that when the
f:attribute tag executes (see below), it always evaluates the provided
expression, and puts that value into the attributes map of the enclosing
UIComponent. That means the expression is only evaluated once (on first
page view). See:
http://svn.apache.org/repos/asf/myfaces/core/tags/1_1_3/api/src/main/java/javax/faces/webapp/AttributeTag.java
In the myfaces trunk, if the value is an expression, I see that a
ValueBinding is stored into the attributes map instead, meaning it will
be evaluated whenever the attribute is fetched from the parent component
(but NOT evaluated until then). See method doStartTag in:
http://svn.apache.org/repos/asf/myfaces/core/trunk/api/src/main/java/javax/faces/webapp/AttributeTag.java
So that's why it is no longer working for you.
Good catch! That definately explains why my hack has broken. Thanks for
your research.
> The commit was r448315:
Modified Wed Sep 20 19:46:32 2006 UTC (3 months, 2 weeks ago)
by mmarinschek
fix for [MYFACES-437] <f:attribute> does not work with <x:dataTable>.
Thanks to Michal Borowiecki.
Maybe mmarinschek didn't realise that moving from objects to
value-bindings in the attribute map would have significant other
effects? Or maybe this new behaviour is in fact in better compliance
with the spec or the sun RI?
Good 'ol Martin. :)
Well it is better compliance with the 1.2 spec, anyway. I haven't tried
this with the RI, maybe that is how they implemented it though? If
that's the case, then the change makes sense. Usually Martin is pretty
aware of side-effects and deals with them if he can... I just wish there
was a good place to announce changes like this that might break existing
code.
Regardless, it seems to me that there is another flaw in this
f:attribute hack, even with the 1.1.3 behaviour.
On first view of this page (ie where there is no component tree to
restore), so that the result of the expression can be added to the
parent component's attribute map. And a side-effect of this is that the
name "publicNavigation" will be registered in the appropriate scope,
which of course is what the code above is really wanting to achieve.
Right, I'm only interested in the side-effect. Painfully inelegant, but
it got the job done.
However on a later view of this page (where the component tree has been
restored), AttributeTag checks whether an attribute with that name
exists, and if so does nothing which seems reasonable. This implies that
for your usage above, if publicNavigation managed bean is of request
scope, then the name only gets registered *on first view*, and won't be
available to jsp expressions later (eg if an action on the page causes a
submit that re-renders the same page). Note that it *is* "instantiated",
but there is no name registered in any scope that references the object.
This behaviour of AttributeTag (only execute on first view) is
consistent with other JSF tags; normally, a JSF tag does nothing if the
corresponding component already exists. An f:attribute tag doesn't
create a UIComponent, but it seems right for it to behave in a
consistent manner.
Ahh I should have mentioned that I only ever use this hack to create
Session-scoped managed beans. These are constructs for navigation that
track where a user currently is in the system. Thus, the behaviour you
describe above was exactly what I wanted and worked quite well for the
better part of a year. :)
Another disadvantage is that the specified object will be in the
attribute-map of the component, so will be "persisted" if the parent
component is persisted (eg when using client-side state saving!). This
isn't terribly elegant, though bearable as long as the value expression
returs something small. In the above case, it returns the relative path
to a jsp file, so that's probably ok. Something like this should also be
acceptable in most cases:
<f:attribute name="Content" value="#{publicNavigation.class.name}" />
Indeed this is rather painfully inelegant. However, I don't see any
elegant way to do this in JSF 1.1. Whatever solution I choose, it will
be a hack of some kind. Fortunately, I only need to do this in a few
places in my code, and like you noted the Strings I am storing are not
large.
By the time the jsp:include tag was evaluated, the publicNavigation
bean was instantiated.
You don't just want it "instantiated", but also registered in the
appropriate scope.
However, when I try with MyFaces Core 1.1.5 (built off
the trunk repeatedly over the last few weeks) this pattern fails
catastrophically. The f:attribute tag does not cause the bean to
instantiate at all!
So why did f:attribute change to not instantiate beans anymore? What
would be a better choice of tag for this pattern? What is the safest
way to do this?
I can't think of a better solution for the moment. Maybe the outputText
hack (with the style set to display:none to hide it) is a reasonable
solution...
Going to go with this for now, thanks for your help!
Cheers,
Simon