There are two problems that limit the scope where EL Expression Cache can
1. Facelets user tags cannot cache EL Expressions.
2. Inclusions using ui:param must always contains the same number of
To understand the reasons it is worth to remember this example:
<ui:param name="var1" value="value1"/>
<ui:param name="var1" value="value1"/>
<ui:param name="var2" value="value2"/>
When facelet c.xhtml is constructed from a.xhtml, "var2" is not recognized as
a parameter so all EL expressions inside c.xhtml holding refereces to "var2"
will be cached. Later, facelet c.xhtml is reused from b.xhtml but since
some EL expressions are cached the passed value in "var2" is not taken into
account and the error arise.
In this point it is good to remember that ui:include or ui:decorate or user
tags are build view time tags, so they are executed only when the view is
built. Parameters or attributes passed by ui:param or as user tag attributes
follows the same principle, they are calculated on build view time through
VariableMapper and the evaluation is stored inside the EL Expression. This
means all EL Expressions holding references to these variables cannot be
cached and needs to be generated each time the view is built.
There is no way to know beforehand which references are affected, because
in a template or an user tag there is no declaration of the parameters or
attributes. But from user point of view that's good, because in this context
a declaration of the parameters is just not necessary.
The problem is ui:param and user tags are very useful features, widely used.
A solution to this problem will improve performance in those cases.
I have been thinking for a long time how to solve this, trying different
strategies. Use some kind of concurrency algorithm inside TagAttributeImpl
does not work because it is too expensive, or use a central storage for
cache the expressions by the cost involved in the comparisons.
The objective of cache EL expressions inside facelets abstract syntax tree
(AST) is minimize the calculations required to get a valid expression. EL
implementations has already an internal map that cache that information,
but that code usually has synchronized blocks or similar things. In that
sense, the idea is rely on that storage in those EL expressions where
there is no choice and they need to be recreated.
After doing many experiments in this part, I came up with a solution, which
involves the following points:
1. Associate to a facelet, the parameters that were considered as passed
through ui:param or as a user tag attribute. If in some point of time
we know for example c.xhtml uses var1, just consider it as c.xhtml(var1).
2. Use DefaultVariableMapper to track the parameters that are passed through
ui:param or as a user tag attribute. When the EL expression is created, if
it uses at least one parameter, mark the expression as not cacheable.
3. Override FaceletCache implementation and force a recompilation of a
facelet if a new parameter is detected that was not considered the first
time the template was created.
4. A facelet stored in the cache can be used if and only if all the
parameters used for the template where considered when it was compiled at
In the example proposed, when facelet c.xhtml is constructed from a.xhtml,
we say that c.xhtml was built with var1 as a known parameter, or
c.xhtml(var1). when we try to reuse facelet c.xhtml from b.xhtml, we discover
that var2 is also a parameter, but since the cached facelet is c.xhtml(var1),
the algorithm discard the facelet and create a new one, but taking into
account var2 too, so the new facelet becomes c.xhtml(var1,var2). If there
is a call to c.xhtml with no params, it is considered that c.xhtml(var1,var2)
can be used in that case.
The final effect is just some extra compilations of the same facelet at
startup but in the medium/long term, the information we need is calculated
and associated with the facelet url. Nice!. Facelet is very fast doing those
extra compilation steps, and the final effect over performance really pays
off. We could even set this mode as default.
The only disadvantage of this strategy is the current contract of FaceletCache
is insuficient. As it has been described in
MYFACES-3705, there are
implementation details inside MyFaces Core and in our facelets implementation,
that needs to be exposed in a proper way. We need to create a custom
AbstractFaceletCache and specify how to implement it.