2 Replies Latest reply on Mar 20, 2008 6:28 PM by Bogdan Minciu

    Conversation and ajax4jsf problem

    Bogdan Minciu Newbie

      Hello guys and girls,


      I have this bean that is backing a blog entry add form:


      @Stateful
      @Name("blogEntryCreate")
      @Scope(ScopeType.CONVERSATION)
      @Restrict("#{identity.loggedIn}")
      public class BlogEntryCreateBean implements LocalBlogEntryCreate {
      
          private String body;
      
          @In(create=true)
          @Out
          private List<Tag> attachedTags;
      
          @Logger
          private Log log;
      
          @Remove
          public void destroy() {
          }
      
          @Factory(value="attachedTags", autoCreate=true)
          public List<Tag> initTags() {
              log.info("initAttachedTags for #0", this);
              return new ArrayList<Tag>();
          }
      
          public String getBody() {
              return body;
          }
      
          public void setBody(String body) {
              this.body = body;
          }
      
          public void attachTag(Tag tag) {
              log.info("BlogEntryCreateBean.attachTag: attaching tag: #0", tag);
              if (!getAttachedTags().contains(tag)) {
                  getAttachedTags().add(tag);
                  log.info("#0 attached", tag);
              }
          }
      
          public void detachTag(Tag tag) {
              log.info("BlogEntryCreateBean.detachTag: detaching tag: #0", tag);
              if (getAttachedTags().contains(tag)) {
                  getAttachedTags().remove(tag);
                  log.info("#0 detached", tag);
              }
          }
      
      
          public List<Tag> getAttachedTags() {
              return attachedTags;
          }
      
          public void setAttachedTags(List<Tag> attachedTags) {
              this.attachedTags = attachedTags;
          }
      }
      



      As you can see it is stateful and scoped to CONVERSATION. It works perfectly when adding the body of the blog entry, the new blog entry is saved into the database.


      The problem is that this bean is created for each new tag added.


      The xhtml backed by this bean is here:


      ...
      <a:outputPanel id="availableTags">
                  <div class="section">
                      Available tags(click to attach...):
                      <h:outputText value="No Tags Found" rendered="#{tags==null or tagList.tags.size==0}"/>
                      <h:form id="attachTag">
                          <ui:repeat id="repeatAvailableTags" value="#{tagList.tags}" var="tag" rendered="#{tags!=null and tagList.tags.size!=0}">
                              <a:commandLink id="attachTagA" action="#{blogEntryCreate.attachTag(tag)}" reRender="addedTags" value="#{tag.text}"/>&#160;
                          </ui:repeat>
                      </h:form>
                  </div>
              </a:outputPanel>
              
              <a:outputPanel id="addedTags">
                  <div class="section">
                      Attached tags(click to detach...):
                      <h:outputText value="No Tags Found" rendered="#{blogEntryCreate.attachedTags==null or blogEntryCreate.attachedTags.size()==0}"/>
                      <h:form id="detachTag">
                          <ui:repeat id="repeatAddedTags" value="#{blogEntryCreate.attachedTags}" var="tag">
                              <a:commandLink id="detachTagA" action="#{blogEntryCreate.detachTag(tag)}" reRender="addedTags" value="#{tag.text}"/>&#160;
                          </ui:repeat>
                      </h:form>
                  </div>
              </a:outputPanel>
      ...
      



      As you can see i handle attaching/detaching of tags via ajax. The method calls are as expected, but the problem is that for each attachTag() method call, a new BlogEntryCreateBean instance is created. This is the output for the log:


      14:44:24,203 INFO  [BlogEntryCreateBean] initAttachedTags for com.brit.xcms.blog.BlogEntryCreateBean@75b3b6
      14:44:33,703 INFO  [BlogEntryCreateBean] initAttachedTags for com.brit.xcms.blog.BlogEntryCreateBean@9112ad
      14:44:33,750 INFO  [BlogEntryCreateBean] BlogEntryCreateBean.attachTag: attaching tag: hardware
      14:44:33,750 INFO  [BlogEntryCreateBean] hardware attached
      14:44:34,765 INFO  [BlogEntryCreateBean] initAttachedTags for com.brit.xcms.blog.BlogEntryCreateBean@598587
      14:44:34,828 INFO  [BlogEntryCreateBean] BlogEntryCreateBean.attachTag: attaching tag: software
      14:44:34,828 INFO  [BlogEntryCreateBean] software attached
      14:44:38,031 INFO  [BlogEntryCreateBean] initAttachedTags for com.brit.xcms.blog.BlogEntryCreateBean@9a8e52
      14:44:38,078 INFO  [BlogEntryCreateBean] BlogEntryCreateBean.attachTag: attaching tag: sport
      14:44:38,078 INFO  [BlogEntryCreateBean] sport attached
      



      Where did I went wrong? Isn't the CONVERSATION scope enough for this type of calls? Why is a new instance of my bean created each time ajax calls its methods?


      Thank you for any hint on this issue,
      Bogdan.

        • 1. Re: Conversation and ajax4jsf problem
          Tom Goring Apprentice

          You need to begin a CONVERSATION for that to work.


          i quick fix you could add



          @Begin
          @Create
          void startUpWhenReferenced() {
          }




          • 2. Re: Conversation and ajax4jsf problem
            Bogdan Minciu Newbie

            Thank you Tom for the reply.


            This is how my component looks like now:


            @Stateful
            @Name("blogEntryCreate")
            @Scope(ScopeType.CONVERSATION)
            @Restrict("#{identity.loggedIn}")
            @Synchronized
            public class BlogEntryCreateBean implements LocalBlogEntryCreate {
            
            ...
                @Out(required = false)
                private BlogEntry newBlogEntry;
            
                private String body;
            
                @In
                private BlogEntryDAO blogEntryDAO;
            
                @Begin
                public void addBlogEntry() {
                    newBlogEntry = new BlogEntry();
                    log.info("@Begin #0. Created and outjected a new blogEntry = #1.", this, newBlogEntry);
                }
            
                @End
                public void persist() {
                    blogEntryDAO.persist(newBlogEntry);
                    FacesMessages.instance().add("BlogEntry created.");
                }
            ...
            }
            



            But I have a more trivial question now. If a user wants to start in 2 different browser tabs the add procedure, I get the exception: java.lang.IllegalStateException: begin method invoked from a long-running conversation, try using @Begin(join=true) on method: addBlogEntry. Well... I can add the @Begin(join=true) annotation, but this means that on each opened tab where the user tries to add a different blogEntry, he will see the values already initialized by the first tab that begun the conversation.


            My question is: how can I start a really NEW conversation in each tab even if a long running conversation is already opened?


            Thank you,
            Bogdan.