9 Replies Latest reply on Mar 1, 2008 4:11 PM by sleroux

    New Topic

    sleroux

      Hi there,


      First a word of warning: I'm new to Seam, EJB3, JSF, Hibernate, and so on... So please forgive me for this newbie question.


      Conversation, as I understand it


      I have some trouble with the @End annotation in my stateful session bean (conversation scope). As I have said, I'm new to Seam. But by reading the Seam Reference manual and previous posts on the old forum (most notably this one), it appears to be clear that calling a @Begin method will start (or join) a conversation, and calling a @End method will end the current conversation.


      I have (mis?)understood that the SFSB will then be destroyed by the application server after the @End method is completed. So the next time a @Begin method on that bean will be used in my JSF page, a new instance of the STSB will be created.


      Conversation in my sample application


      So, I've made a sample application to check that. Very simple, not much more that the Hello Seam world program on Michael Yuan's book.A conversation scoped entity bean (Book), and a conversation scoped stateful session bean (BookManagerAction).
      Here is the code:


      @Entity
      @Name("book")
      @Scope(ScopeType.CONVERSATION)
      public class Book implements Serializable {
          private static final long serialVersionUID = 1L;
      
          @SuppressWarnings("unused")
          @Id
          @Length(min=10,max=10)
          private String     ISBN;
      
          private String     title;
          
          public Book() {};
          
          public Book(String ISBN, String title) {
           this.ISBN = ISBN;
           this.title = title;
          }
          
          public String getISBN() {     return ISBN;    }
          
          public void setISBN(String isbn) {     ISBN = isbn;    }    
          public String getTitle() {     return title;    }
          public void setTitle(String title) { this.title = title; }
          
          @Override
          public String toString() {
           return title + "/" + ISBN;
          }
      }
      



      @Stateful
      @Scope(ScopeType.CONVERSATION)
      @Name("bookManager")
      public class BookManagerAction implements BookManager {
          @PersistenceContext(type=EXTENDED)
          private EntityManager      em;
          
          @In
          private Book          book;
          
          @In
          private FacesContext     facesContext;
      
      
          public BookManagerAction() {
           System.err.println("xxxxxxxxx PrintManagerAction()");
          }
      
          @SuppressWarnings("unchecked")
          @Override
          public List<Book>     allBooks() {
           return em.createQuery("select b from Book b")
                .getResultList();
          }
      
          @Override
          public String save() {
           try {
               em.persist(book);
               em.flush();
                  book = new Book();
           }
           catch(javax.persistence.EntityExistsException e) {
               FacesMessage message = new FacesMessage("Error: already exists. " + e.getMessage());
               facesContext.addMessage(null, message);
               
               return null;
           }
      
           return "done";
          }
      
          @Remove @Destroy
          public void destroy() {
           // nothing to do
          }
      }
      



      And the view:


      <?xml version="1.0" encoding="UTF-8" ?>
      <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
      <html xmlns="http://www.w3.org/1999/xhtml"
            xmlns:ui="http://java.sun.com/jsf/facelets"
            xmlns:f="http://java.sun.com/jsf/core"
            xmlns:h="http://java.sun.com/jsf/html"
            xmlns:s="http://jboss.com/products/seam/taglib">
      <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
      <title>New book</title>
      </head>
      <body>
          <h1>New book</h1>
          <p>This page allow to add a new book to the database</p>
          <h2>Create</h2> 
          <h:messages />
          <form jsfc="h:form" id="Book">
          <s:validateAll>
              <p><label for="title">Title:</label><input id="title" type="text" jsfc="h:inputText" value="#{book.title}" /><h:message for="title" /></p>
              <p><label for="isbn">ISBN-10:</label><input id="isbn" type="text" jsfc="h:inputText" value="#{book.ISBN}" /><h:message for="isbn" /></p>
      
              <p><input type="submit" jsfc="h:commandButton" value="Submit" action="#{bookManager.save()}" /></p>
          </s:validateAll>
          </form>
          
          <h2>Books in the database</h2>
          <h:form> 
          <h:dataTable value="#{bookManager.allBooks()}" var="book">
              <h:column>#{book.title}</h:column>
              <h:column>#{book.ISBN}</h:column>
          </h:dataTable>
          </h:form>
      </body>
      </html>
      



      Notice that the application is deployed as an EAR, and that I do not use any page.xml.


      At this point, it works fine. When running the application, I could see that a new STSB instance is created after each user action.


      So, I've decided to use @Begin and @End annotation: allBooks will start the conversation, and save will end it:


          @Begin(join=true)
          @SuppressWarnings("unchecked")
          @Override
          public List<Book>     allBooks() {
      ...
          }
      



          @End
          @Override
          public String save() {
      ...
          }
      



      To my mind:



      • a new conversation will begin when the user will  load the page.

      • The bean will join that conversation when allBooks will be called.

      • In case of input error (wrong format for the ISBN), the page will be redisplayed, using the same conversation and the same STSB.

      • And finally, the conversation will end at save time, and a new one will begin when the result page will be rendered, so the process will continue.



      But, it doesn't work like that! It appears to me that the same bean will be used even after a save, just like if there wasn't any @End annotation ???


      I cannot figure exactly what's wrong here. Maybe it is due to the fact that I use the same page to end and start a new conversation using the same STSB?
      Or maybe it is due to something I read in a previous post (an that I don't really understand)



      When an @End method is encountered, any long-running conversation context is demoted to a temporary conversation

      So, if you have any idea...


      Thanks in advance,
      Sylvain.

        • 1. Re: New Topic
          sleroux

          Sorry for the title of this topic: It should have been something like @End does not close a conversation - but I've jumped too quickly on the Save button... If anyone knows how to change that...


          Sylvain

          • 2. Re: New Topic
            baz

            What happens when you press the save button? Your action returns done. Are you having a navigation rule for this outcome?


            IMHO, if you render the same page again, after clicking the save-button, the conversations is not ended because you have annotated the method allBooks with @Begin.


            Try to render a different page after clicking save and see what happens.


            Ciao,

            Carsten

            • 3. Re: New Topic
              sleroux

              First of all, thanks Carsten for your answer.


              You're right: I render the same page after a click on the save button. My idea was to have some kind of quick add feature on the list page.


              Nevertheless, I've tried as you suggested to add a confirm page:


              <?xml version="1.0" encoding="UTF-8" ?>
              <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
              <html xmlns="http://www.w3.org/1999/xhtml"
                    xmlns:ui="http://java.sun.com/jsf/facelets"
                    xmlns:f="http://java.sun.com/jsf/core"
                    xmlns:h="http://java.sun.com/jsf/html"
                    xmlns:s="http://jboss.com/products/seam/taglib">
              <head>
              <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
              <title>New book</title>
              </head>
              <body>
                  <h1>New book</h1>
                  <p>Change confirmed</p>
                  <p><a href="new-book.seam">go back to list</a></p>
              </body>
              </html>
              



              And the corresponding navigation rule:


              <?xml version="1.0" encoding="UTF-8"?>
              <!DOCTYPE faces-config 
                  PUBLIC "-//Sun Microsystems, Inc.//DTD JavaServer Faces Config 1.0//EN"
                  "http://java.sun.com/dtd/web-facesconfig_1_0.dtd">
                  
              <faces-config>
              
                <navigation-rule>
                  <from-view-id>*</from-view-id>
                  <navigation-case>
                    <from-outcome>done</from-outcome>
                    <to-view-id>/confirm.xhtml</to-view-id>
                    <redirect />
                  </navigation-case>
                </navigation-rule>
              
              </faces-config>
              



              That time it works just fine.


              But, I have some trouble to understand why it is not working with the one page does all version?


              As far as I understand, in the one page version, when the user click on the save button:



              1. The entity bean is updated with the values entered by the user

              2. The save method is called on the bookManager STSB. This terminates the conversation, and removes the STSB.

              3. Then the response in rendered. So, when the #{bookManager.allBooks} EL is reached, since there is no longer any bookManager in the conversation context, a new one is created.



              Obviously, this is not the way it's done! So the right questions here are:



              • When a STSB is removed from the context when a @End method is called? After the request is handled or after the response is rendered? (my guess now is the second case;)

              • What appends if I call a method from a JSF page on a bean after an @End method on that bean has already be called (especially if I call a @Begin after a @End method)? Does this cancel the end of the conversation?




              Sylvain


              • 4. Re: New Topic
                baz

                Hello,


                Sorry for the delay. There is this Jira Issue.


                IMDO, a conversation is never terminated. It is created when a request begins and it is destroyed when the response is rendered.


                Please read the conversation chapter of the seam documentation. Espesially chapter 6.1 and you will understand the behaviour.



                There is the attribute beforeRedirect which changes the described behaviour. It ends the converstaion before rendering the page.


                @End(beforeRedirect="true")


                Try it out after reading the above mentioned documentation:-)


                • 5. Re: New Topic
                  sleroux

                  Hi Carsten,


                  Thanx for the answer!


                  For some reason, I understood quite literally from the doc that the context will be destroyed just after the request is handled. Here is the relevant extract from Seam Tutorial - 1.6.3: Understanding Seam conversations



                  The  @End   annotation specifies that the annotated method ends the current long-running conversation, so the current conversation context will be destroyed at the end of the request.

                  But, in fact, as I could have read it from Michael Yuan's book (p100) the conversation is destroyed after the response page is fully rendered.


                  By the way, I've missed the beforeRedirect attribute too... that does exactly what I wanted:



                  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.



                  Seems I have to read the doc more carefully!-)
                  Nevertheless, things are getting much more clear now!

                  Sylvain

                  • 6. Re: New Topic
                    damianharvey.damianharvey.gmail.com

                    You can always manually remove your Bean from the context eg: Contexts.removeFromAllContexts("myBean");


                    I find that this is often useful if you want to remain on the same page.


                    Cheers,


                    Damian.

                    • 7. Re: New Topic
                      gavin.king

                      Please don't forget to rate Carsten's posts, thanks :-)

                      • 8. Re: New Topic
                        keithnaas

                        Great, I explained the promotion/demotion of conversations yesterday and no one gave me a rating..heh heh


                        Of course, it looks like I was typing the response after shooting a gallon of coffee.


                        • 9. Re: New Topic
                          sleroux

                          Sorry Keith, I missed that from an other post:



                          [...] Ending the conversation doesn't immediately end it. Instead it demotes it to a temporary conversation. In the case of JSF, this temporary conversation is then over when the render response phase is completed.

                          It is an important aspect of conversation life cycle that explain the behavior I described in my original post.


                          Well, now that I know that, it's quite logical: @Begin promote a temporary conversation to a long-running conversation and @End does the exact opposite: demote from a long-running conversation to a temporary conversation.


                          Moreover, this is the answer to my question about calling a @Begin method on a component after a call to an @End method: the conversation is just demoted to a temporary conversation, then (re)promoted to a long-running conversation. And that conversation just continue using the same component.


                          Things are getting much more clear now, thanks

                          Sylvain