JavaServer Faces, redux

An update on JSF's features and functions

Shortly after the first JavaServer Faces (JSF) early access (EA) release was introduced in September 2002, I wrote two JavaWorld articles detailing JSF (see Resources). Since then, JSF has matured considerably over four EA releases, and today, with a beta release in the wings, is poised to take the stage as the preeminent Java Web application framework. Furthermore, as the standard framework for developing Web applications, JSF will enjoy strong tool support from vendors like Sun Microsystems and its Project Rave development tool—a direct competitor to Microsoft's Visual Studio.

The underlying concepts from my first two JSF articles are still valid, but a multitude of changes to JSF has resulted in a different approach to implementing JSF applications. If you are unfamiliar with JSF, I suggest you review those articles; I will not repeat introductory material here.

These are the major additions to JSF since EA1:

  • Application configuration resources
  • Navigation handling
  • Actions
  • JavaBeans event model
  • Managed Bean Creation facility
  • Portlet compatibility
  • Framework extensions

In this article, I begin with an overview of each of these JSF additions. Once you have a solid theoretical understanding, I'll look at an updated version of the simple login application discussed in my first JavaWorld JSF article. That discussion will make the theoretical more concrete.

Read the previous "A First Look at JavaServer Faces" series:

Note: The code discussed in this article is based on the Early Access 4 release, which you can get by downloading the Java Web Services Developer Pack (JWSDP). See Resources for a link to the JWSDP download page.

Application configuration resources

Application configuration resources are resources you specify in an XML configuration file (typically /WEB-INF/faces-config.xml) instead of Java code; for example, you can specify navigation and managed beans (meaning beans created and managed by JSF) in faces-config.xml. Otherwise known as declarative programming, this is a feature Java 2 Platform, Enterprise Edition (J2EE) developers have learned to appreciate. Specifying things in XML is easier than writing code, and this XML is accessible to page authors with no Java experience. Plus, you can change resources without recompiling. Many JSF resources are now specified in configuration files; here's a partial list:

  • Navigation rules
  • Managed beans
  • Custom components
  • Converters
  • Validators
  • Render kits

Application configuration resources are closely tied to two other features listed above: navigation handling and the Managed Bean Creation facility. Both of those features are specified in the JSF configuration file and are discussed below.

Note: Although /WEB-INF/faces-config.xml is the default JSF configuration file, you can specify an alternate file, or a list of files, that serve as the application configuration file(s).

Actions and navigation handling

In JSF EA1, developers were forced to write an application handler that handled all of an application's action (application-specific) events. That application handler didn't scale well because it was essentially a switch statement that handled all action events. JSF EA4, like Struts, lets developers use the Command pattern to represent actions as objects instead of cases in a switch statement. (See "Take Command of Your Software" (JavaWorld, June 2002) for more about the Command pattern.)

Actions have a processAction() method that returns a string representing the action's outcome. The JSF framework uses that outcome to forward the request to some destination. Navigation, in terms of outcomes and forwarding destinations, among other things, are specified in—you guessed it—a faces configuration file.

A combination of actions and declarative navigation handling accomplishes two things: it moves developers from switch statements to the Command pattern and allows navigation rules, in terms of things like outcomes and JSP pages, to be specified in a faces configuration file. That's a strong one-two combination for maintainability and extensibility.

JavaBeans event model

Event models are an integral part of component frameworks because they let you react to component events, such as when a button is clicked or a value is changed. JSF EA4 offers two types of events and associated listeners: action events, fired when a button or link is activated, and value-changed events, fired when an input component's value is changed. If you use JSF with JavaServer Pages (JSP), you can register event handlers for components like this:

<h:input_text valueRef='someBean.someProperty'>
   <f:valuechanged_listener type='SomeListenerClass'/>
</h:input_text>
...
<h:command_button key='submit.button.prompt' bundle='messages'>
   <f:action_listener type='AnotherListenerClass'/>
</h:command_button>

A full-fledged event-handling example is discussed at the end of this article.

Managed Bean Creation facility

JSF lets you directly wire components to bean properties; for example:

  <h:input_secret valueRef='loginForm.password'/>

