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:
<c:if test="#{not cc.attrs.preventRecursionOnDialog}">
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 :)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With