2

I have a page with a very simple form which submits ajax requests targeted only at components in that same form. In the same page (but outside the form), there is also a ui:repeat which iterates over an array returned from a request scoped managed bean (suppose a list of product categories). This bean has no properties bound in the form and is not accessed in any other way apart from the value attribute of the ui:repeat tag. I don't understand why JSF needs to recreate the request scoped bean on every ajax postback, just as if I asked to render this external ui:repeat (which has nothing to do with the form) along with some component inside the form.

Is it a bug? Or is it an expected behavior? Of course I could annotate the bean as ViewScoped but I don't see the reason to have categories stored in the view scope as they are completely static between postbacks.

Another solution/workaround I found is to render the ui:repeat only in case of non-ajax requests:

<ul>
    <ui:repeat value="#{someRequestScopedBean.categories}" var="category" rendered="#{not facesContext.partialViewContext.ajaxRequest}">
        <li>#{category.name}</li>
    </ui:repeat>
</ul>

but I don't know if this could cause problems and looks not very clear.

TEST CASE

index.xhtml:

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html"
      xmlns:ui="http://xmlns.jcp.org/jsf/facelets"
      xmlns:f="http://xmlns.jcp.org/jsf/core">
    <h:head>
        <title>Facelet Title</title>
    </h:head>
    <h:body>
        <h1>Test page</h1>
        <p>Random jokes</p>
        <ul>
            <ui:repeat value="#{oneLiners.list}" var="oneliner">
                <li>#{oneliner}</li>
            </ui:repeat>
        </ul>
        <h:form>
            <h:selectOneMenu value="#{backingBean.greeting}" hideNoSelectionOption="true">
                <f:selectItem value="#{null}" itemLabel="Select a greeting..." noSelectionOption="true"/>
                <f:selectItems value="#{backingBean.greetings}"/>
                <f:ajax render="@this btn"/>
            </h:selectOneMenu>
            <h:commandButton id="btn" value="Say Hello!" disabled="#{empty backingBean.greeting}">
                <f:ajax render="otxt"/>
            </h:commandButton>
            <h:outputText id="otxt" value="#{backingBean.greeting}, Maurizio!" style="display: #{empty backingBean.greeting ? 'none' : 'block'}"/>
        </h:form>
    </h:body>
</html>

Request scoped bean:

package testuirepajax;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;

/**
 *
 * @author maurizio
 */
@ManagedBean
@RequestScoped
public class OneLiners {
    private String[] list;

    public OneLiners() {
        System.out.println("testuirepajax.OneLiners.<init>()");
        list = new String[] {
            "Life is wonderful. Without it we'd all be dead.",
            "Daddy, why doesn't this magnet pick up this floppy disk?",
            "Daddy, what does FORMATTING DRIVE C mean?",
            "Never forget: 2 + 2 = 5 for extremely large values of 2.",
            "C:\\ is the root of all directories."
        };
    }

    public String[] getList() {
        System.out.println("testuirepajax.OneLiners.getList()");
        return list;
    }
}

Form backing bean:

package testuirepajax;

import javax.faces.bean.ManagedBean;
import javax.faces.bean.ViewScoped;

/**
 *
 * @author maurizio
 */
@ManagedBean
@ViewScoped
public class BackingBean {

    private String[] greetings;
    private String greeting;

    public BackingBean() {
        System.out.println("testuirepajax.BackingBean.<init>()");
        greetings = new String[] {
          "Hello", "Hi", "Good morning", "Good evening", "Good night"
        };
    }

    public String[] getGreetings() {
        return greetings;
    }

    public void setGreeting(String greeting) {
        this.greeting = greeting;
    }

    public String getGreeting() {
        return greeting;
    }
}

Check the output of your container. Using the Payara Server (which ships Mojarra 2.2.12), I see lines like these:

Informazioni:   testuirepajax.OneLiners.<init>()
Informazioni:   testuirepajax.OneLiners.getList()
Informazioni:   testuirepajax.OneLiners.getList()
Informazioni:   testuirepajax.OneLiners.getList()
Informazioni:   testuirepajax.OneLiners.getList()
Informazioni:   testuirepajax.OneLiners.getList()
Informazioni:   testuirepajax.OneLiners.<init>()
Informazioni:   testuirepajax.OneLiners.getList()
Informazioni:   testuirepajax.OneLiners.getList()
Informazioni:   testuirepajax.OneLiners.getList()
Informazioni:   testuirepajax.OneLiners.getList()
Informazioni:   testuirepajax.OneLiners.getList()

when selecting elements from the menu or clicking the "Say Hello!" button.

