No results found

Your search did not match any results.

Building Database-driven Applications with JSF

Develop JSF-based Web applications that update and query a relational database using Oracle TopLink and JSTL's SQL tags
by Andrei Cioroianu

Developer: J2EE

JavaServer Faces (JSF) is the long-awaited standard Java technology for creating Web-based user interfaces. In this article, I will explain how to build JSF forms, validate the form data with JSF, implement JSF actions that access the database, and present the SQL result sets with JSF. A relational database can be updated and queried using a low-level API (JDBC), an object-relational (O-R) mapping framework such as Oracle TopLink, or a JSP tag library (e.g. JSTL). These options are discussed throughout this article along with the main JSF features.

Application Overview

JSF is not just another tag library for Web development. It's an MVC-based framework that controls your Web forms and manages your JavaBeans. JSF provides a limited set of standard UI components, but you can use component libraries such as Oracle Application Development Framework (ADF) Faces, and you can also build your own custom components. Keep in mind that all these UI components can be used together within the same Web page because they are based on the standard JSF API.

JSF pages are regular JSPs that use the standard JSF tag libraries and/or other libraries based on the JSF API. When a JSF page is executed, the JSF framework tries to obtain or restore a so-called "component tree" (or "view"). This contains information about the components of the Web page, the data conversions and validations that must be performed, the bean properties where the UI state must be stored, registered event listeners, etc. JSF creates the component tree if it doesn't exist, and then saves it on the client (using hidden form fields) or on the server (as a session attribute) for subsequent requests.

This article presents an example Web application that lets users subscribe to a few newsletters. Subscribers register themselves providing their email address, name, and preferences. They must also choose a password so that they can change their profile at a later time.

Figure 1 shows the subscription form. The example application doesn't distribute any newsletters, but a real-world app would send separate newsletters to managers, developers, and administrators. (The same subscriber could receive multiple newsletters.) The example application saves the user profiles into a relational database and lets subscribers change their profiles.

Figure 1: Subscription form

Application Configuration

