7 Replies Latest reply on Mar 24, 2008 6:33 PM by bogdanminciu.bogdan.minciu.yahoo.com

    Conversation life-cycle question

    bogdanminciu.bogdan.minciu.yahoo.com

      Hello,


      I'm not able to access properties of a conversation scoped component. Here is my component:


      @Stateful
      @Name("blogEntryList")
      @Scope(ScopeType.CONVERSATION)
      @Restrict("#{identity.loggedIn}")
      public class BlogEntryListBean implements LocalBlogEntryList {
      
      ...
          @In(required = false)
          @Out(required = false)
          private BlogEntry currentBlogEntry;
      
          @DataModel
          private List<BlogEntry> blogEntries;
      
          public BlogEntry getCurrentBlogEntry() {
              return currentBlogEntry;
          }
      
          public void setCurrentBlogEntry(BlogEntry blogEntry) {
              log.info("got blogEntry= #0", blogEntry);
              this.currentBlogEntry = blogEntry;
          }
      
          public List<BlogEntry> getBlogEntries() {
              return blogEntries;
          }
      
          public void setBlogEntries(List<BlogEntry> blogEntries) {
              this.blogEntries = blogEntries;
          }
      ...
      }
      



      The setter setCurrentBlogEntry() is called from the blogEntryList.xhtml page:


      <h:dataTable id="blogEtriesDataTable" value="#{blogEntries}" var="blogEntry" rendered="#{blogEntryList.resultListSize > 0}">
          <h:column>
              <f:facet name="header">ID</f:facet>
              #{blogEntry.id}
          </h:column>
          <h:column>
              <f:facet name="header">Title</f:facet>
              #{blogEntry.title}
          </h:column>
          <h:column>
              <f:facet name="header">Actions</f:facet>
              <s:link id="blogEntryTest"
                      value="Test"
                      action="#{blogEntryList.setCurrentBlogEntry(blogEntry)}"
                      view="/xcms/blog/blogEntryList.xhtml" />&#160;
          </h:column>
      </h:dataTable>
      



      When i click on the link, the setCurrentBlogEntry() method is called, but the output reports null value assigned:


      10:19:00,015 INFO  [BlogEntryListBean] got blogEntry= null
      



      I guess this has something to do with the life-cycle of the conversation scoped components, but since I am very new to seam and to the notion of conversation, I wasn't able to understand what should I do to keep the component alive within several page calls.


      I even tried to promote my conversation to a long-running one by marking one @Create method of this component with @Begin(join=true), with no success.


      On the other hand, if I try to scope the component to SESSION, things are working OK and the blogEntry variable is caught correctly.


      Where am I going wrong?


      Thank you for any hint.
      Bogdan.

        • 1. Re: Conversation life-cycle question
          msystems

          I'm pretty sure it will work just fine if you choose to use <h:commandLink> instead of <s:link>.


          But I can't answer why it doesn't work with the <s:link> !

          • 3. Re: Conversation life-cycle question
            bogdanminciu.bogdan.minciu.yahoo.com

            Thank you Kenneth for the reply!


            As I read through the docs you pointed at, I have found this:



            You must ensure that the parameters are available not only when the page is rendered, but also when it is submittedIf the arguments can not be resolved when the page is submitted the action method will be called with null arguments!

            and this:



            Use inside iterative components — Components like <c:forEach /> and <ui:repeat /> iterate over a List or array, exposing each item in the list to nested components. This works great if you are selecting a row using a <h:commandButton /> or <h:commandLink />:
            @Factory("items")
            public List<Item> getItems() {
               return entityManager.createQuery("select ...").getResultList();
            }
            



            <h:dataTable value="#{items}" var="item">
               <h:column>
                  <h:commandLink value="Select #{item.name}" action="#{itemSelector.select(item})" />
               </h:column>
            </h:dataTable>
            



            However if you want to use <s:link /> or <s:button /> you must expose the items as a DataModel, and use a <dataTable /> (or equivalent from a component set like <rich:dataTable /> ). Neither <s:link /> or <s:button /> submit the form (and therefore produce a bookmarkable link) so a 'magic' parameter is needed to recreate the item when the action method is called. This magic parameter can only be added when a data table backed by a DataModel is used.

            Well, as far as you can see in my backing bean (BlogEntryListBean), my data (blogEntries) is exposed as a DataModel and is rendered inside an <h:dataTable>. Still with no success in using <s:link/>.


            Anyway, I tried using <h:commandLink /> as you suggested, and the method is called as expected, and the data is caught properly (not null). The only problem is that it refreshes the current page, but I guess that with some simple pageflow in pages.xml I can solve this.


            But I want to benefit of the many advantages that s:link offers.


            Any other opinions or suggestions?

            • 4. Re: Conversation life-cycle question
              bogdanminciu.bogdan.minciu.yahoo.com

              Anyway, as far as I see on the booking example, even Gavin decided to hold his listBeans scoped to session, so for the moment I will put this workaround to work.


              @Stateful
              @Scope(SESSION)
              @Name("bookingList")
              @Restrict("#{identity.loggedIn}")
              @TransactionAttribute(REQUIRES_NEW)
              public class BookingListAction implements BookingList, Serializable
              {
              ...
                 @DataModel
                 private List<Booking> bookings;
                 @DataModelSelection 
                 private Booking booking;
              ...
              }
              



              Thank you again for the reply,


              Bogdan.

              • 5. Re: Conversation life-cycle question
                msznapka.martin.sznapka.gmail.com

                Hi,


                as Kenneth mentioned, the problem is


                <s:link action="#{blogEntryList.setCurrentBlogEntry(blogEntry)}"/>
                



                I had same problem in past.


                The result is, that h:commandLink support action method parameters in EL but the s:link does NOT.


                The workaround is simple: use f:param


                <s:link action="#{blogEntryList.setCurrentBlogEntry}">
                  <f:param name="id" value="#{blogEntry.id}">
                </s:link>
                

                • 6. Re: Conversation life-cycle question
                  bogdanminciu.bogdan.minciu.yahoo.com

                  Thanks for the reply Martin.


                  I got the picture with the difference between s:link and h:commandLink. Your workaround is quite nice and I bet it solves the cases where one can't expose components to session.


                  As far as I understood, the problem is not that s:link doesn't support parameters inside JBoss EL (because it does: <s:link id="viewHotel" value="View Hotel" action="#{hotelBooking.selectHotel(hot)}"/>), but that objects passed in there are not properly kept alive in the conversation. My workaround for this was to keep lists in SESSION, and start conversations only after selecting one. This approach was suggested by this:



                  You must ensure that the parameters are available not only when the page is rendered, but also when it is submittedIf the arguments can not be resolved when the page is submitted the action method will be called with null arguments!

                  (From the link provided by Kenneth)


                  So now, my link looks like this:


                  <h:dataTable id="blogEtriesDataTable" value="#{blogEntries}" var="blogEntry">
                  <s:link id="blogEntryEdit"
                          value="Edit"
                          action="#{blogEntryView.editBlogEntry(blogEntry)}"
                          view="/xcms/blog/blogEntryEdit.xhtml" />
                  </h:dataTable>
                  



                  blogEntries is scoped to SESSION and blogEntryView.editBlogEntry(blogEntry) starts a new CONVERSATION. This way, data is available both when the page is rendered and when the data is submitted. I haven't lost the advantage of conversations because several tabs can edit a different blogEntry, since blogEntryView.editBlogEntry(blogEntry) starts a new CONVERSATION.


                  Since I am very new to Seam I will be very grateful if someone would be kind enough to tell his opinion about my approach.


                  Thank you,


                  Bogdan.

                  • 7. Re: Conversation life-cycle question
                    bogdanminciu.bogdan.minciu.yahoo.com

                    Martin,


                    I encountered the case where I need your workaround with <f:param>. And I did something like this:


                    <s:link action="#{blogEntryList.setBlogEntryById}">
                      <f:param name="id" value="#{blogEntry.id}">
                    </s:link>
                    



                    which is backed by this method from the bean:


                    ...
                    @RaiseEvent("com.brit.xcms.cms.Page.pageListModified")
                    public void setBlogEntryById(Long blogEntryId) {
                        this.blogEntry = blogEntryDAO.findBlogEntryById(blogEntryId);
                        find();
                    }
                    ...
                    



                    The link comes out right, but clicking it generates this exception:


                    javax.el.MethodNotFoundException: Method not found: CategoryListBean:a002r-j4glz4-fe78zera-1-fe7ap87m-34.setParentById()
                            at org.jboss.el.util.ReflectionUtil.getMethod(ReflectionUtil.java:252)
                            at org.jboss.el.util.ReflectionUtil.invokeMethod(ReflectionUtil.java:341)
                    ...
                    



                    which I kind of understand, because I think EL needs to build through reflection the actual bean method that needs to be called. As far as I know reflection need the full method prototype, including parameter types. From my EL: blogEntryList.setBlogEntryById, the parameters are void, so it expects the setBlogEntry() method, and not setBlogEntry(Long) as I need.


                    How can I specify the actual method that I need to call?


                    Thanks,
                    Bogdan.