1

I'm running a Tomcat 7, JSF 2.2, OpenWebBeans 1.6, Omnifaces 1.8 and Richfaces 3. I recently added the Omnifaces CombinedResourceHandler, and noticed that it causes a datatable's value method to be called despite the method being in an unrendered block, when the form on the page is submitted.

Example:

<h:body styleClass="bdyPage">
    <h:form id="form">
        <h:commandButton value="test button" />
        <h:panelGroup rendered="false">
            <h:outputText value="#{testBean.getTestString()}" />
            <h:dataTable id="testtable22" value="#{testBean.strings}" var="str"
                rowClasses="odd, even">
                <h:column>
                    <f:facet name="header">
                        test
                    </f:facet>
                    <h:outputText value="str" />
                </h:column>
            </h:dataTable>
        </h:panelGroup>
    </h:form>
</h:body>

I have a simple test bean:

@Named("testBean")
@RequestScoped
public class TestBean1 implements Serializable {

  private static final long serialVersionUID = 1L;

    public String getTestString() {
        System.out.println("Test string");
        return "test string";
    }

    public List<String> getStrings() {
        List<String> strings = new ArrayList<String>();
        strings.add("hej");
        strings.add("hejsan");
        strings.add("hej hej!");
        System.out.println("getting strings");
        return strings;
    }

}

Normally, the getStrings() isn't called because the panelGroup is set to not be rendered. However, when I run both Omnifaces CombinedResourceHandler and Richfaces, getStrings() is called when the submit button is pressed more than once. This only seems to affect the value attribute of the h:dataTable component. Other el expressions don't get invoked.

I set up a small sample project with the following maven dependices:

    <dependencies>
    <dependency>
        <groupId>org.richfaces.ui</groupId>
        <artifactId>richfaces-components-ui</artifactId>
        <version>4.3.7.Final</version>
    </dependency>
    <dependency>
        <groupId>org.richfaces.core</groupId>
        <artifactId>richfaces-core-impl</artifactId>
        <version>4.3.7.Final</version>
    </dependency> 
<!--    <dependency>  
    <groupId>org.richfaces</groupId>  
    <artifactId>richfaces</artifactId>  
  <version>5.0.0.Alpha3</version>  
</dependency>  
     -->
    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>1.0.0.GA</version>
        <type>jar</type>
        <classifier>sources</classifier>
    </dependency>
    <dependency>
        <groupId>javax.validation</groupId>
        <artifactId>validation-api</artifactId>
        <version>1.0.0.GA</version>
        <type>jar</type>
    </dependency>
    <dependency>
        <groupId>org.omnifaces</groupId>
        <artifactId>omnifaces</artifactId>
        <version>1.8</version>
    </dependency>
    <dependency>
        <groupId>javax.enterprise</groupId>
        <artifactId>cdi-api</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.openwebbeans</groupId>
        <artifactId>openwebbeans-impl</artifactId>
        <version>1.6.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.openwebbeans</groupId>
        <artifactId>openwebbeans-jsf</artifactId>
        <version>1.6.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.openwebbeans</groupId>
        <artifactId>openwebbeans-el22</artifactId>
        <version>1.6.2</version>
    </dependency>
    <dependency>
        <groupId>org.apache.openwebbeans</groupId>
        <artifactId>openwebbeans-web</artifactId>
        <version>1.6.2</version>
    </dependency>
    <dependency>
        <groupId>org.glassfish</groupId>
        <artifactId>javax.faces</artifactId>
        <version>2.2.6</version>
    </dependency>
</dependencies>

I am not using any richfaces components in the project or on the test page. I am merely including Richfaces as a dependency. I don't have any javascripts that get combined. If I disable the Omnifaces CRH or remove Richfaces from the pom file, it works as expected.

I tried using Richfaces 5, but I get a similar behaviour there (although the getter is called fewer times). I have not tried the latest Omnifaces version, since it isn't compatible with OWB 1.6.

Anoter point of interest might be that if I surround the datatable with a JSTL <c:if>, it works. But I don't want to have to do that, obviously ...

Am I doing something wrong here? Is there a compatibility issue between these versions Omnifaces CRH and Richfaces?

Edit: Call stack from the datatable's getter

