Leonardo Uribe created MYFACES-3711:
---------------------------------------

             Summary: Add alwaysRecompile mode for EL Expression Cache Mode
                 Key: MYFACES-3711
                 URL: https://issues.apache.org/jira/browse/MYFACES-3711
             Project: MyFaces Core
          Issue Type: New Feature
          Components: JSR-314
            Reporter: Leonardo Uribe
            Assignee: Leonardo Uribe


In MYFACES-3160, EL Expression Cache Mode was introduced but soon it was seen a
problem found on MYFACES-3169 (ui:param and c:set implementations does not 
work as expected).

There are two problems that limit the scope where EL Expression Cache can 
be used:

1. Facelets user tags cannot cache EL Expressions.
2. Inclusions using ui:param must always contains the same number of 
parameters.

To understand the reasons it is worth to remember this example:

a.xhtml
<ui:composition template="c.xhtml">
    <ui:param name="var1" value="value1"/>
</ui:composition>

b.xhtml
<ui:composition template="c.xhtml">
    <ui:param name="var1" value="value1"/>
    <ui:param name="var2" value="value2"/>
</ui:composition>

c.xhtml
<ui:composition>
   <h:outputText value="#{var1}/>
   <h:outputText value="#{var2}/>
</ui:composition>

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
first time.

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.


--
This message is automatically generated by JIRA.
If you think it was sent incorrectly, please contact your JIRA administrators
For more information on JIRA, see: http://www.atlassian.com/software/jira

Reply via email to