9 Replies Latest reply on Mar 11, 2008 12:48 PM by Stan Silvert

    ajaxSubmit produces duplicate id exception

    Jason Hopkins Newbie

      Hi,

      I've got a rich:dataTable with a button in the header:

      
      ...
      ...
       <ui:define name="body" >
       <h:form id="clientListForm" style="padding: 5px">
       <rich:dataTable
       id="clientTable"
       value="#{ClientManagementSession.availableClientsModel}"
       var="client" width="100%">
       <f:facet name="header">
       <h:panelGrid columns="2" columnClasses="nintyFiveWidth bold leftAlign, fiveWidth">
       <h:outputText value="#{BackofficeSession.langMap.configured_clients}" />
       <a4j:commandButton id="openCreateClientDialogueButton"
       value="#{BackofficeSession.langMap.create_new_client}"
       eventsQueue="openDialogueQueue"
       action="#{BackofficeSession.showModalPanel}"
       reRender="createClientDialoguePanel" />
       </h:panelGrid>
       </f:facet>
      ...
      ...
      
      


      And I have the following test:

      
       // just check that the create client dialogue is not visible
       Boolean dialogueOpen = (Boolean)
       server.getManagedBeanValue("#{BackofficeSession.showModalPanel}");
       assertFalse("Show modal dialogue should be false", dialogueOpen.booleanValue());
       // and then by checking the dialogue component itself.
       UIModalPanel modalPanel = (UIModalPanel) server.findComponent("createClientDialogue");
       assertFalse("The create client dialogue is visiable when it should not be", modalPanel.isShowWhenRendered());
      
       try {
       // open the create client dialogue
       richClient.ajaxSubmit("openCreateClientDialogueButton");
       } catch (SAXException ex) {
       fail("Recieved a SaxException while trying to open create client dialogue: " + ex.getMessage());
       } catch (IOException ex) {
       fail("Recieved an IOException while trying to open create client dialogue: " + ex.getMessage());
       }
      
      


      Which works fine when there are zero or one rows in the data table, as soon as there are two rows I get the following exception:

      ...
      <error message="openCreateClientDialogueButton matches more than one JSF component ID. Use a more specific ID suffix. Suffix matches: clientListForm:clientTable:openCreateClientDialogueButton, clientListForm:clientTable:openCreateClientDialogueButton" type="org.jboss.jsfunit.facade.DuplicateClientIDException">
      ...
      


      Anyone have any idea's how I can get around this?

        • 1. Re: ajaxSubmit produces duplicate id exception
          Jason Hopkins Newbie

          Sorry, using JSFUnit Beta 1 with JBoss 4.2.2GA and RichFaces 3.1.4GA

          • 2. Re: ajaxSubmit produces duplicate id exception
            Stan Silvert Master

            This was a bug. Thanks for finding it. I've already reproduced it and committed a fix:
            http://jira.jboss.com/jira/browse/JSFUNIT-78

            You will need to checkout and build JSFUnit from SVN to get the fix:
            http://labs.jboss.com/jsfunit/building-jsfunit.html

            Regards,

            Stan

            • 3. Re: ajaxSubmit produces duplicate id exception
              Jason Hopkins Newbie

              Thanks for the quick response Stan,
              I'll give that a go in the morning.
              Your a star :)

              • 4. Re: ajaxSubmit produces duplicate id exception
                Jason Hopkins Newbie

                Hi Stan,

                I've just tried the latest snapshot and I get a new error now :(

                11:33:51,072 ERROR [[Faces Servlet]] Servlet.service() for servlet Faces Servlet threw exception
                java.lang.IllegalStateException
                 at com.sun.faces.context.FacesContextImpl.assertNotReleased(FacesContextImpl.java:428)
                 at com.sun.faces.context.FacesContextImpl.getExternalContext(FacesContextImpl.java:149)
                 at org.jboss.jsfunit.context.JSFUnitFacesContext.getExternalContext(JSFUnitFacesContext.java:114)
                 at org.jboss.jsfunit.context.JSFUnitFacesContext.release(JSFUnitFacesContext.java:170)
                 at javax.faces.webapp.FacesServlet.service(FacesServlet.java:262)
                 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:290)
                 at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
                 at org.ajax4jsf.webapp.BaseXMLFilter.doXmlFilter(BaseXMLFilter.java:141)
                 at org.ajax4jsf.webapp.BaseFilter.doFilter(BaseFilter.java:281)
                 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:230)
                 at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:175)
                 at org.jboss.web.tomcat.security.SecurityAssociationValve.invoke(SecurityAssociationValve.java:179)
                 at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:524)
                 at org.jboss.web.tomcat.security.JaccContextValve.invoke(JaccContextValve.java:84)
                 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:157)
                 at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:109)
                 at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:262)
                 at org.apache.coyote.http11.Http11Processor.process(Http11Processor.java:844)
                 at org.apache.coyote.http11.Http11Protocol$Http11ConnectionHandler.process(Http11Protocol.java:583)
                 at org.apache.tomcat.util.net.JIoEndpoint$Worker.run(JIoEndpoint.java:446)
                 at java.lang.Thread.run(Thread.java:619)
                11:33:51,175 ERROR [[ServletTestRunner]] Servlet.service() for servlet ServletTestRunner threw exception
                java.lang.IllegalStateException
                 at com.sun.faces.context.FacesContextImpl.assertNotReleased(FacesContextImpl.java:428)
                 at com.sun.faces.context.FacesContextImpl.getExternalContext(FacesContextImpl.java:149)
                 at org.jboss.jsfunit.context.JSFUnitFacesContext.release(JSFUnitFacesContext.java:170)
                 at org.jboss.jsfunit.context.JSFUnitFacesContext.valueUnbound(JSFUnitFacesContext.java:201)
                 at org.apache.catalina.session.StandardSession.removeAttributeInternal(StandardSession.java:1649)
                 at org.apache.catalina.session.StandardSession.removeAttribute(StandardSession.java:1207)
                 at org.apache.catalina.session.StandardSession.removeAttribute(StandardSession.java:1179)
                 at org.apache.catalina.session.StandardSessionFacade.removeAttribute(StandardSessionFacade.java:140)
                 at org.jboss.jsfunit.framework.JSFUnitFilter.doFilter(JSFUnitFilter.java:127)
                 at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:235)
                ...
                ...
                


                The application I'm working on uses form authentication set up in the following manor in the web.xml:

                 <security-constraint>
                 <display-name>Must be logged in</display-name>
                 <web-resource-collection>
                 <web-resource-name>All Pages</web-resource-name>
                 <description>Secure all pages in the application</description>
                 <url-pattern>*.jspx</url-pattern>
                 <http-method>GET</http-method>
                 <http-method>POST</http-method>
                 <http-method>HEAD</http-method>
                 <http-method>PUT</http-method>
                 <http-method>OPTIONS</http-method>
                 <http-method>TRACE</http-method>
                 <http-method>DELETE</http-method>
                 </web-resource-collection>
                 <auth-constraint>
                 <description>Must have login role to view any pages</description>
                 <role-name>log_in</role-name>
                 </auth-constraint>
                 </security-constraint>
                
                 <login-config>
                 <auth-method>FORM</auth-method>
                 <realm-name>xxxxx</realm-name>
                 <form-login-config>
                 <form-login-page>/login.jspx</form-login-page>
                 <form-error-page>/loginError.html</form-error-page>
                 </form-login-config>
                 </login-config>
                
                 <security-role>
                 <description>Must be logged in to view any pages.</description>
                 <role-name>log_in</role-name>
                 </security-role>
                


                And in the tests I use the following code to ensure that the test authenticates with the system:

                 public static JSFClientSession getClientSession(String url)
                 throws MalformedURLException, IOException, SAXException {
                 JSFClientSession client = new JSFClientSession(url);
                 JSFServerSession server = new JSFServerSession(client);
                 if (server.getCurrentViewID().endsWith("login.xhtml")) {
                 // we've been redirected, need to login
                 WebForm loginForm = client.getWebResponse().getFormWithID("loginform");
                 if (loginForm != null) {
                 loginForm.setParameter("j_username", "regaladmin");
                 loginForm.setParameter("j_password", "regaladmin");
                 SubmitButton loginButton = loginForm.getSubmitButton("login");
                 if (loginButton != null) {
                 loginButton.click();
                 } else {
                 throw new IOException("Test for " + url + " was redirected to the login " +
                 "page but we were unable to locate the login button");
                 }
                 FacesContext facesContext = FacesContextBridge.getCurrentInstance();
                 UIViewRoot root = facesContext.getViewRoot();
                 String expectedViewId = url.replace(".jspx", ".xhtml");
                 if (!expectedViewId.equals(root.getViewId())) {
                 throw new IOException("Failed to login to application, last view id was " + root.getViewId());
                 }
                 // no we are logged in, re-request the client to refresh it
                 client = new JSFClientSession(url);
                 } else {
                 throw new IOException("Test for " + url + " was redirected to the login " +
                 "page but we were unable to locate the login form");
                 }
                 }
                 return client;
                 }
                


                I've stuck a debugger on it an it blows up when trying to create the JSFClientSession (JSFClientSession client = new JSFClientSession(url);).

                This was working in the beta-1 code, but not the snapshot.

                • 5. Re: ajaxSubmit produces duplicate id exception
                  Jason Hopkins Newbie

                  Sorry, this is my fault, still had the old beta-1 jar's in the deployment DOH.

                  Thanks again :)

                  • 6. Re: ajaxSubmit produces duplicate id exception
                    Jason Hopkins Newbie

                    I think I may have found another problem :( again with the button in the header. As before I have this set up in the jsf xml:

                    ...
                    ...
                     <ui:define name="body" >
                     <h:form id="clientListForm" style="padding: 5px">
                     <rich:dataTable
                     id="clientTable"
                     value="#{ClientManagementSession.availableClientsModel}"
                     var="client" width="100%">
                     <f:facet name="header">
                     <h:panelGrid columns="2" columnClasses="nintyFiveWidth bold leftAlign, fiveWidth">
                     <h:outputText value="#{BackofficeSession.langMap.configured_clients}" />
                     <a4j:commandButton id="openCreateClientDialogueButton"
                     value="#{BackofficeSession.langMap.create_new_client}"
                     eventsQueue="openDialogueQueue"
                     action="#{BackofficeSession.showModalPanel}"
                     reRender="createClientDialoguePanel" />
                     </h:panelGrid>
                     </f:facet>
                     <rich:column>
                     <f:facet name="header">
                     <h:outputText value="#{BackofficeSession.langMap.client_name}"/>
                     </f:facet>
                     <h:outputText value="#{client.clientName}"/>
                     </rich:column>
                    ...
                    ...
                    
                    


                    And once the JSFClientSession and JSFServerSession are set up the findComponent of server will not find the button component:

                    server.findComponent("openCreateClientDialogueButton")
                    


                    throws a ComponentIDNotFoundException. I've also tried the full client id of the component.

                    Strangely this code works:

                    server.findComponent("clientTable").findComponent("openCreateClientDialogueButton")
                    


                    But the upshot is that when I try and do and ajaxSubmit for the button the ComponentIDNotFoundException is thrown.


                    • 7. Re: ajaxSubmit produces duplicate id exception
                      Jason Hopkins Newbie

                      Sorry, here is the stack trace:

                      −
                       <testsuites>
                      −
                       <testsuite name="com.uk.realistic.regal.web.tests.systemmanagement.ListClientTest" tests="2" failures="0" errors="1" time="290.282">
                      −
                       <testcase name="testCreateClient" time="288.073">
                      −
                       <error message="No component ID was found for clientListForm:clientTable:openCreateClientDialogueButton" type="org.jboss.jsfunit.facade.ComponentIDNotFoundException">
                      org.jboss.jsfunit.facade.ComponentIDNotFoundException: No component ID was found for clientListForm:clientTable:openCreateClientDialogueButton
                       at org.jboss.jsfunit.facade.ClientIDs.findClientID(ClientIDs.java:170)
                       at org.jboss.jsfunit.richfaces.Ajax4jsfClient.setRowIndicies(Ajax4jsfClient.java:92)
                       at org.jboss.jsfunit.richfaces.Ajax4jsfClient.ajaxSubmit(Ajax4jsfClient.java:168)
                       at org.jboss.jsfunit.richfaces.Ajax4jsfClient.ajaxSubmit(Ajax4jsfClient.java:152)
                       at com.uk.realistic.regal.web.tests.systemmanagement.ListClientTest.testCreateClient(ListClientTest.java:72)
                       at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                       ...
                       ....
                      


                      • 8. Re: ajaxSubmit produces duplicate id exception
                        Jason Hopkins Newbie

                        Looks like this is being caused by the fact there are no rows in the table and the addUIData method of ClientIDs only adds children when the component has a rowCount:

                         private void addUIData(UIData component, FacesContext facesContext)
                         {
                         String parentClientID = component.getClientId(facesContext);
                         // TODO: find out if headers and footers are found
                         for (int i=0; i < component.getRowCount(); i++)
                         {
                         component.setRowIndex(i);
                         addAncestors(parentClientID, component.getClientId(facesContext));
                         for (Iterator facetsAndChildren = component.getFacetsAndChildren(); facetsAndChildren.hasNext();)
                         {
                         UIComponent facetOrChild = (UIComponent)facetsAndChildren.next();
                         addAncestors(component, facetOrChild, facesContext);
                         addAllIDs(facetOrChild, facesContext);
                         }
                         }
                        
                         component.setRowIndex(-1);
                         }
                        
                        


                        I fixed it by processing the UIData's facets and children seprately.

                         private void addUIData(UIData component, FacesContext facesContext) {
                         String parentClientID = component.getClientId(facesContext);
                        
                         // TODO: find out if headers and footers are found
                        
                         int rowCount = component.getRowCount();
                        
                         // process the facets
                         for (UIComponent facet : component.getFacets().values()) {
                         addAncestors(component, facet, facesContext);
                         addAllIDs(facet, facesContext);
                         }
                        
                         // and process any children for each row of the data component
                         for (int i = 0; i < rowCount; i++) {
                         component.setRowIndex(i);
                         addAncestors(parentClientID, component.getClientId(facesContext));
                         for (UIComponent child : component.getChildren()) {
                         addAncestors(component, child, facesContext);
                         addAllIDs(child, facesContext);
                         }
                         }
                        
                         component.setRowIndex(-1);
                         }
                        


                        While this does fix my little problem, I don't know what impacts this will have on other UIData components.

                        • 9. Re: ajaxSubmit produces duplicate id exception
                          Stan Silvert Master

                          Thanks for the analysis. I think your code is probably correct. I'll take a closer look very soon. Here is the jira for tracking purposes:
                          http://jira.jboss.com/jira/browse/JSFUNIT-80

                          Stan