6 Replies Latest reply on Jul 10, 2009 12:50 AM by Arbi Sookazian

    Seam component life cycle

    Arbi Sookazian Master

      Seam 2.0.2


      So I have a @DataModel and @Factory method combo to populate a rich:dataTable.  I'm noticing that I have to explicitly set the @DataModel List to null in order for the @Factory method to re-fire after I submit the form (and thus @End the conversation).


      Even if I use @End(beforeRedirect=true).  Why does this happen?  I even added a @Destroy method to my JavaBean to see when the Seam container is destroying the instance of the class.  So I'd expect that if the LRC is demoted to temp conversation by @End, and then the component is destroyed by the Seam container, then after or upon redirect to the same JSF page, an instance would be re-instantiated by JSF's managed bean facility (or perhaps Seam handles that now?) and the @Factory method would fire.


      so what's up here?  am I doing anything wrong?


      xhtml:


      <!DOCTYPE composition PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
                            "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
      
      <ui:composition 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"
           template="/templates/normal.xhtml">
           
      <ui:define name="body">
      
           <h:form>
                <rich:dataTable id="table1" value="#{apmdList_main}" var="row">          
                     <rich:column>
                          <f:facet name="header"><h:outputText value="delete"/></f:facet>                    
                          <h:selectBooleanCheckbox value="false">
                               <a4j:support event="onclick" action="#{myTest1.deleteRow(apmdList_main.getRowIndex())}"/>
                          </h:selectBooleanCheckbox>
                     </rich:column>          
                     <rich:column>
                          <f:facet name="header"><h:outputText value="ID"/></f:facet>                    
                          <h:outputText value="#{row.applicationMetaDataId}"/>
                     </rich:column>
                     <rich:column>
                          <f:facet name="header"><h:outputText value="BuildNumber"/></f:facet>                    
                          <h:inputText value="#{row.buildNumber}"/>
                     </rich:column>
                     <rich:column>
                          <f:facet name="header"><h:outputText value="applicationName"/></f:facet>                    
                          <h:outputText value="#{row.applicationName}"/>
                     </rich:column>
                     <rich:column>
                          <f:facet name="header"><h:outputText value="Added By UserID"/></f:facet>                    
                          <h:outputText value="#{row.addedByUserId}"/>
                     </rich:column>
                     <rich:column>
                          <f:facet name="header"><h:outputText value="Added Date"/></f:facet>                    
                          <h:outputText value="#{row.addedDate}"/>
                     </rich:column>
                     <!-- 
                     <rich:column>
                          <f:facet name="header"><h:outputText value="Delete Row"/></f:facet>                    
                          <h:commandButton value="delete" 
                                               action="#{myTest1.deleteRow(apmdList_main.getRowIndex())}"/> 
                     </rich:column>
                      -->
                </rich:dataTable>
                
                <rich:spacer height="20"/>
                
                <h:panelGrid columns="2">
                     <h:commandButton value="Add" action="#{myTest1.addRow}"/>
                     <h:commandButton value="Save" action="#{myTest1.save}"/>
                </h:panelGrid>
                
           </h:form>
           
           <rich:spacer height="20"/>
           
           <h:form>
                <rich:dataTable id="table2"
                                    value="#{apmdList_addRow}" 
                                   var="row" 
                                   rendered="#{apmdList_addRow.getRowCount() gt 0}">
                     
                     <rich:column>
                          <f:facet name="header"><h:outputText value="BuildNumber"/></f:facet>                    
                          <h:inputText value="#{row.buildNumber}"/>
                     </rich:column>
                     <rich:column>
                          <f:facet name="header"><h:outputText value="Application Name"/></f:facet>                    
                          <h:inputText value="#{row.applicationName}"/>
                     </rich:column>
                     <rich:column>
                          <f:facet name="header"><h:outputText value="Application Short Name"/></f:facet>                    
                          <h:inputText value="#{row.applicationShortName}"/>
                     </rich:column>               
                </rich:dataTable>
                
                <h:commandButton value="Save" 
                                     action="#{myTest1.saveAddedRow}"
                                     rendered="#{apmdList_addRow.getRowCount() gt 0}"/>
           </h:form>
           
      </ui:define>     
      </ui:composition>



      backing bean:


      @Name("myTest1")
      @Scope(ScopeType.CONVERSATION)
      public class TestApplicationMetaDataAction {
           
           @In //inject our Seam-managed Persistence Context (which is conversation-scoped and not tx or component-scoped like EJB3-managed PC) 
           private EntityManager entityManager;
           
           @Logger 
           private Log log;
           
           @DataModel
           private List<ApplicationMetaData> apmdList_main;
           
           @DataModelSelection("apmdList_main")
           private ApplicationMetaData selectedApplicationMetaData;
           
           @DataModel 
           private List<ApplicationMetaData> apmdList_addRow;
           
           private List<Integer> deleteList;
           
           /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^BEGIN BUSINESS METHODS^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
                
           public void addRow(){
                //showAddRow = true;
                ApplicationMetaData amd = new ApplicationMetaData();
                apmdList_addRow = new ArrayList<ApplicationMetaData>();
                apmdList_addRow.add(amd);     
           }
           
           @Begin(join=true, flushMode=FlushModeType.MANUAL)
           @Factory("apmdList_main")
           public void getDataTable(){
                
                if (apmdList_addRow != null && apmdList_addRow.size() > 0)
                     apmdList_addRow.clear();
                
                apmdList_main = entityManager.createQuery("from ApplicationMetaData").getResultList();
                
           }
           
           @End
           public void save(){
                //check deleteList first for removed entities...
                for (Integer i : deleteList){
                     ApplicationMetaData amd = apmdList_main.get(i);
                     entityManager.remove(amd);
                }
                //persist updates and deletes to db...
                entityManager.flush();
                apmdList_main = null;
           }
           
           @End
           public void saveAddedRow(){
                ApplicationMetaData amd = null;
                if (apmdList_addRow != null && apmdList_addRow.size() > 0)
                     amd = apmdList_addRow.get(0);
                amd.setAddedByUserId(0);
                amd.setUpdatedByUserId(0);
                amd.setAddedDate(new Date());
                amd.setUpdatedDate(new Date());
                
                entityManager.persist(amd);
                entityManager.flush();
                apmdList_main = null;
           }
           
           /*@End
           public void deleteRow(){
                entityManager.remove(selectedApplicationMetaData);
                //you could flush here if you wanted to, but you should probly prompt user first!
                //remember that if user has made any changes to the HtmlInputText fields, then those changes will be sync'd with table as well!
                //when you flush the SMPC, you flush *all* queued changes, whether they're inserts, updates or deletes!
                entityManager.flush();
                apmdList_main = null;
           }*/
           
           public void deleteRow(Integer row){
                if (deleteList == null)
                     deleteList = new ArrayList<Integer>();
                deleteList.add(row);
           }
           
           @Destroy
           public void destroy(){
                log.info("myTest1 component destroyed.");
           }
           /*^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^BEGIN GETTER/SETTER METHODS^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^*/
           
           
      }
      

        • 1. Re: Seam component life cycle
          Arbi Sookazian Master

          output from console:


          11:35:24,903 INFO  [STDOUT] Hibernate: 
              select
                  applicatio0_.ApplicationMetaDataID as Applicat1_2116_,
                  applicatio0_.AddedByUserID as AddedByU2_2116_,
                  applicatio0_.AddedDate as AddedDate2116_,
                  applicatio0_.ApplicationName as Applicat4_2116_,
                  applicatio0_.ApplicationShortName as Applicat5_2116_,
                  applicatio0_.BuildNumber as BuildNum6_2116_,
                  applicatio0_.UpdatedByUserID as UpdatedB7_2116_,
                  applicatio0_.UpdatedDate as UpdatedD8_2116_ 
              from
                  EquipmentRecovery.dbo.ApplicationMetaData applicatio0_
          11:35:28,450 INFO  [STDOUT] Hibernate: 
              delete 
              from
                  EquipmentRecovery.dbo.ApplicationMetaData 
              where
                  ApplicationMetaDataID=?
          11:35:29,372 INFO  [TestApplicationMetaDataAction] myTest1 component destroyed.



          so the select is from this in the @Factory method:


          apmdList_main = entityManager.createQuery("from ApplicationMetaData").getResultList();



          and the @Destroy method is called last after the LRC is demoted to temp conversation.


          so when the page redirects to itself (by default, as the methods return null), why doesn't the @Factory method get called again?  Only possibility is if the List associated with the @DataModel is not null but how can that happen if the object was destroyed by the Seam container?

          • 2. Re: Seam component life cycle
            Michael Wohlfart Expert

            Not sure if I fully understand your question or the Datamodel semantics, but afaik your @Factory method is not being called because the "apmdList_main" object still exists in the conversation context, so there is no need to create one.
            You can delete this object from the conversation scope with



            Contexts.getConversationContext().remove("apmdList_main");



            but there is probably a better way to handle updates like that

            • 3. Re: Seam component life cycle
              Arbi Sookazian Master

              But why does it still exist in the conversation context if the Seam component (in this case a JavaBean) has been destroyed and the LRC has been demoted to temporary conversation prior to destruction (using @End or @End(beforeRedirect=true))?


              It just doesn't make sense to me, this behavior...

              • 4. Re: Seam component life cycle
                Arbi Sookazian Master

                Ok, here is at least a partial explanation.  It has to do with using @End(beforeRedirect=true) properly.  Which means you must return a non-null value (e.g. String) and void does not work!


                // this works!
                     
                     /*@End(beforeRedirect=true)
                     public String save(){
                          //check deleteList first for removed entities...
                          for (Integer i : deleteList){
                               ApplicationMetaData amd = apmdList_main.get(i);
                               entityManager.remove(amd);
                          }
                          //persist updates and deletes to db...
                          entityManager.flush();
                          //apmdList_main = null;
                          return "/TestApplicationMetaData.xhtml";
                     }*/
                     
                     //this does not work!
                     
                     /*@End
                     public String save(){
                          //check deleteList first for removed entities...
                          for (Integer i : deleteList){
                               ApplicationMetaData amd = apmdList_main.get(i);
                               entityManager.remove(amd);
                          }
                          //persist updates and deletes to db...
                          entityManager.flush();
                          //apmdList_main = null;
                          return "/TestApplicationMetaData.xhtml";
                     }*/
                     
                     //this does not work if you do not configure properly with <redirect> to same xhtml in pages.xml
                     
                     @End(beforeRedirect=true)
                     public void save(){
                          //check deleteList first for removed entities...
                          for (Integer i : deleteList){
                               ApplicationMetaData amd = apmdList_main.get(i);
                               entityManager.remove(amd);
                          }
                          //persist updates and deletes to db...
                          entityManager.flush();
                          //apmdList_main = null;
                          
                     }



                pages.xml:


                <page view-id="/TestApplicationMetaData.xhtml">
                         <navigation from-action="#{myTest1.save}">
                              <redirect view-id="/TestApplicationMetaData.xhtml"/>
                         </navigation>
                    </page>   



                So if you want to return void, that's ok as long as you configure properly in pages.xml.


                But the bottom line is that if you just use @End instead of @End(beforeRedirect=true), the @Factory method will not get called after form submission and page refresh.


                From SiA:


                beforeRedirect 
                boolean 
                If set to true, instructs Seam to terminate the conversation prior to
                issuing a redirect. The default is to propagate the conversation
                across the redirect and terminate it once the response is complete.
                Default: false.



                So what happens in the case of @End is the LRC is demoted to temp conv but the conv is not destroyed until after the page has been refreshed.  I believe the use case here is to ensure that FacesMessages objects are available for error displaying using <rich:messages>, for example, when the page redirects to itself.


                The Seam ref doc should be refactored to include the fact that a String must be returned (or proper config in pages.xml for that page) in order for the conversation/component to be destroyed on time.


                beforeRedirect — by default, the conversation will not actually be destroyed until after
                any redirect has occurred. Setting beforeRedirect=true specifies that the conversation
                should be destroyed at the end of the current request, and that the redirect will be processed
                in a new temporary conversation context.



                There is nothing in the text above indicating that the method should return a String or pages.xml config required, correct?  This is obviously misleading...

                • 5. Re: Seam component life cycle
                  Michael Wohlfart Expert

                  cool,
                  thanks for sharing, I learned something :-)

                  • 6. Re: Seam component life cycle
                    Arbi Sookazian Master

                    I have created a new JIRA issue for this documentation problem:


                    https://jira.jboss.org/jira/browse/JBSEAM-4296