3 Replies Latest reply on Jun 25, 2009 5:06 PM by Jeff Haynes

    Duplicate component ID on components with binding

    Jeff Haynes Newbie

      I've come across a very frustrating problem and I'm hoping that somebody here can point me in the right direction to a fix.

      I noticed the problem when I was trying to create a user list that would be included in a side navigation panel on any/all various pages of the website. The user list was created with a <rich:dataTable /> tag, and populated with a list of users from our database. The list can be sorted, filtered, and the <rich:datascroller /> was used to paginate between pages of 10 users each.

      I bound the dataTable to a session-scoped backing bean (not SFSB) in jorder to maintain state of the table throughout a user's session, especially if they hide the user table or navigate to a completely different page and then back (I want the selected page, sort order, etc. to remain as it was set by the user for the duration of their session.)

      Pretty straight-forward stuff, but the problem comes when this user table is rendered on a *different view* - i.e. if the table is first rendered on home.xhtml when the user logs in, and then they navigate to another view that includes the list, say profile.xhtml, the following exception is thrown:


      11:58:16,937 ERROR [viewhandler] Error Rendering View[/user.xhtml]
      java.lang.IllegalStateException: duplicate Id for a component navUserListForm:navUserListTable:firstNameCol
      at org.ajax4jsf.application.TreeStructureNode.apply(TreeStructureNode.java:68)
      at org.ajax4jsf.application.TreeStructureNode.apply(TreeStructureNode.java:92)
      at org.ajax4jsf.application.TreeStructureNode.apply(TreeStructureNode.java:92)
      at org.ajax4jsf.application.TreeStructureNode.apply(TreeStructureNode.java:92)
      at org.ajax4jsf.application.TreeStructureNode.apply(TreeStructureNode.java:92)
      at org.ajax4jsf.application.TreeStructureNode.apply(TreeStructureNode.java:92)
      at org.ajax4jsf.application.AjaxStateManager.getTreeStructureToSave(AjaxStateManager.java:187)
      at org.ajax4jsf.application.AjaxStateManager.buildViewState(AjaxStateManager.java:498)
      at org.ajax4jsf.application.AjaxStateManager$SeamStateManagerWrapper.saveView(AjaxStateManager.java:105)
      at org.jboss.seam.jsf.SeamStateManager.saveView(SeamStateManager.java:89)
      at org.ajax4jsf.application.AjaxStateManager.saveSerializedView(AjaxStateManager.java:454)
      at com.sun.facelets.FaceletViewHandler.renderView(FaceletViewHandler.java:615)
      at org.ajax4jsf.application.ViewHandlerWrapper.renderView(ViewHandlerWrapper.java:100)
      at org.ajax4jsf.application.AjaxViewHandler.renderView(AjaxViewHandler.java:176)
      at com.sun.faces.lifecycle.RenderResponsePhase.execute(RenderResponsePhase.java:110)
      at com.sun.faces.lifecycle.Phase.doPhase(Phase.java:100)
      at com.sun.faces.lifecycle.LifecycleImpl.render(LifecycleImpl.java:139)
      at javax.faces.webapp.FacesServlet.service(FacesServlet.java:266)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
      at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:83)
      at org.jboss.seam.web.IdentityFilter.doFilter(IdentityFilter.java:40)
      at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
      at org.jboss.seam.web.MultipartFilter.doFilter(MultipartFilter.java:90)
      at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
      at org.jboss.seam.web.ExceptionFilter.doFilter(ExceptionFilter.java:64)
      at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
      at org.jboss.seam.web.RedirectFilter.doFilter(RedirectFilter.java:45)
      at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
      at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:178)
      at org.ajax4jsf.webapp.BaseFilter.handleRequest(BaseFilter.java:290)
      at org.ajax4jsf.webapp.BaseFilter.processUploadsAndHandleRequest(BaseFilter.java:368)
      at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:495)
      at org.jboss.seam.web.Ajax4jsfFilter.doFilter(Ajax4jsfFilter.java:56)
      at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
      at org.jboss.seam.web.LoggingFilter.doFilter(LoggingFilter.java:58)
      at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
      at org.jboss.seam.web.HotDeployFilter.doFilter(HotDeployFilter.java:53)
      at org.jboss.seam.servlet.SeamFilter$FilterChainImpl.doFilter(SeamFilter.java:69)
      at org.jboss.seam.servlet.SeamFilter.doFilter(SeamFilter.java:158)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
      at org.jboss.web.tomcat.filters.ReplyHeaderFilter.doFilter(ReplyHeaderFilter.java:96)
      at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
      at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
      at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:235)
      at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:191)
      at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:190)
      at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:433)
      at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:92)
      at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.process(SecurityContextEstablishmentValve.java:126)
      at org.jboss.web.tomcat.security.SecurityContextEstablishmentValve.invoke(SecurityContextEstablishmentValve.java:70)
      at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:127)
      at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
      at org.jboss.web.tomcat.service.jca.CachedConnectionValve.invoke(CachedConnectionValve.java:158)
      at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
      at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:330)
      at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:829)
      at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:601)
      at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:447)
      at java.lang.Thread.run(Thread.java:619)


      After a bit (well, quite a lot) of searching, I came across JIRA 2218 https://jira.jboss.org/jira/browse/RF-2218 which seemed pretty close to the problem I was seeing. When I have a component that is bound to a backing bean, I will get the duplicate id exception if I navigate to another page that includes that component. If I remove the binding, I can navigate between the pages that include the component, but the state of the selected page, column sort order, etc. will be lost and the table will be reset on each page.

      In the JIRA, Nick points to an issue in the JSF-API implementation that is related to removing old children from the render tree, and suggests setting the context param org.ajax4jsf.SERIALIZE_SERVER_STATE to true as a workaround.

      My issue is, this workaround does not seem to correct the problem I'm seeing with the environment I'm using:


      jboss-5.0.1.GA
      jboss-seam-2.1.2
      jboss-richfaces-3.3.1.GA
      WinXP Professional 2002 SP3


      After doing a lot of testing and code traces, I found that the last bound component in a tree has its children duplicated on each disparate view. This problem occurs with many of the components that I tested - dataTable, dataList, panels, etc.

      At this point, I'm stuck and pretty sure that the problem is either:

      1. I'm totally missing something, and implementing my JSF application pages incorrectly
      2. There is still a problem in jsf-api.jar (seems to be in UIComponentBase to me now) that is affecting any Richfaces or plain JSF application in a majorly detrimental way

      I'm hoping that it's #1 above, and somebody out there can give me that magic little setting that will make this problem go away. However, after a couple of weeks of looking at this (!), I'm really thinking that it's #2.

      I know this is the Richfaces forum, so I posted here because I'm seeing the problem manifest itself in my use of Richfaces. I could imagine that this post might be better suited to the JBoss AS forum (or one that covers the javax.faces.component/javax.faces.webapp classes), but I'm hoping that somebody here might point me in the right direction.

      I'm actually quite surprised that I can't find a recent JIRA on this (or any other similar problems in the forums/google), so I must admit that I am very perplexed. Should I create a new JIRA, and if so, under what project? I have an ear that demonstrates this problem quite clearly.

      Hopefully somebody out there can enlighten me... it would be much appreciated!


      Of course, the obligatory (seam-gen) files follow.

      table.xhtml, the dataTable page included in home.xhtml and user.xhtml:

      <html xmlns="http://www.w3.org/1999/xhtml"
       xmlns:ui="http://java.sun.com/jsf/facelets"
       xmlns:h="http://java.sun.com/jsf/html"
       xmlns:f="http://java.sun.com/jsf/core"
       xmlns:a4j="http://richfaces.org/a4j"
       xmlns:rich="http://richfaces.org/rich"
       xmlns:s="http://jboss.com/products/seam/taglib">
      
       <h:form id="navUserListForm">
       <h:panelGrid columns="2" columnClasses="top" id="userPanelGrid">
       <rich:dataTable value="#{userManager.userList}" var="user"
       id="navUserListTable" binding="#{userTableBinder.table}"
       rows="4" width="100%" columnClasses="top">
      
       <rich:column sortBy="#{user.firstName}" id="firstNameCol">
       <f:facet name="header">First Name</f:facet>
       <h:outputText value="#{user.firstName}" id="firstName" />
       </rich:column>
      
       <rich:column sortBy="#{user.lastName}" id="lastNameCol">
       <f:facet name="header">Last Name</f:facet>
       <h:outputText value="#{user.lastName}" id="lastName" />
       </rich:column>
      
      
       <f:facet name="footer" >
       <rich:datascroller id="navUserListPaginator" maxPages="10" ajaxSingle="true"
       renderIfSinglePage="false" />
       </f:facet>
      
       </rich:dataTable>
       </h:panelGrid>
       </h:form>
      
      </html>
      



      UserTableBinder.java, used to bind the dataTable:
      package com.test.faces.data;
      
      import org.jboss.seam.ScopeType;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Scope;
      import org.richfaces.component.UIDataTable;
      
      @Scope(ScopeType.SESSION)
      @Name("userTableBinder")
      public class UserTableBinder implements java.io.Serializable {
      
       private static final long serialVersionUID = 9119380620478682684L;
       private UIDataTable table;
      
      
       public UserTableBinder() { }
      
      
       public UIDataTable getTable() {
       return table;
       }
      
       public void setTable(UIDataTable table) {
       this.table = table;
       }
      
      }
      



      web.xml:
      <?xml version="1.0"?>
      <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
      
      
       <display-name>test</display-name>
       <description>Test Application</description>
      
      
       <!-- Duplicate ID Bug? [RF-2218] -->
       <context-param>
       <param-name>org.ajax4jsf.SERIALIZE_SERVER_STATE</param-name>
       <param-value>true</param-value>
       </context-param>
      
       <!-- RichFaces -->
       <context-param>
       <param-name>facelets.DEVELOPMENT</param-name>
       <param-value>true</param-value>
       </context-param>
       <context-param>
       <param-name>facelets.SKIP_COMMENTS</param-name>
       <param-value>true</param-value>
       </context-param>
      
      
       <!-- Application Context Parameters -->
       <context-param>
       <param-name>entityManagerFactory</param-name>
       <param-value>java:/EntityManagerFactory/em</param-value>
       </context-param>
      
      
       <!-- JSF -->
       <context-param>
       <param-name>javax.faces.DEFAULT_SUFFIX</param-name>
       <param-value>.xhtml</param-value>
       </context-param>
      
      
       <!-- Seam -->
       <listener>
       <listener-class>org.jboss.seam.servlet.SeamListener</listener-class>
       </listener>
       <filter>
       <filter-name>Seam Filter</filter-name>
       <filter-class>org.jboss.seam.servlet.SeamFilter</filter-class>
       </filter>
       <filter-mapping>
       <filter-name>Seam Filter</filter-name>
       <url-pattern>/*</url-pattern>
       </filter-mapping>
       <servlet>
       <servlet-name>Seam Resource Servlet</servlet-name>
       <servlet-class>org.jboss.seam.servlet.SeamResourceServlet</servlet-class>
       </servlet>
       <servlet>
       <servlet-name>Faces Servlet</servlet-name>
       <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
       <load-on-startup>1</load-on-startup>
       </servlet>
       <servlet-mapping>
       <servlet-name>Seam Resource Servlet</servlet-name>
       <url-pattern>/seam/resource/*</url-pattern>
       </servlet-mapping>
       <servlet-mapping>
       <servlet-name>Faces Servlet</servlet-name>
       <url-pattern>*.seam</url-pattern>
       </servlet-mapping>
      
      
       <session-config>
       <session-timeout>30</session-timeout>
       </session-config>
      
       <welcome-file-list>
       <welcome-file>index.html</welcome-file>
       </welcome-file-list>
      
       <security-constraint>
       <display-name>Restrict raw XHTML Documents</display-name>
       <web-resource-collection>
       <web-resource-name>XHTML</web-resource-name>
       <url-pattern>*.xhtml</url-pattern>
       </web-resource-collection>
       <auth-constraint/>
       </security-constraint>
      
       <login-config>
       <auth-method>BASIC</auth-method>
       </login-config>
      
      </web-app>
      



      components.xml:
      <?xml version="1.0" encoding="UTF-8"?>
      <components xmlns="http://jboss.com/products/seam/components"
       xmlns:core="http://jboss.com/products/seam/core"
       xmlns:persistence="http://jboss.com/products/seam/persistence"
       xmlns:drools="http://jboss.com/products/seam/drools"
       xmlns:bpm="http://jboss.com/products/seam/bpm"
       xmlns:security="http://jboss.com/products/seam/security"
       xmlns:mail="http://jboss.com/products/seam/mail"
       xmlns:ui="http://jboss.com/products/seam/ui"
       xmlns:web="http://jboss.com/products/seam/web"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:international="http://jboss.com/products/seam/international"
       xsi:schemaLocation=
       "http://jboss.com/products/seam/core http://jboss.com/products/seam/core-2.1.xsd
       http://jboss.com/products/seam/persistence http://jboss.com/products/seam/persistence-2.1.xsd
       http://jboss.com/products/seam/drools http://jboss.com/products/seam/drools-2.1.xsd
       http://jboss.com/products/seam/bpm http://jboss.com/products/seam/bpm-2.1.xsd
       http://jboss.com/products/seam/security http://jboss.com/products/seam/security-2.1.xsd
       http://jboss.com/products/seam/mail http://jboss.com/products/seam/mail-2.1.xsd
       http://jboss.com/products/seam/web http://jboss.com/products/seam/web-2.1.xsd
       http://jboss.com/products/seam/components http://jboss.com/products/seam/components-2.1.xsd
       http://jboss.com/products/seam/international http://jboss.com/products/seam/international-2.1.xsd">
      
       <core:init debug="true" jndi-pattern="@jndiPattern@"/>
      
       <core:manager concurrent-request-timeout="500"
       conversation-timeout="120000"
       conversation-id-parameter="cid"
       parent-conversation-id-parameter="pid"/>
      
       <web:hot-deploy-filter url-pattern="*.seam"/>
      
       <persistence:managed-persistence-context name="entityManager"
       auto-create="true"
       persistence-unit-jndi-name="java:/EntityManagerFactory/em"/>
      
      
      </components>
      




        • 1. Re: Duplicate component ID on components with binding
          Jay Balunas Master

          Wow it certainly sounds like you have investigated this issue well, and I appreciate the details :-)

          I'll move this topic to use forums since this is for discussing the design of the RichFaces project and not for user questions. There it will get more eyes on it.

          • 2. Re: Duplicate component ID on components with binding
            Nick Belaevski Master

            Hi,

            RF-2218 is about request-scoped binding and MyFaces + JSP views; that's not applicable to Facelets pages.

            On your case: note that the component that got duplicate id is not a table itself, but column. So:

            1. You navigate to pageA - component tree is built; table component is stored in session variable together with all children/facets, because it holds links to them.

            2. You navigate to pageB - new view is being built; table is obtained from the session variable through binding and then extra columns are created by view handler, because they are defined on this page also, aren't they? Most likely, they have the same id (page structure is regular), so we end with the component tree containing components with the same clientId - that's forbidden by JSF specification - exception is thrown on view state being saved.

            Couple of words on why components are highly not recommended to be put in session scope:

            1. Components are not Serializable itself, so this may break replication in clustered environment.
            2. Components are designed to be used in single-threaded environment, multi-threaded access (such as session-bound bean) can cause all sorts of concurrency issues - iterators throwing ConcurrentModificationException, etc.

            • 3. Re: Duplicate component ID on components with binding
              Jeff Haynes Newbie

              Nick, thanks very much for the post - it definitely clears some things up for me.

              After reading your post and re-reading the RichFaces docs, specifically:

              The attribute takes a value-binding expression for a component property of a backing bean


              I realized that I just need to keep the state of some of the attributes of the UIColumn and UIDatascroller components, not the entire table itself. I don't know how many times I've read the RichFaces docs, but I kept missing this one little subtlety - don't bind the components themselves, just bind the required attributes to backing beans.

              I did recently see a seam forum posting by Gavin (http://seamframework.org/Community/JavaxejbEJBExceptionCouldNotPassivateFailedToSaveState) that stated that you can't (well, shouldn't) bind components to SFSBs due to the same points that Nick raised, but for some reason I did not see the simplicity of the solution until Nick's post.

              So, for anyone else with a similar situation, I took a different approach to maintaining the UI state of the table (selected page, column sorting, etc.). Instead of binding the UIDataTable to my backing bean (incorrect!), I bound the various values that I need to keep track of instead. These can be SFSB and should work nicely in a cluster if they are Serialized.

              I've included my modified code below as an example. Works as expected now - table state is maintained for the duration of a user session, so you will still see the same selected page, column sorting, etc. anytime you return to a page that contains the table. And I'm no longer getting my duplicate ID exception when on a different view or on a page that contains this table twice (i.e. a simple User list in the left-hand navigation, and a detailed User list in the main content area.)

              Thanks again for the quick replies guys. Sucks that I was simply not implementing things correctly, but at least the coding can proceed now!

              UserTableBinder, modified to bind component properties instead of UIDataTable component:

              package com.test.faces.data;
              
              import org.jboss.seam.ScopeType;
              import org.jboss.seam.annotations.Name;
              import org.jboss.seam.annotations.Scope;
              import org.richfaces.model.Ordering;
              
              @Scope(ScopeType.SESSION)
              @Name("userTableBinder")
              public class UserTableBinder implements java.io.Serializable {
              
               private static final long serialVersionUID = 9119380620478682684L;
               private Ordering sortOrderFirstName = Ordering.ASCENDING;
               private Ordering sortOrderLastName = Ordering.ASCENDING;
               private String filterValueFirstName;
               private String filterValueLastName;
               private int selectedPage = 1;
              
               public UserTableBinder() { }
              
              
               public int getSelectedPage() {
               return selectedPage;
               }
              
               public void setSelectedPage(int selectedPage) {
               this.selectedPage = selectedPage;
               }
              
               public Ordering getSortOrderFirstName() {
               return sortOrderFirstName;
               }
              
               public void setSortOrderFirstName(Ordering sortOrder) {
               this.sortOrderFirstName = sortOrder;
               }
              
               public Ordering getSortOrderLastName() {
               return sortOrderLastName;
               }
              
               public void setSortOrderLastName(Ordering sortOrder) {
               this.sortOrderLastName = sortOrder;
               }
              
               public String getFilterValueFirstName() {
               return filterValueFirstName;
               }
              
               public void setFilterValueFirstName(String filterValue) {
               this.filterValueFirstName = filterValue;
               }
              
               public String getFilterValueLastName() {
               return filterValueLastName;
               }
              
               public void setFilterValueLastName(String filterValue) {
               this.filterValueLastName = filterValue;
               }
              
              }
              


              Note: in my actual implementation, the properties bound in UserTableBinder are persisted to a DataBase, so the table will also maintain the UI state between user logins. Super handy for tables that users are working with all the time.

              Also, this is not a SFSB yet. I'll revisit this code when testing deployment to a cluster - probably going to require a @Stateful annotation and requisite @Local business interface.

              And modified table.xhtml with property bindings:

              <html xmlns="http://www.w3.org/1999/xhtml"
               xmlns:ui="http://java.sun.com/jsf/facelets"
               xmlns:h="http://java.sun.com/jsf/html"
               xmlns:f="http://java.sun.com/jsf/core"
               xmlns:a4j="http://richfaces.org/a4j"
               xmlns:rich="http://richfaces.org/rich"
               xmlns:s="http://jboss.com/products/seam/taglib">
              
               <h:form id="navUserListForm">
               <h:panelGrid columns="2" columnClasses="top" id="userPanelGrid">
               <rich:dataTable value="#{userManager.userList}" var="user"
               id="navUserListTable"
               rows="4" width="100%" columnClasses="top">
              
               <rich:column id="firstNameCol"
               filterBy="#{user.firstName}" filterValue="#{userTableBinder.filterValueFirstName}"
               sortBy="#{user.firstName}" sortOrder="#{userTableBinder.sortOrderFirstName}">
               <f:facet name="header">First Name</f:facet>
               <h:outputText value="#{user.firstName}" id="firstName" />
               </rich:column>
              
               <rich:column id="lastNameCol"
               filterBy="#{user.lastName}" filterValue="#{userTableBinder.filterValueLastName}"
               sortBy="#{user.lastName}" sortOrder="#{userTableBinder.sortOrderLastName}">
               <f:facet name="header">Last Name</f:facet>
               <h:outputText value="#{user.lastName}" id="lastName" />
               </rich:column>
              
              
               <f:facet name="footer" >
               <rich:datascroller id="navUserListPaginator" maxPages="10" ajaxSingle="true"
               page="#{userTableBinder.selectedPage}"
               renderIfSinglePage="false" />
               </f:facet>
              
               </rich:dataTable>
               </h:panelGrid>
               </h:form>
              
              </html>