3

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:

  1. The popup p:dialog expert system summary shows the status icon indicator of the Element being inspected.

  2. 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=".."
        >&nbsp;</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',

Community
  • 1
  • 1
  • Even when I control the recursion I am getting frequent java.lang.OutOfMemoryError: Java heap space problems, presumably because the build phase is building an unnecessary part of a tree with a full view as root and expert system summary dialogs. But I don't want to abandon the strategy of reusing portions of the view page in the expert system summary dialogs (DRY principle). There must be some better way of controlling what is built vs what is rendered. – Webel IT Australia - upvoter May 22 '13 at 03:34
  • I welcome any recommendations on effective ways or tools to help investigate what is built (as opposed to rendered). http://stackoverflow.com/questions/16683815/jsf-what-are-best-techniques-and-tools-for-investigating-the-build-phase – Webel IT Australia - upvoter May 22 '13 at 05:03

2 Answers2

4

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 :)

Community
  • 1
  • 1
3

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?

  1. 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.

  2. 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.

  3. 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).

meriton
  • 68,356
  • 14
  • 108
  • 175
  • I am most grateful for your detailed response that shows a deep understanding of the question, and indeed this matter goes right to the heart of JSF2 and finding a solution to it will help me with lots of other cases. However I won't mark this as answered yet, I need to investigate the options you mention first and will also post a final working implementation. In advance, it seems that since Mojarra 2.18 one should be able to indeed use taghandlers with @ViewScoped, so the c:if approach might/should work for me if I upgrade (your other suggestions are sensible but c:if would be nice) – Webel IT Australia - upvoter May 23 '13 at 09:50
  • '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' correct. It would be nice to be able to create the p:dialog truly as needed, really on-the-fly, with creation triggered by clicking on a status icon, and no resources consumed otherwise. – Webel IT Australia - upvoter May 23 '13 at 09:53
  • According to BalusC, taghandlers apparently play nice(r) with @ViewScoped since Mojarra 2.1.18 (I am using Mojarra-2.1.3 in Netbeans7.1 and have not upgraded because some incompatibilities of Glassfish with some 3rd party software I am using). See http://stackoverflow.com/questions/3342984/jstl-in-jsf2-facelets-makes-sense, and comments at https://java.net/jira/browse/JAVASERVERFACES-1492 (javaserverfaces Component bindings incompatible with View Scope). Can't confirm yet because have not managed to update Mojarra independent of Glassfish+Netbeans, have to solve that task first). Then try c:if – Webel IT Australia - upvoter May 23 '13 at 09:58
  • sorry typo above, Mojarra 2.18 should be Mojarra 2.1.18 of course. – Webel IT Australia - upvoter May 23 '13 at 10:00
  • Having trouble upgrading to higher than Mojarra 2.1.18 in Netbeans 7.1 (http://stackoverflow.com/questions/16715314/jsf-how-upgrade-to-mojarra-2-1-21-in-netbeans7-1-just-sub-jsf-api-jar-and-jsf-i) which I need to do to test whether this makes using c:if for build-time control with @ViewScoped possible. – Webel IT Australia - upvoter May 23 '13 at 13:35
  • Have upgraded to Mojarra 2.1.21 (http://stackoverflow.com/questions/16715314/jsf-how-upgrade-to-mojarra-2-1-21-in-netbeans7-1-just-sub-jsf-api-jar-and-jsf-i), c:if works fine now under @ViewScoped for this purpose. Thanks meriton for input. – Webel IT Australia - upvoter May 28 '13 at 05:27
  • I'll mark this as also answering my question because of this: " 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.", modulated by the need to upgrade to Mojarra 2.1.18 or greater so that c:if works as required under @ViewScoped. – Webel IT Australia - upvoter May 28 '13 at 05:29