The JSF Mail application is a full-featured web application version of the venerable mail example. Like the SQL mail application, JSF Mail uses a database to store messages and user account data (both applications use the same database). JSF Mail replaces the Swing GUI used in SQL Mail with a web interface implemented with Java Server Faces.
You can download the project for this example here.
The welcome page of the application is a simple log-in page. Users can also create new accounts from the page by entering a user name and password to use for the account.
![]() |
The main page of the application displays the messages the user has available to read.
![]() |
Users can read existing messages or compose new messages to send.
![]() |
![]() |
The JSF Mail application uses the same database as the SQL Mail example. Much of the SQL code for accessing the database is similar. In the SQL Mail application we put all of the JDBC code needed to access the database into a MessageManager class. In JSF Mail this code is distributed across three different bean classes that manage different facets of the application.
An important consideration when designing bean classes for a web application is bean scope. JSF managed beans can have one of several scopes, including application scope, session scope, and request scope. Application scope beans have a single instance that persists across the full lifetime of the application and is shared across all user sessions. Beans with session scope have one instance per session which persists for the lifetime of the user session. Beans with request scope persist only for the duration of a single request, collecting property values from the input components of a page, participating in actions triggered on that page, and persisting just long enough to provide data to the elements of the page that results from that action.
A general rule of thumb for managed beans is that a bean should have the briefest scope needed to serve the needs of the application. Request beans are ideal for this, since they persist just long enough to process a single action and then go away again - this saves resources on the server and makes it possible for the application to serve more users with the resources available. Another useful rule of thumb is that bean classes should be as simple as possible and ideally should be focussed on doing just one thing.
The JSF Mail application uses three managed beans, a Login bean, a User bean, and a Message bean.
The User bean is the only bean in the application with session scope. It holds the only property in the application that needs to persist across all page of the application, the user name of the user who is currently logged into the mail application. For convenience, this bean also contains a method that returns an array of SelectItems that the application will use to populate the list of available messages on the main page.
@ManagedBean
@SessionScoped
public class User {
@Resource(name = "jdbc/mail")
private DataSource dataSource;
private Connection connection;
// Properties
protected String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public User() {
}
String handleSQL = "select message_id, handle from handles where recipient=?";
PreparedStatement handleStmt = null;
public ArrayList<SelectItem> getAllHandles() {
ArrayList<SelectItem> result = new ArrayList<SelectItem>();
initConnection();
try {
ResultSet rset = handleStmt.executeQuery();
while (rset.next()) {
result.add(new SelectItem(rset.getString(1), rset.getString(2)));
}
} catch (SQLException ex) {
ex.printStackTrace();
}
return result;
}
private void initConnection() {
try {
if (connection == null) {
connection = dataSource.getConnection();
}
if (handleStmt == null) {
handleStmt = connection.prepareStatement(handleSQL);
handleStmt.setString(1, name);
}
} catch (SQLException ex) {
ex.printStackTrace();
}
}
}
The Login bean is a request bean that exists primarily to handle the actions associated with the login and new account buttons on the start page. The Login class has doLogin() and newAccount() action methods linked to those buttons.
@ManagedBean
@RequestScoped
public class Login {
@Resource(name = "jdbc/mail")
private DataSource dataSource;
// Properties
@ManagedProperty(value = "#{user}")
protected User user;
protected String name;
protected String password;
public void setUser(User user) {
this.user = user;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public Login() {
}
public String doLogin() {
String navString = null;
Connection connection = null;
PreparedStatement loginStmt = null;
String loginSQL = "select password from users where user=?";
try {
connection = dataSource.getConnection();
loginStmt = connection.prepareStatement(loginSQL);
loginStmt.setString(1, name);
ResultSet result = loginStmt.executeQuery();
if (result.next()) {
String actualPassword = result.getString("password");
if (actualPassword != null && actualPassword.equals(password)) {
user.setName(name);
navString = "mail";
} else {
FacesContext.getCurrentInstance().addMessage("form:password", new FacesMessage("Incorrect password."));
}
} else {
FacesContext.getCurrentInstance().addMessage("form:name", new FacesMessage("No such user."));
}
} catch (SQLException ex) {
ex.printStackTrace();
} finally {
try {
if (loginStmt != null) {
loginStmt.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
}
return navString;
}
public String newAccount() {
String navString = null;
Connection connection = null;
PreparedStatement userQuery = null;
String userSQL = "select user from users where user=?";
PreparedStatement insertStmt = null;
String insertSQL = "insert into users(user,password) values(?,?)";
try {
connection = dataSource.getConnection();
userQuery = connection.prepareStatement(userSQL);
userQuery.setString(1, name);
ResultSet result = userQuery.executeQuery();
if (result.next()) {
// User already exists
} else {
insertStmt = connection.prepareStatement(insertSQL);
insertStmt.setString(1, name);
insertStmt.setString(2, password);
insertStmt.executeUpdate();
navString = "mail";
}
} catch (SQLException ex) {
ex.printStackTrace();
} finally {
try {
if (userQuery != null) {
userQuery.close();
}
if (insertStmt != null) {
insertStmt.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
}
return navString;
}
}
The Message bean is the primary bean with responsibility for dealing with mail messages. It contains methods for sending, reading, and deleting mail messages. Since all of these activities span a single request cycle, we can get away with giving this bean class request scope.
@ManagedBean
@RequestScoped
public class Message {
@Resource(name = "jdbc/mail")
private DataSource dataSource;
// Properties
@ManagedProperty(value = "#{user}")
protected User user;
protected int id;
protected String sender;
protected String subject;
protected String body;
protected String recipients;
protected String date;
public void setUser(User user) {
this.user = user;
}
public String getId() {
return Integer.toString(id);
}
public void setId(String id) {
if (id == null) {
this.id = 0;
} else {
this.id = Integer.parseInt(id);
}
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getSubject() {
return subject;
}
public void setSubject(String subject) {
this.subject = subject;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
public String getRecipients() {
return recipients;
}
public void setRecipients(String recipients) {
this.recipients = recipients;
}
public String getDate() {
return date;
}
public Message() {
}
public String read() {
String navString = null;
Connection connection = null;
PreparedStatement readStmt = null;
String readSQL = "select sender, subject, body, date from messages where message_id=?";
try {
connection = dataSource.getConnection();
readStmt = connection.prepareStatement(readSQL);
readStmt.setInt(1, id);
ResultSet result = readStmt.executeQuery();
if (result.next()) {
sender = result.getString(1);
subject = result.getString(2);
body = result.getString(3);
date = result.getString(4);
navString = "read";
}
} catch (SQLException ex) {
ex.printStackTrace();
} finally {
try {
if (readStmt != null) {
readStmt.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
}
return navString;
}
public String send() {
String navString = null;
Connection connection = null;
String insertMsgSQL = "insert into messages (subject,body,sender,date) values (?,?,?,CURDATE())";
PreparedStatement insertMsgStmt = null;
String idQuerySQL = "select LAST_INSERT_ID()";
Statement idQueryStmt = null;
String insertRecipientSQL = "insert into recipients (message_id,recipient) values (?,?)";
PreparedStatement insertRecipientStmt = null;
try {
connection = dataSource.getConnection();
insertMsgStmt = connection.prepareStatement(insertMsgSQL);
idQueryStmt = connection.createStatement();
insertRecipientStmt = connection.prepareStatement(insertRecipientSQL);
// Data in this message has to be spread over two tables, the messages
// table and the recipients table. We do the insertion into the messages
// table so the database can generate a messageID for this message. We
// then use that messageID to do the insertions into the recipients table.
insertMsgStmt.setString(1, subject);
insertMsgStmt.setString(2, body);
insertMsgStmt.setString(3, user.getName());
insertMsgStmt.executeUpdate();
// The id for the new message is generated automatically by the database
// We have to query the database to see what it is.
ResultSet rset = idQueryStmt.executeQuery(idQuerySQL);
if (rset.next()) {
id = rset.getInt(1);
insertRecipientStmt.setInt(1, id);
Iterator<String> iter = this.getAllRecipients().iterator();
while (iter.hasNext()) {
insertRecipientStmt.setString(2, iter.next());
insertRecipientStmt.executeUpdate();
}
}
navString = "mail";
} catch (SQLException ex) {
ex.printStackTrace();
} finally {
try {
if (insertMsgStmt != null) {
insertMsgStmt.close();
}
if (idQueryStmt != null) {
idQueryStmt.close();
}
if (insertRecipientStmt != null) {
insertRecipientStmt.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
}
return navString;
}
public String delete() {
String navString = null;
Connection connection = null;
String deleteRecipientSQL = "delete from recipients where message_id=? and recipient=?";
PreparedStatement deleteRecipientStmt = null;
String queryRecipientSQL = "select recipient from recipients where message_id=?";
PreparedStatement queryRecipientStmt = null;
String deleteMessageSQL = "delete from messages where message_id=?";
PreparedStatement deleteMessageStmt = null;
try {
connection = dataSource.getConnection();
deleteRecipientStmt = connection.prepareStatement(deleteRecipientSQL);
queryRecipientStmt = connection.prepareStatement(queryRecipientSQL);
// Delete this recipient
deleteRecipientStmt.setInt(1, id);
deleteRecipientStmt.setString(2, user.getName());
deleteRecipientStmt.executeUpdate();
// If we just removed the last recipient for this message, remove it from the
// messages table, too.
queryRecipientStmt.setInt(1, id);
ResultSet results = queryRecipientStmt.executeQuery();
if (!results.isBeforeFirst()) {
deleteMessageStmt = connection.prepareStatement(deleteMessageSQL);
deleteMessageStmt.setInt(1, id);
deleteMessageStmt.executeUpdate();
}
navString = "mail";
} catch (SQLException ex) {
ex.printStackTrace();
} finally {
try {
if (deleteRecipientStmt != null) {
deleteRecipientStmt.close();
}
if (queryRecipientStmt != null) {
queryRecipientStmt.close();
}
if (deleteMessageStmt != null) {
deleteMessageStmt.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
}
return navString;
}
private ArrayList<String> getAllRecipients() {
ArrayList<String> items = new ArrayList<String>();
StringTokenizer tokens = new StringTokenizer(recipients, " ,");
while (tokens.hasMoreTokens()) {
items.add(tokens.nextToken());
}
return items;
}
}
As soon as we make the decision to split an application into several distinct bean classes we will have to overcome a technical problem. That problem is how to get information from one bean to another. For example, in this application the Login bean will need to pass the user name over to the User bean at the conclusion of a successful login. In another part of the application the Message bean will need to fetch that user name from User bean in order to send messages and delete messages. In each of these cases one bean will need a reference to another bean in order to communicate with it.
JSF managed beans can obtain references to other beans via a mechanism called dependency injection. To use dependency injection we set up a property whose type is a reference to the type of object we need to link to. We provide a setter method for this property, and then annotate the member variable with an annotation that identifies the property as a managed property.
@ManagedProperty(value = "#{user}")
protected User user;
public void setUser(User user) {
this.user = user;
}
The ManagedProperty annotation specifies which managed bean in the application we want this property linked to. The application server automatically initializes this property with a reference to the desired bean when our bean is created. In the example shown above, we are linking a bean to the User bean found in the current user session.
The only restriction on this mechanism is that the bean we obtain a reference to has to have a scope equal to or greater than the scope of the bean with the managed property. In this application, we use managed properties to link two beans with request scope to the User bean, which has session scope.
Another mechanism used to pass information from one bean to another is to link them via JSF page elements. Here is the page element in the main page that displays the list of messages available for the user to read.
<h:selectOneListbox required="false" value="#{message.id}">
<f:selectItems value="#{user.allHandles}"/>
</h:selectOneListbox>
This construct relies on the User bean to provide it with a list of SelectItem objects to populate the list box. The user's selection will get passed to the id property of the Message bean, since that bean will need to know the message identifier when the user asks it to read the message from the database and display it.
Another technical problem that arises with the use of beans with request scope is that they don't stick around long enough to provide information for some later action.
An example of this occurs when we try to read a message and then delete it. To read a message, the user starts by selecting the message on the main page and clicking the Read button.
![]() |
The list box has its value attribute linked to the id property of a Message bean. The Read button is linked to the read() action method in that same bean. The Message bean's read() method uses the id to look up the message in the database and fill its subject, sender, date, and body properties with data drawn from the database. Those properties are used to populate elements in the next page.
![]() |
Once the Message bean has provided that data to the elements on the next page, it vanishes, taking the id of the message in question with it.
Notice now that the next page has a delete button on it. That delete button is linked to a delete() method in a Message object, but this Message object is a completely new object unrelated to the Message object that got us to this page. In order to delete the message in question, this new Message object will need to know the id of that message. How do we pass that id information from the first Message bean to the second? We hide that information on the page using a special inputHidden element linked to the id property of the Message bean.
<h:inputHidden value="#{message.id}"/>
The first Message bean will provide its id property to this element. That element will store that value hidden from view but embedded in the page. When the user clicks the Delete button, a new Message bean will be created. That second bean will draw its id property value from the inputHidden element and use that id to carry out the delete() method.
The start page of the JSF mail application implements a rudimentary system of error checking and error messaging. For example, if the user enters the wrong password they will see an error message next to the password field.
![]() |
Error messages in a JSF application are implemented via message tags. Message tags are typically linked to specific elements in a page by giving the element in question an 'id' attribute and setting the 'for' attribute of the message tag to that id. For example, here is the password field and the message element linked to it.
<h:inputSecret id="password" value="#{login.password}"/>
<h:message for="password" style="color:red"/>
Message elements are used to display error messages generated by the application. To generate an error message from an action method in a managed bean we use the following Java code.
FacesContext.getCurrentInstance().addMessage("form:password", new FacesMessage("Incorrect password."));
The first parameter of the addMessage method is the identifier of the page element we want to direct the message to. In this case, we are sending the message to the element with id of 'password' found in the form with id of 'form'.
The JSF Mail application uses this mechanism to display error messages for everything that could go wrong in the login process. Here is the code for the Login bean's doLogin() method. Note the code that generates error messages in response to incorrect user names or passwords.
public String doLogin() {
String navString = null;
Connection connection = null;
PreparedStatement loginStmt = null;
String loginSQL = "select password from users where user=?";
try {
connection = dataSource.getConnection();
loginStmt = connection.prepareStatement(loginSQL);
loginStmt.setString(1, name);
ResultSet result = loginStmt.executeQuery();
if (result.next()) {
String actualPassword = result.getString("password");
if (actualPassword != null && actualPassword.equals(password)) {
user.setName(name);
navString = "mail";
} else {
FacesContext.getCurrentInstance().addMessage("form:password", new FacesMessage("Incorrect password."));
}
} else {
FacesContext.getCurrentInstance().addMessage("form:name", new FacesMessage("No such user."));
}
} catch (SQLException ex) {
ex.printStackTrace();
} finally {
try {
if (loginStmt != null) {
loginStmt.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException ex) {
ex.printStackTrace();
}
}
return navString;
}
Note also that in cases when error messages are generated the navigation string ends up with the value of null. This will force the application to navigate back to the start page at the conclusion of the action method. This is exactly the right behavior: when something goes wrong we want to put up an error message and stay on the same page to give the user the opportunity to fix the problem.
Another error handling mechanism in JSF is validation. JSF provides special validation tags that can be embedded in input elements to check that the data provided by the user in that element matches specific requirements.
The start page of the JSF Mail application uses validation tags to check that the user has provided values for the user name and password fields.
Enter your user name:
<h:inputText id="name" value="#{login.name}">
<f:validateRequired for="name"/>
</h:inputText>
<h:message for="name" style="color:red"/>
Enter your password:
<h:inputSecret id="password" value="#{login.password}">
<f:validateRequired for="password"/>
</h:inputSecret>
<h:message for="password" style="color:red"/>
Since validation tags have to be embedded inside the elements they are performing validation for, we have to use the extended form for those input elements, which consist of separate start and end tags enclosing the validation tags.
The validateRequired tag generates an error message when the user leaves the associated input field blank. Other validation tags available in JSF include validateLength, which checks that the input has a length that falls in a specified range, and validateDoubleRange, which checks that the field contains a number that falls in a specified range.
<f:validateLength minimum="5" maximum="10" for="name"/> <f:validateDoubleRange minimum="10.0" maximum="50.0" for="bid"/>