Apologies for not abstracting this problem in a dedicated test case, I hope the example from a real project is simple enough to describe the problem.
I have a JavaEE/JPA2/JSF web application where every @Entity Element (or subclass) has a templated view.xhtml page, and a standard link generator composite component util:view_link.xhtml, invoked as GET with database ID as parameter. A portion (only) of each view page represents an expert system summary; that portion can be abstracted as a composite component, for inclusion in a view page or elsewhere.
I have introduced a Primefaces p:dialog modal popup for displaying that expert system summary portion (and any additional diagnostics) when clicking on a small status icon displayed next to the view link. If you let the status icon be represented by x it looks like this:
x Link_to_Element_by_ID
Click on 'Link_to_Element_by_ID' and it brings up the full view page.
Click on the 'x' icon (expert system test failure indicator) and it pops up the p:dialog with the expert system summary (only).
So the expert system portion of the view page is shared as a composite component.
But this can lead to recursion and a Stackoverflow if either:
The popup p:dialog expert system summary shows the status icon indicator of the Element being inspected.
I include additional element view links along with with status indicators (that would themselves launch a p:dialog for an expert system summary).
I have tried using rendered tests using a recursion blocking attribute 'preventRecursionOnDialog' but it fails, apparently because the recursion is happening during the build phase.
Q: How can I block possible recursion using a test variable ?
Also, I have tried c:if tests instead of JSF 'rendered' tests, but it seems the variable tested are not available under @ViewScoped.
Example, for an Activity element, where util_primefaces:dialog_summary is merely a customised encapsulation of a p:dialog.
From util:status_activity.xhtml:
<composite:attribute
name="activity"
required="true"
type="com.example.entity.Activity"
/>
<composite:attribute
name="preventRecursionOnDialog"
required="false"
default="false"
type="java.lang.Boolean"
/>
</composite:interface>
<composite:implementation>
<util_primefaces:dialog_summary
header="Expert system summary report"
rendered="#{not cc.attrs.preventRecursionOnDialog}"
element="#{cc.attrs.activity}">
<!-- causes StackOverflowError -->
<util:warn_insufficient_subactivities
activityContainer="#{cc.attrs.activity}"
humanTypeDescription="composite activity"
preventRecursionOnDialog="true"
/>
<util:expertsystem_activity activity="#{cc.attrs.activity}"/>
</util_primefaces:dialog_summary>
..
<span
onclick="#{not cc.attrs.preventRecursionOnDialog ? ('dialog'.concat(cc.attrs.activity.id).concat('.show();')) : ''}"
style="float:left;"
class="icon-completed-#{cc.attrs.activity.acceptedEffective}-small"
title=".."
> </span>
The util:warn_insufficient_subactivities (which shows which subactivities of a composite activity have not passed an expert system test) can cause recursion:
<cc:interface>
<cc:attribute name="activityContainer" required="true" type="com.example.entity.IActivityContainer"/>
<cc:attribute name="humanTypeDescription" required="true" type="java.lang.String"/>
<cc:attribute
name="preventRecursionOnDialog"
required="false"
default="false"
type="java.lang.Boolean"
/>
</cc:interface>
<cc:implementation>
<h:panelGroup
rendered="#{not cc.attrs.activityContainer.sufficientSubActivitiesAccepted}">
<util:warn_box
message=".."
>
<!-- CAUTION: can cause Stackoverflow when list included in expertsystem p:dialog popup -->
<util:list_activity_compact
list="#{cc.attrs.activityContainer.activities}"
preventRecursionOnDialog="#{cc.attrs.preventRecursionOnDialog}"
rendered="#{not cc.attrs.preventRecursionOnDialog}"
/>
</util:warn_box>
And the util:list_activity_compact shows a list with status icon indicators (which in turn can offer a popup p:dialog with expert system summary, and can recurse) and util:view_link:
<cc:interface>
<cc:attribute
name="list" required="true" type="java.util.List"
/>
<cc:attribute
name="preventRecursionOnDialog"
required="false"
default="false"
type="java.lang.Boolean"
/>
</cc:interface>
<cc:implementation>
<h:panelGroup display="block">
<ul class="view-field-list-medium">
<ui:repeat var="a" value="#{cc.attrs.list}">
<li class="view-field-list">
<util:status_activity
activity="#{a}"
preventRecursionOnDialog="#{cc.attrs.preventRecursionOnDialog}"/>
<util:view_link element="#{a}"/>
</li>
</ui:repeat>
</ul>
</h:panelGroup>
</cc:implementation>
The point of the question is that the test rendered="#{not cc.attrs.preventRecursionOnDialog}" is not sufficient to block the recursion, even though the part that would recurse is not rendered (blocked by the rendered test), it seems recursion can still happen during the JSF build phase.
BTW I often encounter a similar problem when I only want to render a particular composite component bound to a type, within a subset of type choices; performing a type test in 'rendered' is not enough to prevent a type error. Imagine below that 'value' could be one of many Element subclasses including Activity, but one only wants to display the following composite component portion for an Activity:
<util:component_for_Activity_only
activity="#{cc.attrs.value}"
rendered="#{cc.attrs.value['class'].simpleName=='Activity'}"
/>
(cf. instanceof check in EL expression language, and note that that Class String based type test solution is not very flexible, it does not work for subclasses or for interface tests.)
Again, the attempt to block calling with 'rendered' is not enough, it seems the type test fails already during the build phase. A solution to the recursion problem would also offer a solution to this. Even the introduction (finally) of instanceof in JSF2 (vote here please http://java.net/jira/browse/JSP_SPEC_PUBLIC-113) would not help here if only used in 'rendered',
Answering own questions after further research and trials.
Firstly, thanks to meriton, your response did not exactly answer my question but put me on the right path.
The short answer is that you can use c:if tests to control recursion during the build phase for @ViewScoped since Mojarra 2.1.18. In my case this now works:
As so often, I was led to this answer (the need to upgrade to Mojarra >= 2.1.18 and explanations about improved handling of JSTL taglibs in @ViewScoped) by the contributions of BalusC to other postings including:
https://java.net/jira/browse/JAVASERVERFACES-1492
JSF2 Viewscope validation issue
http://balusc.blogspot.com.au/2010/06/benefits-and-pitfalls-of-viewscoped.html
JSTL in JSF2 Facelets... makes sense?
What are the main disadvantages of Java Server Faces 2.0?
I consider this matter extremely important for anybody working with JSF ! The ability to control what is built - as opposed to what is rendered - easily in @ViewScoped solves many problems and opens up many possibilities, and I recommend that anybody who is seriously working with JSF takes the time to read the remarks by BalusC in the above links.
BalusC, in case your read this, please know that you are are a true hero of JavaServer Faces and Enterprise Java. On behalf of every JSF enthusiast may I thank you.
And thanks to Ed Burns and Ted Goddard for your excellent work in reporting and fixing this: https://java.net/jira/browse/JAVASERVERFACES-1492 It is a major improvement for JSF2.
Finally, I had to use a dirty trick to get Mojarra 2.1.21 installed on NetBeans7.1+Glassfish3.1.1 (required because of 3rd party incompatibilities) as explained here: JSF how upgrade to Mojarra 2.1.21 in Netbeans7.1 (just sub jsf-api.jar and jsf-impl.jar fails)
This was an excellent result for my project. What would I do without Stackoverflow :)
That question is a perfect example why I dislike JSF: As soon as you do anything non-trivial (such as - gasp - trying to reuse code on a large scale), you need knowledge of JSF internals.
JSF represents a view with a component tree. That tree is built out of the view definition by tag handlers, is stateful, and lives until the user leaves the view. Inclusion of a composite component is done by a tag handler. c:if is also implemented by a tag handler.
The component tree is traversed during every phase of the request processing lifecycle. It is however up to the individual components to decide whether (or how many times) their children are processed. That's how the rendered attribute is implemented: During every phase, the component checks whether it is rendered, and skips processing of itself (and its children) if not.
The JSF view scope is kept within the UIViewRoot, which is the root node of the component tree. It is therefore unavailable while tag handlers are processed. That's one of its many shortcomings :-)
So what can you do?
You can include a composite component for every activity in the activity tree, but because that inclusion happens at view build time, it can not occur on demand. Even setting aside problems of mutual recursion, it is likely wasteful to create a dialog for every subactivity on the off chance that the user will want to see that particular dialog. But you can of course limit recursion with c:if, you just need to put the information in a scope available at view build time.
Alternatively, you can create a single dialog in the component tree, but have it show a different activity at different times by binding the current activity to an EL-expression whose target you update. Of course that means only a single dialog is shown at a time. If you need to stack the dialogs of subactivities, you might create as many dialogs as the tree is deep.
Alternatively, you can create a single component that recursively processes itself in every phase of the request processing lifecycle. That can be either an existing component you adapt (such as a tree component), or one written from scratch (which is not exactly trivial because it the component tree is stateful, and state must be preserved and restored for every iteration of the child components).