0

I am having problems getting an action of a managed bean to be called when certain other JSF elements from custom composite components are present in the same page as the h:commandButton that submits the h:form. I can narrow down the page elements that cause problems to those that perform the following harmless (to me) looking rendering tests:

        <h:panelGroup rendered="#{not empty cc.attrs.element}">

or:

        <h:panelGroup rendered="#{empty cc.attrs.element}">

where 'element' is a required attribute, loaded from a JPA entity by id. Something about that 'empty cc.attrs.element' or 'not empty cc.attrs.element' test blocks correct form submission and blocks action calling in any h:form in which it appears.

The form submission page debug.xhtml is:

<f:view>
    <f:metadata>
        <f:viewParam name="id" value="#{elementController.id}"/>
    </f:metadata>
</f:view>

<h:head>
    <title>DEBUG</title>
</h:head>
<h:body>
<h:form>        
<util:view_link element="#{elementController.selected}"/>
<h:commandButton value="Apply" action="#{elementController.reflect}" /> 
        </h:form>
    </h:body>

Note the view parameter 'id' that via ElementController.setId will force the loading of the 'selected' or 'current' Element entity by id from JPA, not shown here. This should then move to test.xhtml via the action reflect():

@JSFaction
public Object reflect() {
    String $i = "reflect";
    log_debug($i);
    if (current==null) {
        log_warn($i, "can't generate reflect outcome for null current element");
        return null;
    }
    //return "reflect?faces-redirect=true&includeViewParams=true&id="+current.getId();
    //return "test?faces-redirect=true&includeViewParams=true&id="+current.getId();
    //return "reflect?id="+current.getId();
     return "/test?faces-redirect=true&id="+current.getId();
}

