3.3. Clickable lists in Seam: the messages example

Clickable lists of database search results are a vital part of an online application. Seam provides special functionality on top of JSF to make it easier to query data with EJB-QL or HQL, and display the result as a clickable list using a JSF <h:dataTable>. The messages example demonstrates this functionality.

3.3.1. Understanding the code

The message list example has one entity bean (Message), one session bean (MessageListBean), and one JSF.

3.3.1.1. The entity bean: Message.java

The Message entity defines the title, text, date, and time of a message, and a flag indicating whether the message has been read.

Example 3.12. Message.java

@Entity
@Name("message")
@Scope(EVENT)
public class Message implements Serializable
{
   private Long id;
   private String title;
   private String text;
   private boolean read;
   private Date datetime;
   
   @Id @GeneratedValue
   public Long getId()
   {
      return id;
   }
   public void setId(Long id)
   {
      this.id = id;
   }
   
   @NotNull @Size(max=100)
   public String getTitle()
   {
      return title;
   }
   public void setTitle(String title)
   {
      this.title = title;
   }
   
   @NotNull @Lob
   public String getText()
   {
      return text;
   }
   public void setText(String text)
   {
      this.text = text;
   }
   
   @NotNull
   public boolean isRead()
   {
      return read;
   }
   public void setRead(boolean read)
   {
      this.read = read;
   }
   
   @NotNull 
   @Basic @Temporal(TemporalType.TIMESTAMP)
   public Date getDatetime()
   {
      return datetime;
   }
   public void setDatetime(Date datetime)
   {
      this.datetime = datetime;
   }
   
}

3.3.1.2. The stateful session bean: MessageManagerBean.java

This example contains a session bean (MessageManagerBean) that defines the action listener methods for both of the buttons on the form. One buttons selects a message from the list and displays the message; the other button deletes a message.
However, MessageManagerBean is also responsible for fetching the list of messages the first time you navigate to the message list page. There are various ways to navigate to the message list page, all the ways are not preceded by a JSF action. (For example, navigating to the page from your favorites will not necessarily call the JSF action.). Therefore, fetching the message list must take place in a Seam factory method, instead of in an action listener method.
To cache the list of messages in memory between server requests, make this a stateful session bean.

Example 3.13. MessageManagerBean.java

@Stateful
@Scope(SESSION)
@Name("messageManager")
public class MessageManagerBean 
    implements Serializable, MessageManager
{
    @DataModel                                                        1
    private List<Message> messageList;
        
    @DataModelSelection                                               2
    @Out(required=false)                                              3
    private Message message;
        
    @PersistenceContext(type=EXTENDED)                                4
    private EntityManager em;
        
    @Factory("messageList")                                           5
    public void findMessages()
    {
        messageList = em.createQuery("select msg " + 
                                     "from Message msg" + 
                                     "order by msg.datetime desc")
                         .getResultList();
    }
        
    public void select()                                              6
    {
        message.setRead(true);
    }
        
    public void delete()                                              7
    {
        messageList.remove(message);
        em.remove(message);
        message=null;
    }
        
    @Remove                                                           8
    public void destroy() {}
        
}

1

The @DataModel annotation exposes an attributes of type java.util.List to the JSF page as an instance of javax.faces.model.DataModel. This allows you to use the list in a JSF <h:dataTable> with clickable links for each row. In this case, the DataModel is made available in the messageList session context variable.

2

The @DataModelSelection annotation tells Seam to inject the List element that corresponds to the clicked link.

3

The @Out annotation exposes the selected value directly to the page. So every time a row of the clickable list is selected, the Message is injected to the attribute of the stateful bean, and subsequently outjected to the message event context variable.

4

This stateful bean has an EJB3 extended persistence context. The messages retrieved in the query remain in the managed state as long as the bean exists. So subsequent method calls to the stateful bean can update the messages without making an explicit call to the EntityManager.

5

