A first look at JavaServer Faces, Part 1

Learn how to implement Web-based user interfaces with JSF

Recently, I had the good fortune of training and mentoring a group of novice Java developers as we implemented a complex Web application using Struts, Enterprise JavaBeans (EJB), servlets, JavaServer Pages (JSP), and the JSP Standard Tag Library (JSTL). As it turned out, the project was a success; it came in under budget and on time, and had numerous features not originally envisioned. As you might imagine, we faced many technical challenges along the way; the most significant were:

  1. Implementing custom components, which included a tree/table viewer and a query builder that lets users dynamically add and remove fundamental components such as text fields and drop-down lists used to build database queries.
  2. Supporting hand-held devices, such as PDAs and radio frequency devices.
  3. Lack of an IDE for effective rapid application development (RAD).

Implementing custom components and supporting hand-held devices—especially the latter—consumed a great deal of our time and effort. Also, although some of the developers used the Eclipse open source IDE, we lacked an effective RAD tool for implementing the Web application's user interface.

Unless you've been living in a cave for the past few years, I'm sure you're aware that tools exist for creating custom Web components and supporting markup languages other than HTML, all of which are wrapped up in a very nice IDE. That software, of course, is Microsoft's .Net with WebForms; the IDE is Visual Studio.

In spite of those attractive .Net features, the company I was working for—like many software development companies nowadays—opted to go with the Java 2 Platform, Enterprise Edition (J2EE) because of its platform and vendor independence and the wealth of available open source software for Java and J2EE.

Wouldn't it be nice if you could take advantage of Java and .Net's best features, platform and vendor independence, open source products such as Ant and log4j, and the ability to easily create custom Web components and render them to multiple devices, all wrapped up in a killer IDE? That's the promise of JavaServer Faces.

Read the whole series, "A First Look at JavaServer Faces:"

Note: You can download this article's source code from Resources.

What is JavaServer Faces?

JavaServer Faces (JSF) is an application framework for creating Web-based user interfaces. If you are familiar with Struts (a popular open source JSP-based Web application framework) and Swing (the standard Java user interface framework for desktop applications), think of JavaServer Faces as a combination of those two frameworks. Like Struts, JSF provides Web application lifecycle management through a controller servlet; and like Swing, JSF provides a rich component model complete with event handling and component rendering.

In a nutshell, JSF eases Web-based application development because it:

  • Lets you create user interfaces from a set of standard, reusable server-side components
  • Provides a set of JSP tags to access those components
  • Transparently saves state information and repopulates forms when they redisplay
  • Provides a framework for implementing custom components
  • Encapsulates event handling and component rendering so you can use standard JSF components or custom components to support markup languages other than HTML
  • Lets tool vendors develop IDEs for a standard Web application framework

Besides being a conceptual combination of Struts and Swing, JSF is a direct competitor to Microsoft's WebForms. The frameworks are very similar, both in concept and implementation. And because JSF represents a standard for Java-based Web application frameworks, tool vendors can concentrate on developing IDEs for JSF instead of developing an IDE for one of approximately 35 existing Java-based Web application frameworks, including Struts.

Note: Struts developers needn't worry; although JSF and Struts have much in common, JSF will not make Struts obsolete. See Resources for a discussion of an integration strategy for Struts and JavaServer Faces.

Currently, JSF is an early access (EA) release, and, as a result, is somewhat immature. The specification leaves some functionality unspecified, and the specification and reference implementation are currently out of sync, with the former specifying new syntaxes and functionality not yet implemented in the latter. On the other hand, JSF is mature enough for you to write code against—although much of that code is guaranteed to be obsolete (see the disclaimer below)—and the reference implementation is fairly complete and relatively bug-free. You can download the JSF specification, the reference implementation, two sample applications, and a JSF tutorial from Resources.

The two articles in this series provide a code-intensive introduction to JavaServer Faces. In this article, I begin with a short discussion of the JSF lifecycle and then dive into some example code that illustrates implementation of Web-based user interfaces with JSF and how you can take advantage of built-in validation. In Part 2, I will explain more advanced JSF concepts such as: implementing custom validation; using model objects; internationalization; creating custom components; and finally, delegating event handling and rendering so you can use components to generate markup languages other than HTML.

