Session context variable missing
gmarcus Jun 9, 2007 6:18 PMI 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?