The first time you navigate to the JSF page, there is no value in the messageList context variable. The @Factory annotation requests Seam to create an instance of MessageManagerBean and invoke the findMessages() method to initialize the value. findMessages()i called a factory method for messages.

6

The select() action listener method marks the selected Message as read, and updates it in the database.

7

The delete() action listener method removes the selected Message from the database.

8

All stateful session bean Seam components must define a parameterless method marked @Remove. Seam uses this method to remove stateful beans when Seam context ends, and clean up server-side states.

Note

This is a session-scoped Seam component. It is associated with the user log in the session, and all requests from a log in the session share the same instance of the component. Session-scoped components are used sparingly in Seam applications.

3.3.1.3. The session bean local interface: MessageManager.java

All session beans have a business interface.

Example 3.14. MessageManager.java

@Local
public interface MessageManager {
    public void findMessages();
    public void select();
    public void delete();
    public void destroy();
}
From this point, local interfaces are not shown in code examples. Components.xml, persistence.xml, web.xml, ejb-jar.xml, faces-config.xml, and application.xml files operate in a similar fashion as in the previous example, and go directly to the JSF.

3.3.1.4. The view: messages.xhtml

The JSF page is a straightforward use of the JSF <h:dataTable> component.

Example 3.15. messages.xhtml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
                      "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:s="http://jboss.org/schema/seam/taglib"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:f="http://java.sun.com/jsf/core">
 <h:head>
  <title>Messages</title>
 </h:head>
 <h:body>
  <f:view>
     <h2>Message List</h2>
     <h:outputText id="noMessages" value="No messages to display" rendered="#{messageList.rowCount==0}"/>
     <h:dataTable id="messages" var="msg" value="#{messageList}" rendered="#{messageList.rowCount>0}">
        <h:column>
           <f:facet name="header">
              <h:outputText value="Read"/>
           </f:facet>
           <h:selectBooleanCheckbox id="read" value="#{msg.read}" disabled="true"/>
        </h:column>
        <h:column>
           <f:facet name="header">
              <h:outputText value="Title"/>
           </f:facet>
           <s:link id="link" value="#{msg.title}" action="#{messageManager.select}"/>
        </h:column>
        <h:column>
           <f:facet name="header">
              <h:outputText value="Date/Time"/>
           </f:facet>
           <h:outputText id="date" value="#{msg.datetime}">
              <f:convertDateTime type="both" dateStyle="medium" timeStyle="short"/>
           </h:outputText>
        </h:column>
        <h:column>
           <s:button id="delete" value="Delete" action="#{messageManager.delete}"/>
        </h:column>
     </h:dataTable>
     <h3><h:outputText id="title" value="#{message.title}"/></h3>
     <div><h:outputText id="text" value="#{message.text}"/></div>
  </f:view>
 </h:body>
</html>

3.3.2. How it works

The first time you navigate to the messages.xhtml page, the page tries to resolve the messageList context variable. As the context variable is not initialized, Seam calls the findMessages()factory method, which performs a query against the database and results in a DataModel being outjected. This DataModel provides the row data needed for rendering <h:dataTable>.
When you click <s:link>, JSF calls the select() action listener. Seam intercepts the call and injects the selected row data into the message attribute of the messageManager component. The action listener fires, marking the selected Message as read. At the end of the call, Seam outjects the selected Message to the message context variable. The EJB container commits the transaction, and the change to the Message is flushed to the database. The page is re-rendered, redisplaying the message list, and displaying the selected message below it.
If you click <s:button>, JSF calls the delete() action listener. Seam intercepts the call and injects the selected row data into the message attribute of the messageManager component. The action listener fires, removing the selected Message from the list, and calling remove() on the EntityManager. At the end of the call, Seam refreshes the messageList context variable and clears the message context variable. The EJB container commits the transaction, and deletes the Message from the database. The page is re-rendered, redisplaying the message list.