Disclaimer: The code discussed in this article was written against the EA2 JSF reference implementation. As mentioned above, the specification and reference implementation are in a state of flux, and therefore, the code in this article is guaranteed to be obsolete in the near future; however, the code works as advertised with the EA2 reference implementation and was tested with both Tomcat 4.0.6 (the latest production release of Tomcat) and Resin 2.1.6. Furthermore, you can read the JSF specification until the cows come home, but to really grasp the concepts, you must ruminate over some code.

The JavaServer Faces lifecycle

JSF handles HTTP requests with seven distinct phases, as shown in Figure 1. The normal flow of control is shown with solid lines, whereas dashed lines show alternate flows depending on whether a component requests a page redisplay or validation or conversion errors occur.

Figure 1. The JavaServer Faces lifecycle. Click on thumbnail to view full-size image.

The Reconstitute Request Tree phase creates a component tree for the requested page. If that page previously displayed and JSF saved the page's state information, the state information is added to the request. This means that JSF automatically retains form information when a form redisplays; for example, when a user does not correctly fill out a form. This handy feature is a fundamental capability provided by Microsoft's WebForms, but is otherwise absent from J2EE.

During the Apply Request Values phase, the JSF implementation iterates over the components in the component tree and calls each component's decode() method. That method extracts information from the request and stores it in the component. Optionally, components may delegate decoding to a renderer.

In addition to decoding request information during the Apply Request Values phase, components or their renderers may create request events. Typically, request events signal a visual change for one or more components; for example, clicking on a graphic in a tree control may expand a branch of the tree. Alternatively, an event in one component may update the visual representation of another component; for example, clicking on a leaf node in a tree may cause an associated list to change its contents and redisplay. In either situation, a request event is generated and added to the JSF context.

Request events, which are generated during the Apply Request Values phase, are handled during the Handle Request Events phase. During the Handle Request Events phase, the JSF implementation calls the processEvents() method for each component that has one or more request events. Components may handle request events themselves, or they may choose to delegate event handling to an event handler. The processEvents() method is a boolean() method. If that method returns false, lifecycle processing advances to the Process Validations phase; otherwise, lifecycle processing advances directly to the Render Response phase.

During the Reconstitute Request Tree phase, the JSF implementation may register one or more validators for any of the components in the component tree. In the Process Validations phase, the JSF implementation invokes the validate() method for each validator. Validators perform correctness checks and return a boolean value from their validate() method; if that method returns true, the JSF lifecycle proceeds normally; otherwise, the JSF implementation invokes the Render Response phase directly.

Each JSF user interface component can be associated with a field in a Java object (known as a model object). During the Update Model phase, component values are copied to the component's model object. A component's updateModel() method carries out that data transfer. Conversion errors can occur during this phase because request parameters are strings, but model values can represent any type of Java object. If a conversion error occurs, the JSF implementation invokes the Render Response phase directly.

In a JSF application, if you submit a form or click on a link (both of which must be represented by JSF components), the JSF implementation creates a form event or a command event, respectively. Those events are handled in the Invoke Application phase by an application-specific handler. Typically, those handlers specify a URL, and the JSF implementation forwards the request to that URL. Currently, application-specific handlers handle form and command events in a single method, typically with a switch statement. The JSF expert group is aware of this approach's ugliness, and therefore, it's almost certain to change in the JavaServer Faces 1.0 release.

Finally, the Render Response phase creates a response component tree and forwards the response. When a user submits a form, clicks on a link, or otherwise generates a request, the cycle starts anew.

Now that we have a general overview of JavaServer Faces and a rudimentary understanding of the JSF lifecycle, let's take a look at some code.

A simple JavaServer Faces example

Figures 2a, 2b, and 2c show a very simple JSF application. The application's opening page contains a link that starts the application. That link points to a JSP page that displays a simple form. Because this simple application does not perform validation, you can click the Log In button without filling in the name and password fields, which will transport you to another JSP page that welcomes you to JavaServer Faces.