You can see above I have experimented with different outcome URLs, but it turns out the problem has nothing to do with that, it is simply that the action is not called at all (when the {not empty cc.attrs.element} test is performed in another page element.

The composite component that causes the problem is my util:view_link::

    <composite:interface>
    <composite:attribute name="element" required="true" type="com.greensoft.objectdb.test.entity.Element"/>
    <composite:attribute name="name" required="false"/>
    <composite:attribute name="showOwnerAsText" required="false" default="false"/>
    <composite:attribute name="showModelClass" required="false" default="false"/>
    <composite:attribute name="showEntityClass" required="false" default="false"/>
    <composite:attribute name="showId" default="false"/>
    <!--composite:attribute name="showHelp" required="false" default="false"/-->
    <!--composite:attribute name="title" required="false"/-->
</composite:interface>

<composite:implementation>

    <h:panelGroup rendered="#{empty cc.attrs.element}">
        <span class="warn">NULL</span>
    </h:panelGroup>

    <h:panelGroup rendered="#{not empty cc.attrs.element}">
        <h:panelGroup rendered="#{cc.attrs.showOwnerAsText}">
            <h:outputText style="font-weight:bold" value="#{cc.attrs.element.owner.name}."/>
        </h:panelGroup>

        <h:panelGroup rendered="#{cc.attrs.showId}">
            <h:link outcome="/view" value="&lt;#{cc.attrs.element.id}&gt;">
                <f:param name="id" value="#{cc.attrs.element.id}"/>
            </h:link>
        </h:panelGroup>

        <h:link outcome="/view" 
                value="#{empty cc.attrs.name ? cc.attrs.element.name: cc.attrs.name}"
                >
            <f:param name="id" value="#{cc.attrs.element.id}"/>
        </h:link>
    </h:panelGroup>
</composite:implementation>

You can see this is just a reusable way of creating a link to each Element, with the database id passed as parameter. If there is no Element a "NULL" is shown.

By commenting out bits at a time (bracketing) I can prove the problem has something to do with rendered="#{empty cc.attrs.element} and rendered="#{not empty cc.attrs.element} tests, which both prevent form submission and the action from being invoked by a h:commandButton in the same composed page (see debug.xhtml above). If for example I just use plain h:panelGroup elements without the 'rendered="#{empty cc.attrs.element}' and 'rendered="#{not empty cc.attrs.element}' tests, everything displays fine and the h:commandButton works as expected and the action is called ok, and the navigation works fine !

Q: Why does test 'rendered="#{empty cc.attrs.element}' or 'rendered="#{not empty cc.attrs.element}' prevent correct form submission and stop the action from being called ?

Grateful for ideas, Webel

EDIT: The problem only happens when I test on elementController.selected, where .selected is an @Entity Element. I tried it with a simple Duummy class with String name property, and a @RequestScoped DebugController, and it worked fine (the action was called).

<h:panelGroup rendered="#{not empty debugController.dummy}">
<h:outputText value="#{debugController.dummy.name}"/>
</h:panelGroup>
<h:commandButton value="Apply" action="#{elementController.reflect}" /> 

Reported as bug: http://java.net/jira/browse/JAVASERVERFACES-2343

1 Answers1

0

I can now partially answer this question. I can now see what is happening and describe it, but I still do not understand fully - in terms of JSF phases and request scope - why it is happening as it is. It is a subtle and tricky problem.

To explain it I have rewritten the test case to make it simpler. The view.xhtml test page that takes an id query parameter is:

<f:view>
    <f:metadata>
        <f:viewParam name="id" value="#{elementController.id}"/>
    </f:metadata>
</f:view>
<h:body>   
    <h:form>

       <h:panelGroup rendered="#{empty elementController.selected}">
           <div style="color:orange;">
               No Element found for id(#{elementController.id}) or page called without id query param.
           </div>
       </h:panelGroup>

       <h:panelGroup rendered="#{not empty elementController.selected}">
           [<h:outputText value="#{elementController.selected.id}"/>]&nbsp;
           <h:outputText value="#{elementController.selected.name}"/>
       </h:panelGroup>

        <br/><br/>

       <h:commandButton value="Apply" action="#{elementController.action}" />

    </h:form>
</h:body>

Where ElementController extends RequestController. On loading a view.xhtml the id query parameter is set as a viewParam using setId, which loads a matching Element entity by id (from database) and sets it as the 'current' (a.k.a. "selected") Element, available as getSelected(), which also performed a test for null current, and that was what was causing the problem:

public void setId(Long id) {
    log_debug("setId","id",id);
    if (id != null) {
        this.id = id;
        T found = (T) getAbstractFacade().find(id);
        if (found == null) {
            String $error = "No object with id(" + id + ") found for class " + getManagedClass().getSimpleName();
            log_error($error);
        }
        setCurrent(found);
    }
}

public T getSelected() {
    log_debug("getSelected","current",current);        
    if (current == null) {
        //current = newElement(); //THIS WAS CAUSING PROBLEMS
        log_warn("getSelected","null current Element");
    }
    return current;
}

The problem was that on submission of the form, getSelected() was being called twice for each instance of the test '#{not empty elementController.selected}' or '#{empty elementController.selected}' before the action was being called and with the field 'current' null.

In the @RequestScoped @ManagedBean ElementController the getSelected() method had a test for whether the current (a.k.a. selected) Element is null, and was taking an action that lead to an error, and this was shortcircuiting the call of the action on the commandButton when a 'not empty elementController.selected' was called:

(The problem I am seeing has nothing to do with whether the "selected" Element subjected to the 'not empty' or 'empty' test is an @Entity or not, I was mislead to believe that because the problem is with the ElementController that handles Element entities, and of course that conclusion did not sound right anyway.)

If I get rid of the assignment of newElement(), which was causing an error, and was there to catch cases when the view page is invoked without an id parameter (that triggers loading of the 'current' entity by id), and instead log a warning, I can see that for each instance of a 'not empty elementController.selected' or 'empty elementController.selected' test, that warning is logged twice BEFORE the action of the commandButton is called on form submission.

Somehow - and this is the part I do not fully understand - under @RequestScoped, the Element that was assigned to variable 'current' (a.k.a. "selected") is lost, and the 'not empty elementController.selected' test in the view page (and thus getSelected()) is invoked twice with current = null (with side-effects).

If I change the scope of the ElementController to @SessionScoped the problem vanishes; the current Element state is maintained, and getSelected() is never called with 'current' null.

I would appreciate any reply comments that explain in terms of JSF phases and request scope why my 'current' variable is suddenly null and why the '#{not empty elementController.selected}' is performed twice before the action is called. I have checked that setId is not called at all in between (only on initial view page load), something else is resetting current to null, or the request scoped bean is recreated in between form submission and the calling of the action.