The preceding tag represents an input component whose value is wired to the password property of a bean named loginForm. Early JSF versions supported wiring components to bean properties (valueRef has replaced the modelReference attribute), but the developer had to instantiate the bean, typically with <jsp:useBean>. With EA4, the JSF framework takes care of instantiating those beans; all you need to do is declare them in a faces configuration file. The configuration file also lets you specify bean properties and runtime relationships between those beans. Those configuration files sure are handy!

Portlet compatibility

JSF EA4 provides portlet compatibility by eliminating references to the servlet API. For instance, in EA1, you could obtain a reference to the servlet context by calling FacesContext.getServletContext(), but that code won't work in a portlet; instead, you would need a reference to the portlet context. JSF EA4 no longer has a FacesContext.getServletContext() method; instead, you call FacesContext.getExternalContext(). The ExternalContext class is a wrapper around some type of context (servlet or portlet, typically). You can use the external context to access container contexts in a neutral fashion. For example, ExternalContext.getRequest() returns an Object reference that either points to a ServletRequest instance or an instance of PortletRequest; you cast the Object reference to whatever is appropriate. If you must have a reference to either the ServletContext or PortletContext, you can call ExternalContext.getContext(), which also returns an Object reference that you must cast.

Framework extensions

Finally, JSF EA4 lets developers modify and extend the JSF framework with pluggable objects for:

  • Navigation handler
  • Default action listener
  • Property resolver
  • Variable resolver
  • View handler

There are many reasons why you might want to change the default functionality offered by the objects listed above. For example, you might want to embed security checks in JSF every time the navigation handler forwards to a resource based on an outcome; if so, you could implement your own navigation handler and substitute it for the default. Perhaps you want JSF to recognize your own implicit objects in its value reference expressions; you can do that with a custom variable resolver. Of course, you register your custom classes—for example, AcmeNavigationHandler or AcmeVariableResolver—in a faces configuration file.

Old versus new

Now that you have a theoretical understanding of new JSF features, let's examine some code by comparing an application I wrote for EA1 and subsequently rewrote for EA4. In my first JavaWorld JSF article, I discussed the simple login application displayed in Figure 1.

Figure 1. A simple JSF login application. Click on thumbnail to view full-size image.

Figure 1's application works as you would expect: input is verified, and if correct, the application forwards to a welcome page. If login information is incorrect, the application redisplays the login page with an error message, as Figure 1 shows. The application uses standard JSF components and validators. Let's compare the original implementation with more current code.

The most offensive injustice a developer had to endure with JSF EA1 was the application handler—boilerplate code for event handling and navigation that had to be implemented by every JSF application. In my original JavaWorld JSF series, I implemented the application handler listed in Example 1.

Example 1. A JSF EA1 application handler

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;
   }
}

The preceding application handler forwards all JSF requests to /welcome.jsp, so clicking Figure 1's Log In button always takes you to /welcome.jsp as long as there are no validation errors. In addition to the application handler, JSF EA1 required developers to implement a servlet context listener to install the application handler. I list the servlet context listener for my original JSF article series in Example 2.

Example 2. A servlet context listener installs the application handler

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
   }
}

Finally, you had to register Example 2's context listener in /WEB-INF/web.xml, as Example 3 shows.

Example 3. The servlet context listener is registered in /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>
   ...
</web-app>

The preceding listings show an awfully long-winded way to say go to /welcome.jsp. Instead of implementing code that grapples with factories and other JSF objects that the average developer will otherwise never directly access, Example 4 shows how Examples 1 through 3 are represented in JSF EA4.

Example 4. Navigation rules specified in XML replace Java code

<navigation-rule>
   <navigation-case>
      <description>All JSF requests (there's only one request in
                   this application) are directed to /welcome.jsp</description>
      <to-tree-id>/welcome.jsp</to-tree-id>
   </navigation-case>
</navigation-rule>

Example 4's XML is an excerpt from a faces configuration file that mimics the code from Examples 1 through 3: all JSF requests are forwarded to /welcome.jsp. Look how much work you save specifying that simplest of navigations: two Java source files and an entry in /WEB-INF/web.xml have been reduced to six lines of XML. And if you decide to change, for instance, /welcome.jsp to /start.jsp, you can do that without modifying or recompiling Java code.

Besides navigation handling, the old application handler was responsible for reacting to all application events. Figure 1's login application only has one event (Log In button activation), but in reality application handlers quickly became unmanageable switch statements that looked something like this:

1 2 Page
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more