Figure 2a. A simple JavaServer Faces application. Click on thumbnail to view full-size image.
Figure 2b. JavaServer Faces login screen. Click on thumbnail to view full-size image.
Figure 2c. Welcome to JavaServer Faces. Click on thumbnail to view full-size image.

First, let's explore the logistics of implementing this simple application, and JavaServer Faces applications in general. JSF requires the following jar files in the WEB-INF/lib directory:

  • WEB-INF/lib/commons-beanutils.jar
  • WEB-INF/lib/commons-collections.jar
  • WEB-INF/lib/commons-digester.jar
  • WEB-INF/lib/commons-logging-api.jar
  • WEB-INF/lib/jsf-api.jar
  • WEB-INF/lib/jsf-ri.jar
  • WEB-INF/lib/jstl.jar
  • WEB-INF/lib/standard.jar

The jar files listed above are all you need for JSF applications. Even though, as we will see shortly, JSF applications typically use JSP tags implemented by the JSF implementation, there are no separate tag library descriptor (TLD) files because that information is contained in the jar files.

Here's a listing of the other files that comprise the application shown in Figure 2:

  • WEB-INF/web.xml
  • WEB-INF/classes/com/sabreware/listeners/SimpleContextListener.java
  • WEB-INF/classes/com/sabreware/appHandlers/SimpleApplicationHandler.java
  • /index.html
  • /index.jsp

Example 1 lists the deployment descriptor (WEB-INF/web.xml).

Example 1. WEB-INF/web.xml