In order to let JSF control your Web forms, you have to configure the Faces Servlet in the web.xml application descriptor. Your Web pages will have the .jsp extension, but the URLs will use either the .faces suffix or the /faces/ prefix. The JSF controller servlet must be mapped to *.faces or /faces/* in the web.xml file. The example application presented in this article uses the suffix mapping, and saves the JSF views on the client:

 
 <web-app> 
 ... 
 <context-param> 
 <param-name>javax.faces.STATE_SAVING_METHOD</param-name>
 <param-value>client</param-value>
 </context-param>
 <servlet> 
 <servlet-name>FacesServlet</servlet-name>
 <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
 <load-on-startup>1</load-on-startup>
 </servlet> 
 <servlet-mapping> 
 <servlet-name>FacesServlet</servlet-name> 
 <url-pattern>*.faces</url-pattern> 
 </servlet-mapping> 
 ... 
 </web-app>
 

Model-View Separation

Java Web applications, including those based on JSF, separate the presentation from the application logic using JSPs and JavaBeans. The attributes of the JSF tags can be bound to bean properties (and, sometimes, bean methods), using an expression language (EL) that is similar to the JSP 2.0 EL. Instead of using ${...} constructs, the JSF EL prefers the #{...} syntax so that it can be used with both JSP 1.2 and JSP 2.0. This also allows JSF to evaluate (and reevaluate) the expressions any time is necessary, instead of letting the JSP container control the evaluation of the expressions. The JSF EL bindings are bi-directional when it makes sense. For example, a UI component can get the value of a bean property and present it to the user as a default value. When the user submits the form data, the UI component updates the bean property so that the application logic can process the new value.

JavaBeans (models), JSF pages (views) and the Faces Servlet (controller) have well defined roles, separating the object model, the presentation, and the request processing. You can go one step further, separating the UI-specific Java code from the classes whose instances maintain and process the data. One possible solution is to build model beans that know nothing about the user interface, and then extend those model classes with JSF-specific methods, such as actions and validators, which are discussed later in this article.

The example application has two model beans ( LoginInfo and Subscriber ) that are extended by two view beans ( LoginInfoBean and SubscriberBean ) whose instances are managed by JSF. Each view bean instance has an identifier and a scope, which you can find in Table 1. Note that the JSF specification uses the terms "managed beans" or "backing beans" when it talks about the view beans. JSF makes no distinction between "model beans" and "view beans." In fact, you could place the application logic and the UI code within the same class, but this may reduce your ability to reuse the classes, the maintenance becomes harder, and specialized developers cannot focus on their main tasks, such as application logic or Web design.

Model bean class View bean class Bean identifier JSF scope
LoginInfo LoginInfoBean loginInfo request
Subscriber SubscriberBean subscriber session

Resources

With Oracle JDeveloper 10g (10.1.3) developer preview you get a single IDE that lets you define your toplink mapping, and visually build your JSF and JSP pages, in addition to writing your Java code. Oracle JDeveloper also comes with the ADF Faces set of rich JSF components that you can reuse in your application

  • Building JSF/TopLink applications with JDeveloper

Additional Resources:

Learn How To Use ADF Faces With JDeveloper 10g

Making Faces

JavaServer Faces (JSF) provides a powerful new framework for creating Web GUIs.

From ADF UIX to JSF

Oracle ADF Faces Components brings a library of reuse to JavaServer Faces.

Table 1: The model and view beans of the example Web application

Regular JSP pages use the <jsp:useBean> standard action to instantiate JavaBeans. When using the JSF framework, you don't have to specify the class names in your Web pages anymore. Instead, you configure your bean instances in an XML file, usually named faces-config.xml . (You may use multiple configuration files if you develop a large application. In this case, you must add a javax.faces.CONFIG_FILES parameter in the web.xml file.) For each bean instance, you have to specify an identifier (bean name), the class name, and a valid JSF scope ( application, session, request , or none ). When the bean is referred in a JSF expression ( #{...} ), the JSF framework verifies if the bean instance exists in the given scope, and if not found, the instance is created and initialized with the default values that can be specified in the JSF configuration file too:

 
 
 <faces-config> 
 ... 
 <managed-bean> 
 <managed-bean-name>loginInfo</managed-bean-name> 
 <managed-bean-class>jsfdb.view.LoginInfoBean</managed-bean-class> 
 <managed-bean-scope>request</managed-bean-scope> 
 </managed-bean> 
 <managed-bean> 
 <managed-bean-name>subscriber</managed-bean-name> 
 <managed-bean-class>jsfdb.view.SubscriberBean</managed-bean-class>
 <managed-bean-scope>session</managed-bean-scope> 
 <managed-property> 
 <property-name>email</property-name> 
 <null-value/> 
 </managed-property> 
 ... 
 <managed-property> 
 <property-name>subscriptionType</property-name> 
 <value>1</value> 
 </managed-property> 
 </managed-bean> 
 ... 
 </faces-config>
 

You do not have to declare each bean property, in the JSF configuration file, as managed property, since undeclared properties can be bound to the UI components of the JSF pages. You need to specify the bean properties in the faces-config.xml file only if you want them initialized with default values.

Bean-Page Relationships

The data access methods (presented in the next section) are called from the action methods of the view beans. Each action method is bound to a submit button in a JSP page as noted in Table 2. When the user clicks the button, the Web browser sends the form data to the server. The JSF framework validates the form data and returns the form to the user if there are any errors. Otherwise, the valid form data is stored into the properties of the managed bean, and JSF calls the action method that is bound to the clicked button. Therefore, your action methods should implement the application logic, processing the values of the bean's properties.

Data access method Managed bean and JSF action JSF page
SubscriberDAO.select() LoginInfoBean.loginAction() login.jsp
SubscriberDAO.insert() SubscriberBean.subscribeAction() subscribe.jsp
SubscriberDAO.update() SubscriberBean.profileAction() profile.jsp
SubscriberDAO.delete() SubscriberBean.unsubscribeAction() unsubscribe.jsp

Table 2: DAO methods used by the JSF actions that are bound to the submit buttons from the JSF pages

Page-to-Page Navigation

Each action method returns a string called "outcome." JSF uses a navigation handler to determine what it has to do for each outcome. If an action method returns null , the same page must be redisplayed. Otherwise, another page can be displayed, depending on the returned outcome. Table 3 contains the JSF forms of the example Web application, possible outcomes, and the pages that are displayed for each outcome.

JSF form Possible outcome Displayed page
subscribe.jsp subscribed subscribed.jsp
login.jsp profile profile.jsp
login.jsp list list.jsp
profile.jsp login login.jsp
unsubscribe.jsp login login.jsp
unsubscribe.jsp unsubscribed unsubscribed.jsp
unsubscribe.jsp cancel profile.jsp

Table 3: When the user clicks a submit button of a form, JSF validates the form data and, if no error occurs, calls the action method that is bound to the clicked button. Then, JSF uses the outcome returned by the action method to determine what page should be displayed next.

The default JSF navigation handler uses a set of navigation rules that are specified in the JSF configuration file. For example, when the user fills out the login form ( login.jsp ) and clicks the Login button, the loginAction() method may return profile or list depending on the role of the logged user: subscriber or administrator. Note that loginAction() returns null if the user is unknown or the password is incorrect. In this case, JSF shows the same login form. If the authentication is successful, JSF forwards the request to profile.jsp or list.jsp , depending on the outcome returned by loginAction() . Here is the navigation rule from faces-config.xml :

 
 
 <faces-config>
 ...
 <navigation-rule>
 <from-view-id>/login.jsp</from-view-id>
 <navigation-case>
 <from-outcome>profile</from-outcome>
 <to-view-id>/profile.jsp</to-view-id>
 </navigation-case>
 <navigation-case>
 <from-outcome>list</from-outcome>
 <to-view-id>/list.jsp</to-view-id>
 </navigation-case>
 </navigation-rule>
 ...
 </faces-config>
 

JavaBeans and Data Access Objects (DAO)

This section shows how to create model and view beans, and how to implement the bean persistence. Model beans define the properties that must be saved into the database. View beans extend the model beans with UI-specific code: actions, validators, etc. JSF creates instances of the view beans as specified in the faces-config.xml file, but the persistence layer of the example application works with model beans. Therefore, the application needs a utility method like ModelUtils.copy() , which copies the properties of the view beans instantiated by JSF into the model objects created by the persistence layer, and vice-versa. The ModelUtils class also lets you get the model resources (such as configuration parameters, SQL statements, and error messages) from a resource bundle named ModelResources . Finally, ModelUtils has a getSubscriberDAO() method that returns an instance of the SubscriberDAO interface that defines the methods for selecting, inserting, updating and deleting Subscriber objects from/into the relational database.

The example application provides two implementations of the SubscriberDAO interface, one based on the JDBC API, and the other using Oracle TopLink. These two implementations aren't used within the same application instance. The ModelResources bundle has a DAO parameter, which lets you specify the DAO implementation that you want to use. The only advantage of the JDBC-based DAO is that it uses only standard Java APIs and SQL. TopLink uses JDBC internally too, but if adds many benefits:

  • you become more productive because you define the O-R mappings and configure the application using visual tools (Mapping Workbench and Session Editor). This reduces significantly the amount of code that you have to write by hand;
  • you don't have to use a low-level API like JDBC anymore;
  • the application runs faster because TopLink caches the objects that are read from the database. In addition, TopLink generates internally SQL statements that are very efficient;
  • besides SQL, TopLink supports several other querying mechanisms such as "query by example," Java expression-based queries, and EJB QL;
  • the support for multiple server instance (clustering) allows you to build scalable applications.

Creating Model Beans The example Web application contains two model beans ( LoginInfo and Subscriber ) that don't use any JSF APIs. The LoginInfo class defines two properties ( email and password ) and is declared serializable, like any JavaBean, with the help of the java.io.Serializable interface:

 
 package jsfdb.model;
 
 public class LoginInfo implements java.io.Serializable {
 private String email;
 private String password;
 
 public String getEmail() {
 return email;
 }
 
 public void setEmail(String email) {
 this.email = email;
 }
 
 public String getPassword() {
 return password;
 }
 
 public void setPassword(String password) {
 this.password = password;
 }
 
 }
 

The Subscriber class extends LoginInfo , defines five additional properties ( name, manager, developer, administrator and subscriptionType ), and has a method named countNewsletters() :

 
 package jsfdb.model;
 
 public class Subscriber extends LoginInfo {
 public static final int TYPE_DAILY = 1;
 public static final int TYPE_WEEKLY = 2;
 public static final int TYPE_MONTHLY = 3;
 
 private String name;
 private boolean manager;
 private boolean developer;
 private boolean administrator;
 private int subscriptionType;
 
 public String getName() {
 return name;
 }
 
 public void setName(String name) {
 this.name = name;
 }
 
 ...
 
 public int countNewsletters() {
 int count = 0;
 if (isManager())
 count++;
 if (isDeveloper())
 count++;
 if (isAdministrator())
 count++;
 return count;
 }
 
 }
 

The instances of Subscriber are saved into a table that can be created with the following SQL statement:

 
 
 CREATE TABLE subscribers (
 subscriberEmail VARCHAR2(80) PRIMARY KEY,
 subscriberPassword VARCHAR2(20),
 subscriberName VARCHAR2(80),
 managerFlag NUMBER(1),
 developerFlag NUMBER(1),
 administratorFlag NUMBER(1),
 subscriptionType NUMBER(1) )
 

Usually, the bean's properties and the corresponding table columns should have the same names. This article uses different names so that you can identify easily where the properties are used and where the columns are used.

Using Data Access Objects The SubscriberDAO interface defines the methods that are called by the view beans to save and retrieve the properties that are inherited from the model beans

 
 package jsfdb.model.dao;
 
 import jsfdb.model.LoginInfo;
 import jsfdb.model.Subscriber;
 import jsfdb.model.err.IncorrectPasswordException;
 import jsfdb.model.err.LoginException;
 import jsfdb.model.err.ProfileException;
 import jsfdb.model.err.SubscribeException;
 import jsfdb.model.err.UnknownSubscriberException;
 import jsfdb.model.err.UnsubscribeException;
 
 public interface SubscriberDAO {
 public Subscriber select(LoginInfo loginInfo)
 throws LoginException,
 UnknownSubscriberException,
 IncorrectPasswordException;
 
 public void insert(Subscriber subscriber)
 throws SubscribeException;
 
 public void update(Subscriber subscriber)
 throws ProfileException;
 
 public void delete(Subscriber subscriber)
 throws UnsubscribeException;
 }
 
 

The getSubscriberDAO() method loads a DAO implementation ( TopLinkSubscriberDAO or JDBCSubscriberDAO ) and returns an instance of the loaded class:

 
 package jsfdb.model;
 
 import jsfdb.model.dao.SubscriberDAO;
 ...
 public class ModelUtils {
 ...
 private static SubscriberDAO subscriberDAO;
 ...
 public synchronized static SubscriberDAO getSubscriberDAO() {
 if (subscriberDAO == null)
 try {
 Class daoClass = Class.forName(getResource("DAO"));
 subscriberDAO
 = (SubscriberDAO) daoClass.newInstance();
 } catch (ClassNotFoundException x) {
 log(x);
 throw new InternalError(x.getMessage());
 } catch (IllegalAccessException x) {
 log(x);
 throw new InternalError(x.getMessage());
 } catch (InstantiationException x) {
 log(x);
 throw new InternalError(x.getMessage());
 }
 return subscriberDAO;
 }
 ...
 }
 
 

The getSubscriberDAO() method gets the name of the DAO implementation with getResource() , which uses getResources() to obtain the model resources. Any unexpected error is logged with the log() method:

 
 package jsfdb.model;
 ...
 import java.util.ResourceBundle;
 import java.util.MissingResourceException;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
 public class ModelUtils {
 public static final String RESOURCES
 = ModelUtils.class.getPackage().getName()
 + ".res.ModelResources";
 private static ResourceBundle resources;
 
 public static void log(Throwable x) {
 Logger.global.log(Level.SEVERE, x.getMessage(), x);
 }
 
 public static synchronized ResourceBundle getResources() {
 if (resources == null)
 try {
 resources = ResourceBundle.getBundle(RESOURCES);
 } catch (MissingResourceException x) {
 log(x);
 throw new InternalError(x.getMessage());
 }
 return resources;
 }
 
 public static String getResource(String key) {
 return getResources().getString(key);
 }
 ...
 }
 

The ModelResources bundle contains the DAO parameters, the SQL statements used by the JDBC-based DAO, and the messages of the exceptions thrown by the DAO methods:

 
 DAO=jsfdb.model.dao.TopLinkSubscriberDAO
 TopLinkSession=JSFDBSession
 
 # DAO=jsfdb.model.dao.JDBCSubscriberDAO
 JavaCompEnv=java:comp/env
 DataSource=jdbc/OracleDS
 
 SelectStatement=...
 
 InsertStatement=...
 
 UpdateStatement=...
 
 DeleteStatement=...
 
 SubscribeException=Subscription failed. \
 Please try another email address.
 ProfileException=Couln't update your profile. \
 Please contact the Webmaster.
 UnsubscribeException=Unsubscription failed. \
 Please contact the Webmaster.
 LoginException=Login failed. \
 Please contact the Webmaster.
 UnknownSubscriberException=Unknown subscriber. \
 Please subscribe.
 IncorrectPasswordException=Incorrect password. \
 Please try to login again.
 

All exception classes extend ModelException:

 
 
 package jsfdb.model.err;
 
 import jsfdb.model.ModelUtils;
 
 public class ModelException extends Exception {
 public ModelException(String messageKey) {
 super(ModelUtils.getResource(messageKey));
 }
 }
 
 
 

Each exception class contains only a constructor that passes the class name to the constructor of the ModelException superclass:

 
 
 package jsfdb.model.err;
 
 public class LoginException extends ModelException {
 public LoginException() {
 super("LoginException");
 }
 
 }
 
 

The JDBCSubscriberDAO class obtains a DataSource with JNDI, and implements the methods defined by SubscriberDAO . See the source code of JDBCSubscriberDAO.java from the downloadable archive that accompanies this article.

The TopLinkSubscriberDAO class implements the SubscriberDAO interface too. The TopLinkSubscriberDAO() constructor gets a server session and adds a shutdown hook that will close the session when the JVM is stopped. The TopLink-based DAO has utility methods for acquiring client sessions and units of work that are used by the methods that implement the SubscriberDAO interface:

 
 
 package jsfdb.model.dao;
 ...
 import oracle.toplink.sessions.UnitOfWork;
 import oracle.toplink.threetier.ClientSession;
 import oracle.toplink.threetier.Server;
 import oracle.toplink.tools.sessionmanagement.SessionManager;
 
 public class TopLinkSubscriberDAO implements SubscriberDAO {
 private Server serverSession;
 
 public TopLinkSubscriberDAO() {
 SessionManager manager = SessionManager.getManager();
 String id = ModelUtils.getResource("TopLinkSession");
 ClassLoader loader = this.getClass().getClassLoader();
 serverSession = (Server) manager.getSession(id, loader);
 Runtime.getRuntime().addShutdownHook(new Thread() {
 public void run() {
 serverSession.logout();
 SessionManager.getManager().getSessions().remove(
 ModelUtils.getResource("TopLinkSession"));
 }
 });
 }
 
 private ClientSession acquireClientSession() {
 return serverSession.acquireClientSession();
 }
 
 private UnitOfWork acquireUnitOfWork() {
 return acquireClientSession().acquireUnitOfWork();
 }
 ...
 }
 
 
 

Creating View Beans As mentioned earlier, the two view beans ( LoginInfoBean and SubscriberBean) extend the model beans ( LoginInfo and Subscriber ) with UI-specific code, such as methods that validate the data (validators) and method that process the data (actions). The LoginInfoBean class has a single action method named loginAction() . The other view bean ( SubscriberBean ) defines an additional property ( loggedIn ), implements the emailValidator() method that verifies if the @ character is present in the email address, provides several action methods, and exposes the constants inherited from Subscriber as read-only properties so that they can be accessed in the JSF pages, using the EL:

 
 package jsfdb.view;
 
 import jsfdb.model.Subscriber;
 ...
 public class SubscriberBean extends Subscriber {
 ...
 private transient boolean loggedIn = false;
 
 public boolean isLoggedIn() {
 return loggedIn;
 }
 
 public void setLoggedIn(boolean loggedIn) {
 this.loggedIn = loggedIn;
 }
 
 public void emailValidator(FacesContext context,
 UIComponent comp, Object value) {
 ...
 }
 
 public String subscribeAction() {
 ...
 }
 
 public String profileAction() {
 ...
 }
 
 public String unsubscribeAction() {
 ...
 }
 
 public String cancelAction() {
 if (!loggedIn)
 return "login";
 else
 return "cancel";
 }
 
 public int getDailyConst() {
 return TYPE_DAILY;
 }
 
 public int getWeeklyConst() {
 return TYPE_WEEKLY;
 }
 
 public int getMonthlyConst() {
 return TYPE_MONTHLY;
 }
 
 }
 

The action methods of the two view beans are detailed in the following paragraphs of this section. Concretely, you'll see how

  • loginAction() selects a row,
  • subscribeAction() inserts a new row,
  • profileAction() updates an existing row,
  • unsubscribeAction() delete a row.

The next section ( JSF Views and Data Validation )

  • presents the JSF pages of the example application,
  • explains how the data is validated with the help of JSF,
  • shows how to create the bindings between the UI components and the bean properties,
  • demonstrates how the action methods are bound to the submit buttons of the JSF forms.

Selecting a Row (Login Action) The select() method of JDBCSubscriberDAO executes an SQL query that selects the subscriber with a given email address, and then verifies the password. Here is the SELECT statement used by the JDBC-based DAO:

 
 SELECT subscriberPassword, 
 subscriberName, 
 managerFlag, 
 developerFlag, 
 administratorFlag, 
 subscriptionType 
 FROM subscribers 
 WHERE subscriberEmail=?
 

When using TopLink, you don't have to build SQL statements, though you can use SQL if you want. The SQL queries are generated by the TopLink framework if you use the query API as in the private read() method, which is called from several public methods of TopLinkSubscriberDAO to execute a query that returns the subscriber with the given email address:

 
 package jsfdb.model.dao;
 ...
 import oracle.toplink.expressions.ExpressionBuilder;
 import oracle.toplink.queryframework.ReadObjectQuery;
 import oracle.toplink.sessions.Session;
 
 public class TopLinkSubscriberDAO implements SubscriberDAO {
 ...
 private Subscriber read(Session session, String email) {
 ReadObjectQuery query
 = new ReadObjectQuery(Subscriber.class);
 ExpressionBuilder builder = new ExpressionBuilder();
 query.setSelectionCriteria(
 builder.get("email").equal(email));
 return (Subscriber) session.executeQuery(query);
 }
 ...
 }
 
 

The select() method of TopLinkSubscriberDAO acquires a client session and calls read() :

 
 package jsfdb.model.dao;
 ...
 public class TopLinkSubscriberDAO implements SubscriberDAO {
 ...
 public Subscriber select(LoginInfo loginInfo)
 throws LoginException,
 UnknownSubscriberException,
 IncorrectPasswordException {
 Subscriber s = null;
 try {
 ClientSession session = acquireClientSession();
 s = read(session, loginInfo.getEmail());
 } catch (Exception x) {
 ModelUtils.log(x);
 throw new LoginException();
 }
 if (s == null)
 throw new UnknownSubscriberException();
 if (!s.getPassword().equals(loginInfo.getPassword()))
 throw new IncorrectPasswordException();
 return s;
 }
 ...
 }
 

The LoginInfoBean class contains the loginAction() method, which uses ViewUtils.eval() to get the instance of the SubscriberBean class that is managed by JSF, and to obtain the value of the adminEmail initialization parameter. Then, loginAction() calls select() , uses ModelUtils.copy() to copy the properties of the selected subscriber into the JavaBean managed by JSF, sets the loggedIn flag to true , and returns the list or profile outcome depending on the subscriber's email:

 
 
 package jsfdb.view;
 
 import jsfdb.model.LoginInfo;
 import jsfdb.model.Subscriber;
 import jsfdb.model.ModelUtils;
 import jsfdb.model.err.LoginException;
 import jsfdb.model.err.IncorrectPasswordException;
 import jsfdb.model.err.UnknownSubscriberException;
 
 public class LoginInfoBean extends LoginInfo {
 public String loginAction() {
 SubscriberBean subscriber
 = (SubscriberBean) ViewUtils.eval("#{subscriber}");
 String adminEmail
 = (String) ViewUtils.eval("#{initParam.adminEmail}");
 try {
 Subscriber selectedSubscriber
 = ModelUtils.getSubscriberDAO().select(this);
 ModelUtils.copy(selectedSubscriber, subscriber);
 subscriber.setLoggedIn(true);
 if (subscriber.getEmail().equals(adminEmail))
 return "list";
 else
 return "profile";
 } catch (LoginException x) {
 ViewUtils.addExceptionMessage(x);
 return null;
 } catch (UnknownSubscriberException x) {
 ViewUtils.addExceptionMessage(x);
 return null;
 } catch (IncorrectPasswordException x) {
 ViewUtils.addExceptionMessage(x);
 return null;
 }
 }
 
 }
 

The ViewUtils class provides utility methods that evaluate JSF expressions and add error messages to a JSF context:

 
 package jsfdb.view;
 
 import javax.faces.application.FacesMessage;
 import javax.faces.context.FacesContext;
 import javax.faces.el.ValueBinding;
 
 import java.util.ResourceBundle;
 
 public class ViewUtils {
 public static Object eval(String expr) {
 FacesContext context
 = FacesContext.getCurrentInstance();
 ValueBinding binding
 = context.getApplication().createValueBinding(expr);
 return binding.getValue(context);
 }
 
 public static void addErrorMessage(FacesContext context,
 String compId, String messageId) {
 ResourceBundle bundle = ResourceBundle.getBundle(
 context.getApplication().getMessageBundle());
 FacesMessage message = new FacesMessage(
 bundle.getString(messageId));
 message.setSeverity(FacesMessage.SEVERITY_ERROR);
 context.addMessage(compId, message);
 }
 
 public static void addExceptionMessage(Exception x) {
 FacesContext context
 = FacesContext.getCurrentInstance();
 FacesMessage message
 = new FacesMessage(x.getMessage());
 message.setSeverity(FacesMessage.SEVERITY_FATAL);
 context.addMessage(null, message);
 }
 
 }
 

The copy() method of ModelUtils gets the properties of a source bean and sets the properties of another bean, using the JavaBeans Introspection API and the Java Reflection API:

 
 package jsfdb.model;
 ...
 import java.beans.BeanInfo;
 import java.beans.Introspector;
 import java.beans.PropertyDescriptor;
 import java.beans.IntrospectionException;
 import java.lang.reflect.Method;
 import java.lang.reflect.InvocationTargetException;
 ...
 public class ModelUtils {
 ...
 public static void copy(Object source, Object dest) {
 try {
 Class sourceClass = source.getClass();
 Class destClass = dest.getClass();
 BeanInfo info = Introspector.getBeanInfo(sourceClass);
 PropertyDescriptor props[]
 = info.getPropertyDescriptors();
 Object noParams[] = new Object[0];
 Object oneParam[] = new Object[1];
 for (int i = 0; i < props.length; i++) {
 Method getter = props[i].getReadMethod();
 if (getter == null)
 continue;
 Object value = getter.invoke(source, noParams);
 Method setter = props[i].getWriteMethod();
 if (setter != null && sourceClass != destClass)
 try {
 setter = destClass.getMethod(
 setter.getName(),
 setter.getParameterTypes());
 } catch (NoSuchMethodException x) {
 setter = null;
 }
 if (setter != null) {
 oneParam[0] = value;
 setter.invoke(dest, oneParam);
 }
 }
 } catch (IntrospectionException x) {
 log(x);
 throw new InternalError(x.getMessage());
 } catch (IllegalAccessException x) {
 log(x);
 throw new InternalError(x.getMessage());
 } catch (IllegalArgumentException x) {
 log(x);
 throw new InternalError(x.getMessage());
 } catch (SecurityException x) {
 log(x);
 throw new InternalError(x.getMessage());
 } catch (InvocationTargetException x) {
 log(x.getTargetException());
 throw new InternalError(
 x.getTargetException().getMessage());
 }
 }
 
 }
 

The copy() method works well if all properties are primitives or immutable objects, such as String . If a bean contains mutable objects such as sub-beans or collections, you should clone the values of those properties, and store their clones into the dest bean.

Inserting a New Row (Subscribe Action) The insert() method of JDBCSubscriberDAO executes an SQL statement that inserts a new subscriber into the database:

 
 
 INSERT INTO subscribers ( 
 subscriberEmail, 
 subscriberPassword, 
 subscriberName, 
 managerFlag, 
 developerFlag, 
 administratorFlag, 
 subscriptionType ) 
 VALUES (?, ?, ?, ?, ?, ?, ?)
 

When using TopLink, the SQL statements are generated automatically from the O-R mappings that you have to define with the TopLink Mapping Workbench tool. The insert() method of TopLinkSubscriberDAO acquires a unit of work, creates a new Subscriber instance, sets its properties, registers the new object and calls commit() :

 
 package jsfdb.model.dao;
 ...
 public class TopLinkSubscriberDAO implements SubscriberDAO {
 ...
 public void insert(Subscriber subscriber)
 throws SubscribeException {
 try {
 UnitOfWork uow = acquireUnitOfWork();
 Subscriber s = new Subscriber();
 ModelUtils.copy(subscriber, s);
 uow.registerObject(s);
 uow.commit();
 } catch (Exception x) {
 ModelUtils.log(x);
 throw new SubscribeException();
 }
 }
 ...
 }
 

The subscribeAction() method of SubscriberBean calls insert() , sets the loggedIn flag to true , and returns the subscribed outcome if no error occurs:

 
 package jsfdb.view;
 ...
 public class SubscriberBean extends Subscriber {
 ...
 public final static String SELECT_NEWSLETTER_ID
 = "jsfdb.view.SubscriberBean.SELECT_NEWSLETTER";
 ...
 public String subscribeAction() {
 if (countNewsletters() == 0) {
 ViewUtils.addErrorMessage(
 FacesContext.getCurrentInstance(),
 null, SELECT_NEWSLETTER_ID);
 return null;
 }
 try {
 ModelUtils.getSubscriberDAO().insert(this);
 setLoggedIn(true);
 return "subscribed";
 } catch (SubscribeException x) {
 ViewUtils.addExceptionMessage(x);
 return null;
 }
 }
 ...
 }
 

Updating an Existing Row (Profile Action) The update() method of JDBCSubscriberDAO executes an SQL statement that updates the profile of an existing subscriber:

 
 UPDATE subscribers SET 
 subscriberPassword=?, 
 subscriberName=?, 
 managerFlag=?, 
 developerFlag=?, 
 administratorFlag=?, 
 subscriptionType=? 
 WHERE subscriberEmail=?
 

The update() method of TopLinkSubscriberDAO acquires a unit of work, calls the private read() method to obtain the bean with the given email, updates the bean properties, and calls commit() :

 
 package jsfdb.model.dao;
 ...
 public class TopLinkSubscriberDAO implements SubscriberDAO {
 ...
 public void update(Subscriber subscriber)
 throws ProfileException {
 try {
 UnitOfWork uow = acquireUnitOfWork();
 Subscriber s = read(uow, subscriber.getEmail());
 ModelUtils.copy(subscriber, s);
 uow.commit();
 } catch (Exception x) {
 ModelUtils.log(x);
 throw new ProfileException();
 }
 }
 ...
 }
 

The profileAction() method of SubscriberBean calls update() , and returns the null outcome:

 
 package jsfdb.view;
 ...
 public class SubscriberBean extends Subscriber {
 ...
 public final static String SELECT_NEWSLETTER_ID
 = "jsfdb.view.SubscriberBean.SELECT_NEWSLETTER";
 ...
 public String profileAction() {
 if (!loggedIn)
 return "login";
 if (countNewsletters() == 0) {
 ViewUtils.addErrorMessage(
 FacesContext.getCurrentInstance(),
 null, SELECT_NEWSLETTER_ID);
 return null;
 }
 try {
 ModelUtils.getSubscriberDAO().update(this);
 return null;
 } catch (ProfileException x) {
 ViewUtils.addExceptionMessage(x);
 return null;
 }
 }
 ...
 }
 

Deleting a Row (Unsubscribe Action) The delete() method of JDBCSubscriberDAO executes an SQL statement that deletes a subscriber from the database:

DELETE FROM subscribers WHERE subscriberEmail=?

The delete() method of TopLinkSubscriberDAO acquires a unit of work, calls the private read() method to obtain the bean with the given email, deletes the bean, and calls commit() :

 
 package jsfdb.model.dao;
 ...
 public class TopLinkSubscriberDAO implements SubscriberDAO {
 ...
 public void delete(Subscriber subscriber)
 throws UnsubscribeException {
 try {
 UnitOfWork uow = acquireUnitOfWork();
 Subscriber s = read(uow, subscriber.getEmail());
 uow.deleteObject(s);
 uow.commit();
 } catch (Exception x) {
 ModelUtils.log(x);
 throw new UnsubscribeException();
 }
 }
 
 }
 

The unsubscribeAction() method of SubscriberBean calls delete(), and returns the unsubscribed outcome if no error occurs:

 
 package jsfdb.view;
 ...
 public class SubscriberBean extends Subscriber {
 ...
 public String unsubscribeAction() {
 if (!loggedIn)
 return "login";
 try {
 ModelUtils.getSubscriberDAO().delete(this);
 return "unsubscribed";
 } catch (UnsubscribeException x) {
 ViewUtils.addExceptionMessage(x);
 return null;
 }
 }
 ...
 }
 

Creating the TopLink Project The following instructions describe how to create a TopLink project using the Mapping Workbench.

Step 1: Launch OracleAS TopLink Mapping Workbench . Then, select File and New Project... from the main menu. In the Create New Project dialog, provide the name of an existing database (e.g. orcl ) and click OK .

Step 2: The Mapping Workbench will show a window that lets you save the project. Select a directory, provide a file name (e.g. jsfdb ), and click Save .

Step 3: Select the database in the left Navigator pane, click Add... , enter a login label (e.g. orclLogin), and click OK .

Step 4: In the main window, select the newly created login, and provide the name of a JDBC driver class, the database URL, the user name and the password. Don't forget to select the Save Password checkbox.

Step 5: Right-click on the database name in the Navigator pane and click Log In to Database .

Step 6: Right-click again on the database name in the Navigator pane and click Add or Update Existing Tables from Database .

Step 7: In the Import Tables from Database window, enter SUBSCRIBERS in the Table Name Pattern field, click Get Table Names , select the SUBSCRIBERS table from the left Available Tables pane, add it to the right Selected Tables pane, and click OK .

Step 8: Select the project ( jsfdb ) in the Navigator pane, make sure that General is the current tab of the right pane, click Add Entries... , navigate in the directory tree that shows up, select the directory that contains the compiled classes of the Web application, and then click OK . The selected directory will be added to the Class Path list in the main window.

Step 9: Right-click again on the project in the Navigator pane and click Add or Refresh Classes...

Step 10: Select the LoginInfo and Subscriber classes in the left Available Packages/Classes pane of the Select Classes window.

Step 11: Move the LoginInfo and Subscriber classes into the right Selected Classes pane and click OK . Only the Subscriber class will be mapped to a table, but we need to add LoginInfo too since it's the superclass of Subscriber .

Step 12: Select the Subscriber class in the Navigator pane of the main window. In the right Editor pane, make sure that Descriptor is the current tab, and select SUBSCRIBERS in the Associated Table list.

Step 13: Right-click on the Subscriber class in the Navigator pane, select Map Inherited Attributes and click To Superclass .

Step 14: Right-click on each bean property of the Subscriber class (e.g. administrator ), select Map As... and click Direct to Field .

Step 15: Select each bean property of the Subscriber class (e.g. email ) in the Navigator pane and set the corresponding Database Field in the right Editor pane (e.g. SUBSCRIBEREMAIL ).

Step 16: The LoginInfo class must be removed from the project because it is not mapped to any table. Right-click on the LoginInfo in the Navigator pane, and click Remove Class . When the Remove Descriptors dialog shows up, click Remove .

Step 17: In the main menu, select File, Export, Project Deployment XML... or just use CTRL+D . Enter JSFDBProject.xml and click OK. Then, select a directory where you want to save the XML file.

 

Step 18: Save the project with File and Save , or with CTRL+S . In addition to storing the project settings into the jsfdb.mwp file, the saving operation will create several XML files into the class, descriptor and table subdirectories. Now you may close OracleAS TopLink Mapping Workbench .

Configuring the TopLink Session The following instructions describe how to configure a TopLink session using the Sessions Editor.

Step 1: Launch OracleAS TopLink Sessions Editor . Select File and New... from the main menu (or use CTRL+N ). In the New dialog, let the sessions.xml file name unchanged, click the Browse button to select the Location for this file, enter JSFDBSession in the Session Name field and click OK .

Step 2: Select the JSFDBSession in the Navigator pane, make sure that General is the current tab of the Editor pane, and enter JSFDBProject.xml in the XML field of Project Type group.

Step 3: Select the Logging tab of the Editor pane, and select True in the Enable Logging list.

Step 4: Select the Login tab of the Editor pane, select the Database Platform checkbox, select Oracle in the corresponding list, and enter java:comp/env/jdbc/OracleDS in the Data Source field.

Step 5: Select File and Save from the main menu (or use CTRL+S ) to save the sessions.xml file. Now you may close OracleAS TopLink Sessions Editor .

JSF Views and Data Validation

The example Web application contains a subscription page ( subscribe.jsp ), a login page ( login.jsp ), a profile-editing page (profile.jsp ), an unsubscription page ( unsubscribe.jsp ), two confirmation pages ( subscribed.jsp and unsubscribed.jsp), a page that lists all subscribers (list.jsp ), and a logout page ( logout.jsp ). Some of the forms created by the JSF pages trigger actions that execute the basic database operations ( INSERT, SELECT, UPDATE and DELETE ) with the help of the DAO methods from the previous section.

Using JSF in Web Pages JSF defines two standard tag libraries (Core and HTML) that you have to declare in your JSP pages with the <%@taglib%> directive. The JSF Core library contains tags that don't depend on any markup language, while the JSF HTML library was designed for pages that are viewed in a Web browser. The standard prefixes of the two tag libraries are f for the JSF Core and h for the JSF HTML. All JSF tags must be nested inside a <f:view> element. The <f:view> tag allows the JSF framework to save the state of the UI components as part of the response to a HTTP request.

The pages of the example Web application use JSF and the JSTL Core library to create the HTML headers:

 
 <%@ taglib prefix="c" uri="#" %>
 <%@ taglib prefix="f" uri="#" %>
 <%@ taglib prefix="h" uri="#" %>
 <f:view> 
 <f:loadBundle var="labels" basename="jsfdb.view.res.Labels"/>
 <c:set var="stylesheet" 
 value="${pageContext.request.contextPath}/stylesheet.css"/> 
 <html> 
 <head> 
 <title><h:outputText value="#{labels.subscribe}"/></title> 
 <link rel="stylesheet" type="text/css" 
 href="<c:out value='${stylesheet}'/>">
 </head> 
 <body> 
 ... 
 </body> 
 </html>
 </f:view>
 
 

The <h:outputText> tag of JSF is very similar to the <c:out> tag of JSTL, but there are some differences. For example, <h:outputText> uses the JSF EL, while <c:out> uses the expression language created initially for JSTL 1.0 and then adopted by JSP 2.0. In the preceeding code fragment, the <f:loadBundle> tag loads a resource bundle that contains the labels and titles of all JSF pages:

 
 
 subscribe=Subscribe
 subscribed=Subscribed
 login=Login
 logout=Logout
 profile=Profile
 update=Update
 unsubscribe=Unsubscribe
 unsubscribed=Unsubscribed
 cancel=Cancel
 email=Email
 password=Password
 passwordDetail=Useful to change your profile
 name=Name
 newsletters=Newsletters
 manager=Manager
 developer=Developer
 administrator=Administrator
 subscriptionType=Subscription Type
 daily=Daily
 weekly=Weekly
 monthly=Monthly
 list=List
 

Using the JSF Tags to Build Forms Many of the application's Web pages use JSF to create forms. For example, login.jsp uses and that render two HTML elements of types text and password , respectively. Each form field has a label rendered with . Any error messages are rendered with and . The tag verifies the length of the strings that the user enters in the form fields. The renders a HTML submit button. All these tags are placed within a element that renders the HTML

and
tags. Besides the HTML form, the login.jsp page provides a link to the subscription form, using and to render the HTMLelement and its contents:

 
 
 <!-- login.jsp -->
 <%@ taglib prefix="c" uri="#" %> 
 <%@ taglib prefix="f" uri="#" %> 
 <%@ taglib prefix="h" uri="#" %>
 <c:remove var="subscriber" scope="session"/>
 <f:view> 
 ... 
 <h1><h:outputText value="#{labels.login}"/></h1>
 <h:outputLink value="subscribe.faces"> 
 <h:outputText value="#{labels.subscribe}"/> 
 </h:outputLink> 
 <h:form id="login"> 
 <h:messages globalOnly="true" styleClass="message"/> 
 <p><h:outputLabel for="email" 
 value="#{labels.email}"/> 
 <h:message for="email" styleClass="message"/>
 <br> 
 <h:inputText id="email" required="true"
 value="#{loginInfo.email}" 
 size="40" maxlength="80"> 
 <f:validateLength minimum="1" maximum="80"/> 
 </h:inputText> 
 <p><h:outputLabel for="password" 
 value="#{labels.password}"/> 
 <h:message for="password" styleClass="message"/><br>
 <h:inputSecret id="password" required="true" 
 value="#{loginInfo.password}" 
 size="10" maxlength="20"> 
 <f:validateLength minimum="6" maximum="20"/> 
 </h:inputSecret> 
 <p><h:commandButton id="command" 
 value="#{labels.login}" 
 action="#{loginInfo.loginAction}"/> 
 </h:form> 
 ... 
 </f:view>
 

When the login.jsp page is executed, JSF creates an instance of the LoginInfoBean , whose properties are bound to the UI components. The Web browser submits the user's email address and password to the server when the Login button is clicked. The JSF framework verifies if the user provided values for both required fields. If the lengths of the string values pass the validation performed by <f:validateLength> , JSF sets the bean properties and calls the loginAction() method, which authenticates the user.

Note that login.jsp uses the <c:remove> tag of JSTL to remove any subscriber bean from the session scope. If the user accesses for the first time the Web application or if his previous session has expired, such a bean instance doesn't exists and <c:remove> has no effect. Otherwise, this tag cleans the session scope. Since the subscriber bean is referred in an EL expression evaluated in loginAction() , JSF will create an instance of SubscriberBean as specified in the faces-config.xml file. TheloginAction() method reads the subscriber's profile from the database and sets the bean's properties. The subscriber bean will be used by other JSF pages and will be kept in the session scope until logout.jsp removes it:

 
 <!-- logout.jsp --> 
 ... 
 <f:view> 
 ... 
 </f:view>
 <c:remove var="subscriber" scope="session"/>
 

The subscribe.jsp and profile.jsp pages use JSF to render additional form elements, such as checkboxes and lists. The <h:panelGrid> and <h:panelGroup> tags of JSF are used to render a HTML table whose cells contain the three checkboxes of the subscription form. The <h:selectBooleanCheckbox> tag renders an <input> element of type checkbox :

 
 <!-- subscribe.jsp --> 
 ... 
 <f:view> 
 ... 
 <h:form id="subscribe"> 
 ... 
 <p><h:outputText value="#{labels.newsletters}"/> 
 <h:message for="newsletters" styleClass="message"/><br> 
 <h:panelGrid id="newsletters" 
 columns="3" border="0" cellspacing="5">
 <h:panelGroup> 
 <h:selectBooleanCheckbox id="manager" 
 value="#{subscriber.manager}"/> 
 <h:outputLabel for="manager" 
 value="#{labels.manager}"/> 
 </h:panelGroup> 
 <h:panelGroup> 
 <h:selectBooleanCheckbox id="developer"
 value="#{subscriber.developer}"/> 
 <h:outputLabel for="developer" 
 value="#{labels.developer}"/> 
 </h:panelGroup> 
 <h:panelGroup> 
 <h:selectBooleanCheckbox id="administrator" 
 value="#{subscriber.administrator}"/> 
 <h:outputLabel for="administrator" 
 value="#{labels.administrator}"/> 
 </h:panelGroup> 
 </h:panelGrid> 
 ... 
 </h:form> 
 ... 
 </f:view>
 

The <h:selectOneMenu> and <f:selectItem> tags render a drop-down list that lets users select the type of their subscription: daily, weekly or monthly:

 
 
 <!-- subscribe.jsp -->
 ...
 <f:view> 
 ... 
 <h:form id="subscribe"> 
 ... 
 <p><h:outputLabel for="subscriptionType"
 value="#{labels.subscriptionType}"/> 
 <h:message for="subscriptionType" 
 styleClass="message"/><br> 
 <h:selectOneMenu id="subscriptionType" 
 value="#{subscriber.subscriptionType}" 
 required="true"> 
 <f:validateLongRange minimum="1" maximum="3"/>
 <f:selectItem itemLabel="#{labels.daily}" 
 itemValue="#{subscriber.dailyConst}"/> 
 <f:selectItem itemLabel="#{labels.weekly}"
 itemValue="#{subscriber.weeklyConst}"/> 
 <f:selectItem itemLabel="#{labels.monthly}" 
 itemValue="#{subscriber.monthlyConst}"/> 
 </h:selectOneMenu> 
 ... 
 </h:form> 
 ... 
 </f:view> 
 

The subscribeAction() method (used in subscribe.jsp ) saves the profile of the new subscriber into the database. The profile.jsp page presents the preferences maintained by the subscriber bean and lets the user change his profile (the profileAction() method updates the information stored in the database).

The form rendered by unsubscribe.jsp contains two submit buttons that are bound to different action methods:

 
 
 <!-- unsubscribe.jsp --> 
 ... 
 <f:view> 
 ... 
 <h:form id="unsubscribe"> 
 ... 
 <p><h:commandButton id="command" 
 value="#{labels.unsubscribe}"
 action="#{subscriber.unsubscribeAction}"/> 
 <h:commandButton id="cancel" 
 value="#{labels.cancel}" 
 action="#{subscriber.cancelAction}"/> 
 </h:form> 
 ... 
 </f:view>
 

The unsubscribeAction() method deletes the subscriber's profile from the database. The code of all action methods is discussed in the previous section ( JavaBeans and Data Access Objects ).

Using Standard and Custom Validators JSF offers a few validator tags, such as <f:validateLength> and <f:validateLongRange> that are used in the JSF pages of the example application. In addition, many HTML tags support a required attribute that lets you specify if the user must always provide a value for the UI component. In most cases, however, you need application-specific validations. You can build custom validation tags, but it is easier to use the validator attribute, which is supported by many standard JSF tags. You can use this attribute to specify a method that will be used to verify the value entered by the user:

 
 <!-- subscribe.jsp --> 
 ... 
 <f:view> 
 ... 
 <h:form id="subscribe">
 ... 
 <p><h:outputLabel for="email"
 value="#{labels.email}"/> 
 <h:message for="email" styleClass="message"/><br>
 <h:inputText id="email" required="true" 
 validator="#{subscriber.emailValidator}" 
 value="#{subscriber.email}" 
 size="40" maxlength="80"> 
 <f:validateLength minimum="1" maximum="80"/> 
 </h:inputText> 
 ... 
 </h:form> 
 ... 
 </f:view>
 

The emailValidator() method of SubscriberBean verifies if the email address contains the @ character:

 
 package jsfdb.view;
 ...
 import javax.faces.component.UIComponent;
 import javax.faces.component.EditableValueHolder;
 import javax.faces.context.FacesContext;
 
 public class SubscriberBean extends Subscriber {
 public final static String INVALID_EMAIL_ID
 = "jsfdb.view.SubscriberBean.INVALID_EMAIL";
 ...
 public void emailValidator(FacesContext context,
 UIComponent comp, Object value) {
 String email = (String) value;
 if (email.indexOf("@") == -1) {
 String compId = comp.getClientId(context);
 ViewUtils.addErrorMessage(
 context, compId, INVALID_EMAIL_ID);
 ((EditableValueHolder) comp).setValid(false);
 }
 }
 ...
 }
 

The error message is rendered in the JSF page with <h:message/> and is obtained from the application message bundle:

 
 jsfdb.view.SubscriberBean.INVALID_EMAIL=invalid
 jsfdb.view.SubscriberBean.SELECT_NEWSLETTER=\
 You must subscribe to at least one newsletter.
 javax.faces.component.UIInput.REQUIRED=required
 javax.faces.validator.LengthValidator.MAXIMUM=\
 may contain maximum {0} characters
 javax.faces.validator.LengthValidator.MINIMUM=\
 must contain minimum {0} characters
 

In addition to the application-specific messages, this resource bundle contains replacements for several default JSF messages. For example, the javax.faces.component.UIInput.REQUIRED key is followed by the error message that is rendered when the user doesn't provide a value for a required form element. The application message bundle must be configured in faces-config.xml :

 
 
 <faces-config>
 
 <application>
 <locale-config>
 <default-locale>en</default-locale>
 </locale-config>
 <message-bundle>jsfdb.view.res.Messages</message-bundle>
 </application>
 ...
 </faces-config>
 

Validator tags and methods verify the value of a single UI component. Sometimes, however, you have to validate a group of components together. This can be done in an action method. For example, any checkbox of the subscription form may be left unchecked, but the user should subscribe to at least one newsletter. The subscribeAction() method signals an error if countNewsletters() returns 0 :

 
 package jsfdb.view;
 ...
 public class SubscriberBean extends Subscriber {
 ...
 public final static String SELECT_NEWSLETTER_ID
 = "jsfdb.view.SubscriberBean.SELECT_NEWSLETTER";
 ...
 public String subscribeAction() {
 if (countNewsletters() == 0) {
 ViewUtils.addErrorMessage(
 FacesContext.getCurrentInstance(),
 null, SELECT_NEWSLETTER_ID);
 return null;
 }
 ...
 }
 ...
 }
 

Error messages that aren't attached to any particular UI component can be rendered in a JSF page with <h:messages globalOnly="true"/> .

Using JSF with the SQL Tags of JSTL The JSF API defines an abstract data model for tables ( javax.faces.model.DataModel ), and provides implementations that wrap arrays, lists, JDBC result sets, the results from the <sql:query> tag of JSTL, and scalars. The JSF data models have methods that let you access the row data, but it is up to the concrete model implementations how the rows are represented. When using arrays or lists, each element is a row. When using result sets, each row is represented as a java.util.Map whose keys are the table columns. Since the row model is not restricted, it is the Web developer's responsibility to define how the columns are rendered in a JSF page using <h:column> tags that must be nested inside a <h:dataTable> element.

The web.xml file of the example application declares a data source with <resource-ref> and configures it as the default JSTL data source with a context parameter named javax.servlet.jsp.jstl.sql.dataSource . Another parameter ( adminEmail ) specifies an email address that will be compared (in list.jsp ) with the email property of the subscriber that is logged in:

 
 
 <web-app>
 
 <context-param>
 <param-name>adminEmail</param-name>
 <param-value>admin@localhost</param-value>
 </context-param>
 
 <context-param>
 <param-name>javax.servlet.jsp.jstl.sql.dataSource</param-name>
 <param-value>jdbc/OracleDS</param-value>
 </context-param>
 ...
 <resource-ref>
 <res-ref-name>jdbc/OracleDS</res-ref-name>
 <res-type>javax.sql.DataSource</res-type>
 <res-auth>Container</res-auth>
 </resource-ref>
 ...
 </web-app>
 

Only the user whose email address is specified in web.xml can execute the list.jsp page. Therefore, you have to subscribe as admin@localhost , after deploying the application. When you log in using the adminEmail address, the login.jsp page forwards the request to list.jsp . This authentication mechanism is used in the example application in order to show how an action method, such as loginAction() , can return different outcomes ( list or profile ) depending on a programmable condition. A real application would use the standard HTTP-based or from-based authentication to verify the identity of an administrator.

The list.jsp page executes an SQL query with the JSTL tag, which creates a subscriberList variable that holds the result set. This is passed to <h:dataTable> , which iterates over the rows of the result set, invoking the tag body for each row. Each <h:column> tag contains a <h:outputText value="#{row...}"/> tag that renders the value of the current cell, and a facet that is rendered as part of the table's header:

 
 
 <!-- list.jsp --> 
 <%@ taglib prefix="c" uri="#" %> 
 <%@ taglib prefix="sql" uri="#" %>
 <%@ taglib prefix="f" uri="#" %>
 <%@ taglib prefix="h" uri="#" %> 
 <c:if test="${subscriber == null || !subscriber.loggedIn}"> 
 <c:redirect url="/login.faces"/> 
 </c:if> 
 <c:if test="${subscriber.email != initParam.adminEmail}">
 <c:redirect url="/profile.faces"/> 
 </c:if> 
 <sql:query var="subscriberList" scope="request">
 SELECT * FROM subscribers ORDER BY subscriberEmail </sql:query> 
 <f:view> 
 ... 
 <h:form id="list"> 
 <h:dataTable id="table" var="row" 
 value="#{subscriberList}" 
 border="1" cellpadding="5"> 
 <h:column> 
 <f:facet name="header"> 
 <h:outputText value="#{labels.email}"/>
 </f:facet> 
 <h:outputText value="#{row.subscriberEmail}"/> 
 </h:column> 
 <h:column> 
 <f:facet name="header"> 
 <h:outputText value="#{labels.password}"/> 
 </f:facet> 
 <h:outputText value="#{row.subscriberPassword}"/>
 </h:column> 
 ... 
 </h:dataTable> 
 </h:form> 
 </body> 
 </html> 
 </f:view>
 

The <sql:query> tag uses the request scope in the list.jsp page because JSF doesn't support the JSP page scope. If you forget to specify a scope supported by JSF, the JSTL tag will use the default page scope, and the tags of the two tag libraries will not be able to communicate. Therefore, make sure that you use one of the common scopes ( request, session or application ) when using the tags of JSF and JSTL together.

Summary

This article has presented a JSF-based Web application that accesses a relational database using the DAO pattern, JDBC, SQL, TopLink, and JSTL. You can apply the techniques discussed here when developing real-world applications and you may test the examples of this article with the Oracle Database, Oracle Application Server Containers for J2EE, the TopLink O-R mapping framework, and the JSF Reference Implementation.

Andrei Cioroianu ( devtools@devsphere.com ) is the founder of Devsphere , a provider of Java frameworks, XML consulting, and Web development services. Andrei has written many Java articles published by Oracle Technology Network, ONJava , JavaWorld , and Java Developer's Journal . He also coauthored the books Java XML Programmer's Reference and Professional Java XML (both from Wrox Press).