|
|
1.3. Clickable lists in Seam: the messages example
Clickable lists of database search results are such an important part of any
online application that Seam provides special functionality on top of JSF to
make it easier to query data using EJB-QL or HQL and display it as a clickable
list using a JSF <h:dataTable>. The messages example demonstrates this functionality.
1.3.1. Understanding the code
The message list example has one entity bean, Message, one session bean, MessageListBean and
one JSP.
1.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 1.10.
@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 @Length(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; } }
1.3.1.2. The stateful session bean: MessageManagerBean.java
Just like in the previous example, we have a session bean, MessageManagerBean, which
defines the action listener methods for the two buttons on our form. One
of the buttons selects a message from the list, and displays that message.
The other button deletes a message. So far, this is not so different to
the previous example.
But MessageManagerBean is also responsible for fetching the list of messages the first
time we navigate to the message list page. There are various ways the user
could navigate to the page, and not all of them are preceded by a JSF
action—the user might have bookmarked the page, for example. So the job of
fetching the message list takes place in a Seam
factory method, instead of in an action
listener method.
We want to cache the list of messages in memory between server requests,
so we will make this a stateful session bean.
Example 1.11.
@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("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 attibute of type java.util.List to the JSF
page as an instance of javax.faces.model.DataModel. This allows us to use the list in
a JSF <h:dataTable> with clickable links for each row. In this case, the
DataModel is made available in a session context variable named messageList.
|
|
(2)
|
The @DataModelSelection annotation tells Seam to inject the List element that
corresponded to the clicked link.
|
|
(3)
|
The @Out annotation then exposes the selected value directly to
the page. So ever time a row of the clickable list is
selected, the Message is injected to the attribute of the stateful
bean, and the subsequently
outjected to the event
context variable named message.
|
|
(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 any
subsequent method calls to the stateful bean can update them
without needing to make any explicit call to the EntityManager.
|
|
(5)
|
The first time we navigate to the JSP page, there will be no
value in the messageList context variable. The @Factory annotation tells
Seam to create an instance of MessageManagerBean and invoke the findMessages() method to
initialize the value. We call findMessages() 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 have a method with no
parameters marked @Remove that Seam uses to remove the stateful
bean when the Seam context ends, and clean up any server-side
state.
|
Note that this is a session-scoped Seam component. It is associated with
the user login session, and all requests from a login session share the
same instance of the component. (In Seam applications, we usually use
session-scoped components sparingly.)
1.3.1.3. The session bean local interface:
MessageManager.java
All session beans have a business interface, of course.
@Local public interface MessageManager { public void findMessages(); public void select(); public void delete(); public void destroy(); }
From now on, we won't show local interfaces in our code examples.
Let's skip over components.xml, persistence.xml, web.xml, ejb-jar.xml, faces-config.xml and application.xml since they are much the
same as the previous example, and go straight to the JSP.
1.3.1.4. The view: messages.jsp
The JSP page is a straightforward use of the JSF <h:dataTable> component. Again,
nothing specific to Seam.
Example 1.12.
<%@ taglib uri="http://java.sun.com/jsf/html" prefix="h" %> <%@ taglib uri="http://java.sun.com/jsf/core" prefix="f" %> <html> <head> <title>Messages</title> </head> <body> <f:view> <h:form> <h2>Message List</h2> <h:outputText value="No messages to display" rendered="#{messageList.rowCount==0}"/> <h:dataTable var="msg" value="#{messageList}" rendered="#{messageList.rowCount>0}"> <h:column> <f:facet name="header"> <h:outputText value="Read"/> </f:facet> <h:selectBooleanCheckbox value="#{msg.read}" disabled="true"/> </h:column> <h:column> <f:facet name="header"> <h:outputText value="Title"/> </f:facet> <h:commandLink value="#{msg.title}" action="#{messageManager.select}"/> </h:column> <h:column> <f:facet name="header"> <h:outputText value="Date/Time"/> </f:facet> <h:outputText value="#{msg.datetime}"> <f:convertDateTime type="both" dateStyle="medium" timeStyle="short"/> </h:outputText> </h:column> <h:column> <h:commandButton value="Delete" action="#{messageManager.delete}"/> </h:column> </h:dataTable> <h3><h:outputText value="#{message.title}"/></h3> <div><h:outputText value="#{message.text}"/></div> </h:form> </f:view> </body> </html>
The first time we navigate to the messages.jsp page, whether by a JSF postback (faces
request) or a direct browser GET request (non-faces request), the page will
try to resolve the messageList context variable. Since this context variable is not
initialized, Seam will call the factory method findMessages(), which performs a query
against the database and results in a DataModel being outjected. This DataModel provides
the row data needed for rendering the <h:dataTable>.
When the user clicks the <h:commandLink>, JSF calls the select() action listener. Seam
intercepts this 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 context variable named message. Next, the EJB container commits the
transaction, and the change to the Message is flushed to the database. Finally,
the page is re-rendered, redisplaying the message list, and displaying the
selected message below it.
If the user clicks the <h:commandButton>, JSF calls the delete() action listener. Seam
intercepts this call and injects the selected row data into the message
attribute of the messageList component. The action listener fires, removing the
selected Message from the list, and also calling remove() on the EntityManager. At the end of
the call, Seam refreshes the messageList context variable and clears the context
variable named message. The EJB container commits the transaction, and deletes
the Message from the database. Finally, the page is re-rendered, redisplaying
the message list.
|