<?xml version="1.0"?>
<!DOCTYPE web-app PUBLIC
  "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
  "http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
   <!-- Context Listener creates and sets the application handler -->
   <listener>
      <listener-class>
         com.sabreware.listeners.SimpleServletContextListener
      </listener-class>
   </listener>
   <!-- Faces Servlet -->
   <servlet>
      <servlet-name>Faces Servlet</servlet-name>
      <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
      <load-on-startup>1</load-on-startup>
   </servlet>
   <!-- Faces Servlet Mapping -->
   <servlet-mapping>
      <servlet-name>Faces Servlet</servlet-name>
      <url-pattern>/faces/*</url-pattern>
   </servlet-mapping>
   <welcome-file-list>
      <welcome-file>index.html</welcome-file>
   </welcome-file-list>
</web-app>

The deployment descriptor listed above declares four things:

  1. A servlet context listener
  2. A controller servlet
  3. A mapping for the controller servlet
  4. A welcome file

The deployment descriptor listed in Example 1 associates the JSF controller servlet with the URL /faces/*, which causes the servlet container to map all URLs that start with /faces to the JSF controller servlet. JSF uses the controller servlet to control the JSF lifecycle.

The servlet context listener is listed in Example 2.

Example 2. WEB-INF/com/sabreware/listeners/SimpleServletContextListener

package com.sabreware.listeners;
import javax.servlet.*;
import javax.faces.*;
import javax.faces.lifecycle.*;
import com.sabreware.appHandlers.SimpleApplicationHandler;
public class SimpleServletContextListener implements ServletContextListener {
   public void contextInitialized(ServletContextEvent e) {
      LifecycleFactory factory = (LifecycleFactory)
        FactoryFinder.getFactory(FactoryFinder.LIFECYCLE_FACTORY);
      Lifecycle lifecycle = factory.getLifecycle(LifecycleFactory.DEFAULT_LIFECYCLE);
      lifecycle.setApplicationHandler(new SimpleApplicationHandler());
   }
   public void contextDestroyed(ServletContextEvent e) {
      // Nothing to do here
   }
}

Servlet containers create servlet context listeners at application startup and invoke the listener's contextInitialized() method. When the application shuts down, the servlet container invokes the listener's contextDestroyed() method. The servlet context listener listed above creates an application handler and associates it with the JSF lifecycle. Application handlers handle application events and specify a URL that the JSF implementation subsequently forwards to, as illustrated by the application handler created in Example 2 and listed in Example 3.

Example 3. WEB-INF/com/sabreware/appHandlers/SimpleApplicationHandler

package com.sabreware.appHandlers;
import javax.faces.FactoryFinder;
import javax.faces.context.FacesContext;
import javax.faces.event.FacesEvent;
import javax.faces.lifecycle.ApplicationHandler;
import javax.faces.tree.TreeFactory;
public class SimpleApplicationHandler implements ApplicationHandler {
   public boolean processEvent(FacesContext context, FacesEvent facesEvent) {
      TreeFactory treeFactory = (TreeFactory)FactoryFinder.
        getFactory(FactoryFinder.TREE_FACTORY);
      context.setResponseTree(
        treeFactory.getTree(context.getServletContext(),
                            "/welcome.jsp"));
      return true;
   }
}

In the JSF lifecycle's Render Response phase, the JSF implementation forwards to a URL. That URL represents a component tree, which is known as the response tree. The application handler listed above sets the response tree to /welcome.jsp, and the JSF implementation subsequently forwards to that URL after the application handler is invoked (in the Invoke Application phase of the JSF lifecycle). Our simple example only generates one application event—a form event generated when the Log In button is activated. JSF applications can generate two types of application events: form events and command events. Command events are generated when you click on a link.

The servlet context listener listed in Example 2 and the application handler listed in Example 3 work hand in hand. The context listener creates the application handler, and the application handler specifies a URL that the JSF implementation forwards to when the login form is submitted.

The welcome file, /index.html, is listed in Example 4.

Example 4. /index.html

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
   <head>
      <title>A Simple JavaServer Faces Application</title>
   </head>
   <body>
      <font size='4'>Welcome to a simple JavaServer Faces Application</font>
      <p>
      <a href='faces/index.jsp'>Click here to start the application</a>
   <body>
</html>

The welcome file listed above creates a link to the first JSP page displayed by the application. All JSF applications must route the first JSP page displayed by the application through the JSF controller servlet. You can either provide an HTML page that contains a link to that JSP page, as illustrated in this example, or you can rely on the user to type the correct URL to start the application. Unfortunately, neither solution is very appealing; hopefully, JSF 1.0 will provide a mechanism to obviate this requirement.

The application's initial JSP page is listed in Example 5.

Example 5. /index.jsp

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
   <head>
      <title>A Simple JavaServer Faces Application</title>
   </head>
   <body>
      <%@ taglib uri="http://java.sun.com/j2ee/html_basic/" prefix="faces" %>
      <font size="4">Please enter your name and password</font>
      
      <faces:usefaces>
         <faces:form id="simpleForm" formName="simpleForm">
            <table>
               <tr>
                  <td>Name:</td>
                  <td><faces:textentry_input id="name"/></td>
               </tr>
               <tr>
                  <td>Password:</td>
                  <td><faces:textentry_secret id="password"/></td>
              </tr>
            </table>
            <p><faces:command_button id="submit" commandName="Log In"/>
         </faces:form>
      </faces:usefaces>
   </body>
</html>

The preceding JSP page is where most of the action takes place in our simple application. JSF provides JSP tags for all of the standard components that JSF supports. Those tags must be contained within the body of a <faces:usefaces> tag. The JSP page listed above uses the <faces:form> tag, which creates an HTML form, and the <faces:textentry_input> and <faces:textentry_secret> tags, which render an HTML text element and an HTML password element, respectively. The JSP page also uses the <faces:command_button> tag, which renders an HTML Submit button.

When the form in the preceding JSP page is submitted, the JSF lifecycle begins, and the application handler subsequently invokes. That handler specifies /welcome.jsp as the response tree, and the JSF implementation subsequently forwards to that JSP page, which is listed in Example 6.

Example 6. /welcome.jsp

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
   <head>
      <title>A Simple JavaServer Faces Application</title>
   </head>
   <body>
      Welcome to JavaServer Faces!
   </body>
</html>

Now that we've seen how a simple JSF application works, let's extend that application by adding field validation.

Validation with JavaServer Faces

JavaServer Faces provides built-in validation and a framework for creating custom validators. Built-in validation is discussed below; custom validation will be discussed in Part 2.

Built-in validation

JavaServer Faces provides the following built-in validators:

  • DoubleRangeValidator
  • LengthValidator
  • LongRangeValidator
  • RequiredValidator
  • StringRangeValidator

The preceding list of validators represent class names. Those classes reside in the javax.faces.validator package. The DoubleRangeValidator and LongRangeValidator validate that a request parameter (which is always a string) can convert to either a double or long, respectively, and that those values fall within a specified range. The LengthValidator checks the string length of a request parameter against minimum or maximum values. The RequiredValidator requires a non-null value for a given field. The StringRangeValidator converts a string into either a long or a double and checks that value against specified minimum or maximum values.

Figure 3 shows the length validator in action.

Figure 3. JavaServer Faces built-in validation. Click on thumbnail to view full-size image.

Figure 3 shows an error message generated by a length validator. The minimum length was specified as three, but the value entered in the corresponding field was only two characters, so a validation error and corresponding error message were generated when the field's corresponding form was submitted.

Example 7 lists the JSP page shown in Figure 3.

Example 7. Use the LengthValidator

<!DOCTYPE HTML PUBLIC '-//W3C//DTD HTML 4.0 Transitional//EN'>
<html>
   <head>
      <title>A Simple JavaServer Faces Application</title>
   </head>
   <body>
      <%@ taglib uri='http://java.sun.com/j2ee/html_basic/' prefix='faces' %>
      <font size='4'>Please enter your name and password</font>
      <faces:usefaces>
         <faces:form id='simpleForm' formName='simpleForm'>
            <table>
               <tr>
                  <td>Name:</td>
                  <td>
                     <faces:textentry_input id='name'> 
                        <faces:validator 
                          className='javax.faces.validator.LengthValidator'/>
                           <faces:attributename=
                             'javax.faces.validator.LengthValidator.MINIMUM'
                              value='3'/>
                      </faces:textentry_input>
                  </td>
                  <td>
                     <faces:validation_message componentId='name'/>
                  </td>
               </tr>
               <tr>
                  <td>Password:</td>
                  <td>
                     <faces:textentry_secret id='password'/> 
                  </td>
               </tr>
            </table>
            <p><faces:command_button id='submit' commandName='Log In'/>
         </faces:form>
      </faces:usefaces>
   </body>
</html>

As evidenced by the preceding JSP page, JSF validators are easy to use: simply add a <faces:validator> tag and one or more <faces:attribute> tags to the body of the component you want to validate. Validators store error messages in the JSF context when validation fails; you can extract those error messages with the <faces:validation_message>, which lets you specify the component to which those messages apply.

More to come

JSF represents a new paradigm for developing J2EE applications. With a well-defined request processing lifecycle, event handling, validation, and a rich component hierarchy for developing complex custom components that can write to multiple devices, JSF will greatly facilitate the development of J2EE application Web tiers.

In this article, I introduced basic JavaServer Faces concepts, including the JSF lifecycle, using JSF standard components and their corresponding JSP tags, and built-in validation. In Part 2, I will discuss more advanced JSF features, including custom validation, internationalization, using model objects, and implementing custom components.

David Geary is the author of Core JSTL Mastering the JSP Standard Tag Library (Prentice Hall, 2002; ISBN: 0131001531); Advanced JavaServer Pages (Prentice Hall, 2001; ISBN: 0130307041); and the Graphic Java series (Sun Microsystems Press). David has been developing object-oriented software with numerous object-oriented languages for 18 years. Since the GOF Design Patterns book was published in 1994, David has been an active proponent of design patterns and has used and implemented design patterns in Smalltalk, C++, and Java. In 1997, David began working full-time as an author and occasional speaker and consultant. David is a member of the expert group defining the JSP Standard Tag Library, and is a contributor to the Apache Struts JSP framework. He writes JavaWorld's Java Design Patterns column.

Learn more about this topic

Join the discussion
Be the first to comment on this article. Our Commenting Policies