TestBean1.getStrings() line: 33 
TestBean1$$OwbNormalScopeProxy0.getStrings() line: not available    
NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]  
NativeMethodAccessorImpl.invoke(Object, Object[]) line: not available   
DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: not available   
Method.invoke(Object, Object...) line: not available    
BeanELResolver.getValue(ELContext, Object, Object) line: 87 
DemuxCompositeELResolver._getValue(int, ELResolver[], ELContext, Object, Object) line: 176  
DemuxCompositeELResolver.getValue(ELContext, Object, Object) line: 203  
AstValue.getValue(EvaluationContext) line: 183  
ValueExpressionImpl.getValue(ELContext) line: 185   
WrappedValueExpression.getValue(ELContext) line: 70 
TagValueExpression.getValue(ELContext) line: 109    
ComponentStateHelper.eval(Serializable, Object) line: 194   
ComponentStateHelper.eval(Serializable) line: 182   
HtmlDataTable(UIData).getValue() line: 732  
HtmlDataTable(UIData).getDataModel() line: 1822 
HtmlDataTable(UIData).setRowIndexWithoutRowStatePreserved(int) line: 484    
HtmlDataTable(UIData).setRowIndex(int) line: 473    
HtmlDataTable(UIData).visitColumnsAndColumnFacets(VisitContext, VisitCallback, boolean) line: 2104  
HtmlDataTable(UIData).visitTree(VisitContext, VisitCallback) line: 1446 
HtmlPanelGroup(UIComponent).visitTree(VisitContext, VisitCallback) line: 1701   
HtmlForm(UIComponent).visitTree(VisitContext, VisitCallback) line: 1701 
HtmlForm(UIForm).visitTree(VisitContext, VisitCallback) line: 371   
HtmlBody(UIComponent).visitTree(VisitContext, VisitCallback) line: 1701 
UIViewRoot(UIComponent).visitTree(VisitContext, VisitCallback) line: 1701   
FaceletViewHandlingStrategy.locateComponentByClientId(FacesContext, String) line: 2082  
FaceletViewHandlingStrategy.reapplyDynamicRemove(FacesContext, ComponentStruct) line: 2174  
FaceletViewHandlingStrategy.reapplyDynamicActions(FacesContext) line: 2116  
FaceletViewHandlingStrategy.buildView(FacesContext, UIViewRoot) line: 966   
RenderResponsePhase.execute(FacesContext) line: 99  
RenderResponsePhase(Phase).doPhase(FacesContext, Lifecycle, ListIterator<PhaseListener>) line: 101  
LifecycleImpl.render(FacesContext) line: 219    
FacesServlet.service(ServletRequest, ServletResponse) line: 647 
ApplicationFilterChain.internalDoFilter(ServletRequest, ServletResponse) line: 305  
ApplicationFilterChain.doFilter(ServletRequest, ServletResponse) line: 210  
StandardWrapperValve.invoke(Request, Response) line: 222    
StandardContextValve.invoke(Request, Response) line: 123    
NonLoginAuthenticator(AuthenticatorBase).invoke(Request, Response) line: 472    
StandardHostValve.invoke(Request, Response) line: 171   
ErrorReportValve.invoke(Request, Response) line: 99 
AccessLogValve.invoke(Request, Response) line: 936  
StandardEngineValve.invoke(Request, Response) line: 118 
CoyoteAdapter.service(Request, Response) line: 407  
Http11Processor(AbstractHttp11Processor).process(SocketWrapper<S>) line: 1004   
Http11Protocol$Http11ConnectionHandler(AbstractProtocol$AbstractConnectionHandler).process(SocketWrapper<S>, SocketStatus) line: 589    
JIoEndpoint$SocketProcessor.run() line: 312 
ThreadPoolExecutor(ThreadPoolExecutor).runWorker(ThreadPoolExecutor$Worker) line: not available 
ThreadPoolExecutor$Worker.run() line: not available 
TaskThread(Thread).run() line: not available [local variables unavailable]  
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
Robin
  • 63
  • 6
  • 1
    The latest version of RichFaces is 4.5.11.Final (5.x was scrapped), not that I think it helps. Calling the getter is certainly unnecessary but does it cause any issues? – Makhiel Dec 14 '15 at 16:37
  • Figured I should reply to you as well! Even if we fix the badly constructed getters, it could lead to performance issues if @PostConstructs are called more often than expected. Not likely to be a huge issue ... but it feels very unnecessary. – Robin Dec 17 '15 at 12:22

1 Answers1

1

First of all, getter methods are not supposed to perform business logic: How and when should I load the model from database for h:dataTable. Once you fix your getter method to solely return the property, the concern of it being called multiple times becomes completely unnecessary.

Coming back to the observed behavior, the getter method of an unrendered <h:dataTable> may be called at "unexpected" times when code performs a UIComponent#visitTree() without VisitHint#SKIP_UNRENDERED and/or VisitHint#SKIP_ITERATION. The CombinedResourceHandler itself isn't doing that. At least, not directly. It only manipulates the component resources in the component tree. It however has a RichFaces specific hack in place in order to extract its homegrown resource library approach. Still, its source code doesn't seem to trigger a visitTree() anywhere. To nail down the real cause, just put a debug breakpoint on the getter method and explore the call stack who is responsible for the call.

As to the apparent JSTL <c:if> aversion (the word "obviously" is a bit too strong here), this is food for read: JSTL in JSF2 Facelets... makes sense?

That said, latest OmniFaces 1.x release is the 1.8.3, not 1.8. See also download section. It would also be worth the effort to upgrade Mojarra to latest which is as of now 2.2.12.

Community
  • 1
  • 1
BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555
  • Thanks for the reply. I know that getters shouldn't perform business logic. There's some legacy code doing things it shouldn't, and I've no issues fixing this where I've found it. I was asking more to prevent something similar to happen unexpectedly. It does seem like UIComponent#visitTree() is in the call stack, actually. From what I can tell, it doesn't take SKIP_UNRENDERED into consideration. It doesn't seem to be as if OmniFaces is in there ... I've updated my post with the call stack. Any ideas? – Robin Dec 15 '15 at 08:39
  • Right, it's done by Facelets `FaceletViewHandlingStrategy.reapplyDynamicActions`. This will happen during a rebuild of a dynamically manipulated view (the CRH removes components). I only wonder why the view is rebuilt during render response. This is usually unnecessary. Perhaps you're navigating to a different view using POST instead of GET? – BalusC Dec 15 '15 at 08:48
  • In my small test app I just have a view with a commandButton and a datatable. When the method is run as unrendered, FacesContext at least still believes that it's doing a postback. But it's a bit curious ... I can press the commandButton once without the unrendered method being called. It's when I do it more than once that it works, that is, if I keep making postbacks to the same view. If I completely refresh the page (e.g. just navigating to it via url again), it starts over with one button click working, and the rest triggering this behaviour. – Robin Dec 15 '15 at 09:16