maurizeio
  • 266
  • 2
  • 11
  • Does it contain input components? Which JSF impl/version? Similar bug was fixed in Mojarra 2.2.7 and 2.1.29. Nice workaround there! Add if necessary a `` for future readers before they get mindblown. – BalusC Feb 22 '16 at 18:36
  • @BalusC 1) The form contains a couple of `h:selectOneMenu` and an `h:button` but happens also with forms containing `h:commandButton`. 2) Mojarra 2.2.12 – maurizeio Feb 22 '16 at 19:38
  • They are inside the ui:repeat? Well, then this side effect is unavoidable as JSF needs to reiterate over it anyway in order to restore state of those components during postback. "Partial restore" is unfortunately not supported by JSF (chicken-egg and all). – BalusC Feb 22 '16 at 19:47
  • @BalusC there are no input components inside the `ui:repeat`. They are inside the form only. I'm writing a test case right now. – maurizeio Feb 22 '16 at 19:57
  • @BalusC I don't know if relevant but I'm using the Payara server which bundles Mojarra 2.2.12. – maurizeio Feb 22 '16 at 20:19
  • @BalusC Actually, I misunderstood your last question. You were talking about *the form*, right? Well, the form is not nested inside the `ui:repeat` (nor vice-versa). See the test case. – maurizeio Feb 23 '16 at 12:01
  • Great test case. I'll check it. – BalusC Feb 23 '16 at 12:16

1 Answers1

2

I placed a breakpoint on getList() method and inspected the call stack when it's "unnecessarily" hit during postback in order to learn about the who and the why:

Daemon Thread [http-nio-8088-exec-5] (Suspended (breakpoint at line 23 in OneLiners))   
    owns: NioChannel  (id=83)   
    OneLiners.getList() line: 23    
    NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]  
    NativeMethodAccessorImpl.invoke(Object, Object[]) line: 62  
    DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: 43  
    Method.invoke(Object, Object...) line: 497  
    BeanELResolver.getValue(ELContext, Object, Object) line: 97 
    DemuxCompositeELResolver._getValue(int, ELResolver[], ELContext, Object, Object) line: 176  
    DemuxCompositeELResolver.getValue(ELContext, Object, Object) line: 203  
    AstValue.getValue(EvaluationContext) line: 169  
    ValueExpressionImpl.getValue(ELContext) line: 184   
    TagValueExpression.getValue(ELContext) line: 109    
    UIRepeat.getValue() line: 279   
    UIRepeat.getDataModel() line: 255   
    UIRepeat.visitTree(VisitContext, VisitCallback) line: 727   
    HtmlBody(UIComponent).visitTree(VisitContext, VisitCallback) line: 1700 
    UIViewRoot(UIComponent).visitTree(VisitContext, VisitCallback) line: 1700   
    PartialViewContextImpl.processComponents(UIComponent, PhaseId, Collection<String>, FacesContext) line: 403  
    PartialViewContextImpl.processPartial(PhaseId) line: 266    
    UIViewRoot.processDecodes(FacesContext) line: 927   
    ApplyRequestValuesPhase.execute(FacesContext) line: 78  
    ApplyRequestValuesPhase(Phase).doPhase(FacesContext, Lifecycle, ListIterator<PhaseListener>) line: 101  
    LifecycleImpl.execute(FacesContext) line: 198   
    FacesServlet.service(ServletRequest, ServletResponse) line: 658 
    ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 291  
    ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206  
    WsFilter.doFilter(ServletRequest, ServletResponse, FilterChain) line: 52    
    ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 239  
    ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 206  
    StandardWrapperValve.invoke(Request, Response) line: 212    
    StandardContextValve.invoke(Request, Response) line: 106    
    FormAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 502    
    StandardHostValve.invoke(Request, Response) line: 141   
    ErrorReportValve.invoke(Request, Response) line: 79 
    AccessLogValve(AbstractAccessLogValve).invoke(Request, Response) line: 616  
    StandardEngineValve.invoke(Request, Response) line: 88  
    CoyoteAdapter.service(Request, Response) line: 521  
    Http11NioProcessor(AbstractHttp11Processor<S>).process(SocketWrapper<S>) line: 1096 
    Http11NioProtocol$Http11ConnectionHandler(AbstractProtocol$AbstractConnectionHandler<S,P>).process(SocketWrapper<S>, SocketStatus) line: 674    
    NioEndpoint$SocketProcessor.doRun() line: 1500  
    NioEndpoint$SocketProcessor.run() line: 1456    
    ThreadPoolExecutor(ThreadPoolExecutor).runWorker(ThreadPoolExecutor$Worker) line: 1142  
    ThreadPoolExecutor$Worker.run() line: 617   
    TaskThread$WrappingRunnable.run() line: 61  
    TaskThread(Thread).run() line: 745  

The interesting lines are the ones above FacesServlet. The class/method names already kind of speak for themselves.

It thus happened during apply request values phase, when the partial request needs to process the decode of components. The component tree is visited in order to find the components identified by client IDs specified in <f:ajax execute> (which defaults to @this). As the <ui:repeat> is in the way before the component of interest, it's inspected first. A visitTree() triggers full iteration because the client IDs of interest are only available during iteration.

When I moved <ui:repeat> to below the <h:form>, it isn't invoked anymore. All components of interest have already been found at that point.

This behavior is, unfortunately, "by design". Your work around is a good one. Better is to check #{not facesContext.postback} instead as this also covers non-ajax postbacks.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks Balus. Looking at other facelets I made, I was thinking about the same idea too (moving the `ui:repeat` in order to see if position was relevant). Now everything is crystal-clear. – maurizeio Feb 23 '16 at 13:48