7 Replies Latest reply on Jun 11, 2007 6:42 PM by pmuir

    Session context variable missing

    gmarcus

      I have an Entitiy called Category as defined here:

      package com.mystuff.fresh.domain;
      
      import static org.jboss.seam.ScopeType.SESSION;
      import static org.jboss.seam.ScopeType.CONVERSATION;
      
      import java.io.Serializable;
      
      import javax.persistence.Entity;
      import javax.persistence.GeneratedValue;
      import javax.persistence.Id;
      import javax.persistence.NamedQueries;
      import javax.persistence.NamedQuery;
      import javax.persistence.Transient;
      
      
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Role;
      import org.jboss.seam.annotations.Scope;
      
      
      @Entity
      @Name("category")
      @Scope(CONVERSATION)
      @Role(name = "currentCategory", scope=SESSION)
      @NamedQueries( {
       @NamedQuery(name = "Category.findById", query = "SELECT c FROM Category c WHERE c.id=:id"),
       @NamedQuery(name = "Category.findAll", query = "SELECT c FROM Category c"),
       @NamedQuery(name = "Category.findRoot", query = "SELECT c FROM Category c WHERE c.id=0"),
       @NamedQuery(name = "Category.findByName", query = "SELECT c FROM Category c WHERE toupper(c.name)=toupper(:name)")})
      public class Category implements Serializable
      {
       private static final long serialVersionUID = 1881413500711441952L;
      
       @Id
       @GeneratedValue
       private long id;
      
       // parent category
       private long parentId;
      
       private String name;
      
       private String description;
      
       private long numberOfEntries;
      
       // TODO: keep a view count on categories to know which are the most popular
       // private long numViews;
      
       private Boolean visible;
      
      
       public Category()
       {
       }
      
       /**
       * @return the description
       */
       public String getDescription()
       {
       return description;
       }
      
       /**
       * @param description
       * the description to set
       */
       public void setDescription(String description)
       {
       this.description = description;
       }
      
       /**
       * @return the id
       */
       public long getId()
       {
       return id;
       }
      
       /**
       * @param id
       * the id to set
       */
       public void setId(long id)
       {
       this.id = id;
       }
      
       /**
       * @return the name
       */
       public String getName()
       {
       return name;
       }
      
       /**
       * @param name
       * the name to set
       */
       public void setName(String name)
       {
       this.name = name;
       }
      
       /**
       * @return the numberOfEntries
       */
       public long getNumberOfEntries()
       {
       return numberOfEntries;
       }
      
       /**
       * @param numberOfEntries
       * the numberOfEntries to set
       */
       public void setNumberOfEntries(long numberOfEntries)
       {
       this.numberOfEntries = numberOfEntries;
       }
      
       /**
       * @return the parentId
       */
       public long getParentId()
       {
       return parentId;
       }
      
       /**
       * @param parentId
       * the parentId to set
       */
       public void setParentId(long parentId)
       {
       this.parentId = parentId;
       }
      
       /**
       * @return the visible
       */
       public Boolean getVisible()
       {
       return visible;
       }
      
       /**
       * @param visible
       * the visible to set
       */
       public void setVisible(Boolean visible)
       {
       this.visible = visible;
       }
      
       /**
       * @return Boolean
       */
       @Transient
       public Boolean isVisible()
       {
       return getVisible();
       }
      
       @Override
       public String toString()
       {
       return ("Category(id:" + id + ", parentId:" + parentId + ", name:" + name + ", description:" + description + ", numberOfEntries:" + numberOfEntries + ", visible:" + visible + ")");
       }
      
      }
      


      I have a SFSB defined here:
      package com.mystuff.fresh.actions;
      
      import static javax.persistence.PersistenceContextType.EXTENDED;
      
      import java.util.List;
      
      import javax.ejb.Remove;
      import javax.ejb.Stateful;
      import javax.persistence.EntityManager;
      import javax.persistence.PersistenceContext;
      
      import org.jboss.seam.annotations.Create;
      import org.jboss.seam.annotations.Destroy;
      import org.jboss.seam.annotations.Factory;
      import org.jboss.seam.annotations.In;
      import org.jboss.seam.annotations.Logger;
      import org.jboss.seam.annotations.Name;
      import org.jboss.seam.annotations.Out;
      import org.jboss.seam.annotations.datamodel.DataModel;
      import org.jboss.seam.annotations.datamodel.DataModelSelection;
      import org.jboss.seam.log.Log;
      
      import com.mystuff.fresh.domain.Category;
      import com.mystuff.fresh.services.CategoryManager;
      
      @Stateful
      @Name("categoryManager")
      public class CategoryManagerAction implements CategoryManager
      {
       @SuppressWarnings("unused")
       @DataModel
       private List<Category> categories;
      
       @DataModelSelection
       @In(required = false, create = false)
       @Out(required = false)
       private Category currentCategory;
      
       @Out(required = false)
       Category rootCategory;
      
       @PersistenceContext(type = EXTENDED)
       private EntityManager em;
      
       @Logger
       private static Log log;
      
       @Factory("rootCategory")
       public String findRoot()
       {
       log.trace("findRoot() called");
       rootCategory = (Category) em.createNamedQuery("Category.findRoot").getSingleResult();
       return "success";
       }
      
       @SuppressWarnings("unchecked")
       @Factory("categories")
       public String findCategories()
       {
       log.trace("findAll() called");
       categories = em.createNamedQuery("Category.findAll").getResultList();
       return "success";
       }
      
       // TODO: Need to find a way to initialize currentCategory at startup of the
       // session so that
       // It is available before any page loads. This is a current issue with the
       // home.xhtml
       // which wants to display the current category before referencing categories
       @Create
       public String init()
       {
       log.trace("init() called");
      
       if (currentCategory == null)
       {
       log.warn("init() currentCategory is NULL");
       currentCategory = (Category) em.createNamedQuery("Category.findRoot").getSingleResult();
       }
      
       log.info("init() - currentCategory: #0", currentCategory);
       return "success";
       }
      
       public void select()
       {
       return;
       }
      
       @Remove
       @Destroy
       public void destroy()
       {
       }
      }
      


      On the homepage, I want to display the list of Categories, with the currentCategory displayed with a graphic (a bullet) next to it:

      home.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:s="http://jboss.com/products/seam/taglib"
       xmlns:ui="http://java.sun.com/jsf/facelets"
       xmlns:f="http://java.sun.com/jsf/core"
       xmlns:h="http://java.sun.com/jsf/html"
       template="/layout/template.xhtml">
      
      <ui:define name="body">
      
       <h:messages globalOnly="true" styleClass="message"/>
       <h:outputText value="CurrentCategory: #{currentCategory.name}"/>
      
       <table class="standard">
       <tr>
       <td width="18%">
       <ui:include src="/layout/categories.xhtml"/>
       </td>
       </tr>
       </table>
      
      
      
      </ui:define>
      </ui:composition>
      


      which includes the following xhtml snippet:
      categories.xhtml
      <div class="category"
       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:s="http://jboss.com/products/seam/taglib">
      <h:form id="categorylistform">
      <h:outputText value="No categories to display" rendered="#{categories.rowCount==0}"/>
      <h:dataTable styleclass="category" value="#{categories}" var="cat" rendered="#{categories.rowCount>0}">
       <h:column>
       <h:graphicImage styleClass="vcenter" id="selectedcategoryimage" alt="selected category" url="/img/pic_selected_dot_9x9.gif" rendered="#{cat.id==currentCategory.id}"/>
       </h:column>
       <h:column>
       <h:commandLink styleClass="topcategory" value="#{cat.name}" title="#{cat.description}" action="#{categoryManager.select}" rendered="#{cat.parentId == rootCategory.id}"/>
       <h:commandLink styleClass="childcategory" value="#{cat.name}" title="#{cat.description}" action="#{categoryManager.select}" rendered="#{cat.parentId != rootCategory.id}"/>
       <h:outputText value="   (#{cat.numberOfEntries})"/>
       </h:column>
      </h:dataTable>
      </h:form>
      </div>
      


      here is a sample of the data loaded by import.sql:
      insert into Category (id, parentId, name, description, visible, numberOfEntries) values (0, 0, 'All', 'All description', true, 0)
      insert into Category (id, parentId, name, description, visible, numberOfEntries) values (1, 0, 'Arts', 'Arts description', true, 0)
      insert into Category (id, parentId, name, description, visible, numberOfEntries) values (2, 1, 'Art', 'Arts | Art description', true, 0)
      insert into Category (id, parentId, name, description, visible, numberOfEntries) values (3, 1, 'Books', 'Arts | Books description', true, 2)
      insert into Category (id, parentId, name, description, visible, numberOfEntries) values (4, 1, 'Movies', 'Arts | Movies description', true, 0)
      insert into Category (id, parentId, name, description, visible, numberOfEntries) values (5, 1, 'Music', 'Arts | Music description', true, 0)
      insert into Category (id, parentId, name, description, visible, numberOfEntries) values (6, 1, 'Television', 'Arts | Television description', true, 0)
      insert into Category (id, parentId, name, description, visible, numberOfEntries) values (7, 1, 'Theater', 'Arts | Theater description', true, 0)
      insert into Category (id, parentId, name, description, visible, numberOfEntries) values (8, 0, 'Business', 'Business description', true, 0)
      



      Again, my goal is to remember what category the user has selected. I made currentCategory a SESSION scoped context variable. CategoryManagerAction is CONVERSATION component.

      When I first load up home.xhtml, currentCategory is set to the Root Category result returned from entitymanager and the bullet appears next to the root category (All in this case).

      When I click on a another category, the page refreshes and the bullet appears on the newly selected category (Books for example).

      My issue is that if I refresh the page (using the browser refresh), the bullet disappears (I would expect it to be drawn next to Books).

      If I hit refresh a second time, a bullet appears next to All.

      It is as if the currentCategory is lost after the response is written. I checked debug.seam after clicking hitting refresh, and I see currentCategory in the SESSION, and it has the correct Book value.

      Am I correct in defining currentCategory to be SESSION scoped in the entity, and setting it from the CONVERSATION scoped SFSB?

      Is there a better way to organize this? Do I need @Factory("currentCategory") on a method on the SFSB to make this all work?



        • 1. Re: Session context variable missing
          gmarcus

          My runtime environment is:
          Seam 1.2.1GA
          Jboss 4.0.4GA

          • 2. Re: Session context variable missing
            gmarcus

            An update to my use case.

            When I hit the browser refresh, it works as expected, the last selected Category stays selected and the bullet image shows up next to it.

            The problem occurs when I navigate back to home.xhtml from any other page, the currentCategory is displayed, but the bullet image is not being rendered. Here is a snippet of my menu.xhtml that is included in my main template

            <div class="menuButtons"
             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:s="http://jboss.com/products/seam/taglib">
             <s:link view="/home.xhtml" value="Home"/>
            </div>
            


            Come on guys, I could really use your help here...

            1. Can I bi-ject the SESSION currentCategory into a @DataModelSelection member variable of a CONVERSATION stateful session bean? If so, what is the right @In and @Out annotations?



            • 3. Re: Session context variable missing
              gmarcus

              Anyone have any ideas?

              • 4. Re: Session context variable missing
                pmuir

                 

                @DataModelSelection
                @In(required = false, create = false)
                @Out(required = false)
                private Category currentCategory;


                Having both @DataModelSelection and @In on the same variable isn't advisable - how does Seam know which one to jnject from?

                Also, you would probably have more luck if you actually outjected to the SESSION scope, rather than the CONVERSATION scope (by default you it outjects to the scope of it's parent).

                And, a hint about posting - exclude irrelevant information - I don't need to see your imports, your named queries, all your getters and setters - you've provided so much information to trawl through!

                • 5. Re: Session context variable missing
                  gmarcus

                  Thank you so much for your reply Peter.
                  Specifically helpful is the netiquette on posting.
                  From now on, I will just include the important stuff.

                  I am still trying to wrap my head around bi-jection and scopes.


                  Also, you would probably have more luck if you actually outjected to the SESSION scope, rather than the CONVERSATION scope (by default you it outjects to the scope of it's parent).

                  Is it true that during injection, SEAM will lookup a named component in all scopes based on the well known precedence (STATELESS, EVENT, PAGE, ...)?

                  I thought that by default, SEAM will outject to the same scope that it injected from.
                  My thinking was that for every function call on my SFSB, SEAM would inject currentCategory from the SESSION,
                  let it be changed by the SFSB and outject it back to the SESSION.

                  I want whatever Category the user selects to be reflected in the session.

                  How about this approach?
                  ...
                   @DataModel
                   private List<Category> categories;
                  
                   @DataModelSelection
                   private Category selectedCategory;
                  
                  ...
                  
                   @Create
                   public String init()
                   {
                   if (currentCategory == null)
                   {
                   currentCategory = (Category) em.createNamedQuery("Category.findRoot").getSingleResult();
                   }
                   return "success";
                   }
                  
                  ...
                  
                   public void select()
                   {
                   currentCategory = selectedCategory;
                   return;
                   }
                  
                  ...
                  






                  • 6. Re: Session context variable missing
                    pmuir

                     

                    "gmarcus" wrote:
                    Is it true that during injection, SEAM will lookup a named component in all scopes based on the well known precedence (STATELESS, EVENT, PAGE, ...)?


                    Yes



                    I thought that by default, SEAM will outject to the same scope that it injected from. My thinking was that for every function call on my SFSB, SEAM would inject currentCategory from the SESSION, let it be changed by the SFSB and outject it back to the SESSION.


                    No, it outjects into the specified scope, or if unspecified, the scope of the parent component. @In and @Out have no effect on the operation of each other.

                    ...
                     @DataModel
                     private List<Category> categories;
                    
                     @DataModelSelection
                     private Category selectedCategory;
                    
                    ...
                    
                     @Create
                     public String init()
                     {
                     if (currentCategory == null)
                     {
                     currentCategory = (Category) em.createNamedQuery("Category.findRoot").getSingleResult();
                     }
                     return "success";
                     }
                    
                    ...
                    
                     public void select()
                     {
                     currentCategory = selectedCategory;
                     return;
                     }
                    
                    ...
                    


                    • 7. Re: Session context variable missing
                      pmuir

                      Argh, fecking forum. Sorry.

                      ...
                       @DataModel
                       private List<Category> categories;
                      
                       @DataModelSelection
                       private Category selectedCategory;
                      
                      ...
                      
                       @Create
                       public String init()
                       {
                       if (currentCategory == null)
                       {
                       currentCategory = (Category) em.createNamedQuery("Category.findRoot").getSingleResult();
                       }
                       return "success";
                       }
                      
                      ...
                      
                       public void select()
                       {
                       currentCategory = selectedCategory;
                       return;
                       }
                      
                      ...
                      


                      Assuming you have @Out(scope=SESSION) CurrentCategory currentCategory and you call #{categoryManager.select} method when you change the category that would be a good approach, yes.