getting started with javaserver faces 1.2, part 2: jsf ... · this tutorial series covers how to...

52
Getting started with JavaServer Faces 1.2, Part 2: JSF life cycle, conversion, validation, and phase listeners Skill Level: Introductory Richard Hightower ([email protected]) CTO ArcMind 29 Jan 2008 This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that offers a component-based approach to Web user-interface development. Part 1 gets you started with a JSF 1.2 overview and a basic application. This sequel gives you a firm grasp of JSF's more-advanced features: custom validators, converters, and phase listeners. Along the way you'll gain an understanding of the JSF application life cycle. Section 1. Before you start About this series Get an introduction to Java™ Server Faces (JSF) technology, a server-side user-interface component framework for Java-based Web applications. This series is for developers who are new to JSF and want to come up to speed quickly — not just with JSF, but with using JSF components to reduce effort. This series covers just the essentials, with lots of examples. JSF is a more-traditional GUI development environment like AWT, SWT, and Swing. One of its major benefits is that it makes Web development easier by putting the hard work on the framework developers, not the application developers. Granted, JSF itself is more complex than many other Web frameworks, but the complexity is JSF life cycle, conversion, validation, and phase listeners © Copyright IBM Corporation 1994, 2008. All rights reserved. Page 1 of 52

Upload: hoangkhue

Post on 08-Nov-2018

227 views

Category:

Documents


0 download

TRANSCRIPT

Page 1: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

Getting started with JavaServer Faces 1.2, Part 2:JSF life cycle, conversion, validation, and phaselistenersSkill Level: Introductory

Richard Hightower ([email protected])CTOArcMind

29 Jan 2008

This tutorial series covers how to get started with Java™ Server Faces (JSF)technology, a server-side framework that offers a component-based approach toWeb user-interface development. Part 1 gets you started with a JSF 1.2 overviewand a basic application. This sequel gives you a firm grasp of JSF's more-advancedfeatures: custom validators, converters, and phase listeners. Along the way you'llgain an understanding of the JSF application life cycle.

Section 1. Before you start

About this series

Get an introduction to Java™ Server Faces (JSF) technology, a server-sideuser-interface component framework for Java-based Web applications. This series isfor developers who are new to JSF and want to come up to speed quickly — not justwith JSF, but with using JSF components to reduce effort. This series covers just theessentials, with lots of examples.

JSF is a more-traditional GUI development environment like AWT, SWT, and Swing.One of its major benefits is that it makes Web development easier by putting thehard work on the framework developers, not the application developers. Granted,JSF itself is more complex than many other Web frameworks, but the complexity is

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 1 of 52

Page 2: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

hidden from the application developer. It is much easier to develop Web applicationsin JSF than in most other frameworks: it requires less code, less complexity, andless configuration.

If you are doing Java server-side Web development, JSF is the easiest framework tolearn. It is geared for creating Web applications (not Web sites). It allows you tofocus on your Java code without handling request objects, session objects, orrequest parameters, or dealing with complicated XML files. With JSF, you can getmore things done more quickly than with other Java Web frameworks.

About this tutorial

This tutorial picks up where Part 1 leaves off. If you are new to JSF or just want arefresher, then read the first installment before you begin this one. Even if you arean old JSF pro, there is likely a gem or two in that will help you out.

Although tool support is a main benefit of JSF, you won't use fancy tools or IDEsupport in this tutorial. This tutorial covers the essentials with just backgroundinformation to keep the discussion going and to keep you productively learning touse JSF to build Web applications.

Objectives

In this tutorial, continue getting an overview of JSF's features, and learn how to workwith all of the JSF components. Build a simple contact-management application — abasic CRUD (create, read, update, delete) listing. After learning about the JSFapplication life cycle, improve the application with custom converters and validators.The tutorial winds down with a taste of some advanced JSF programming: create anobject-level validation framework using a phase listener.

Who should take this tutorial?

If you are new to JSF, this tutorial is for you. Even if you have used JSF but have nottried out the JSF 1.2 features or have only used GUI tools to build JSF applications,you will likely learn a lot from both tutorials in this series.

Prerequisites

This tutorial is written for Java developers whose experience is at a beginning tointermediate level. You should have a general familiarity with using the Javalanguage, with some GUI development experience.

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 2 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 3: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

System requirements

To run the examples in this tutorial, you need a Java development environment(JDK) and Apache Maven. It helps to also have a Java IDE. Maven project files andEclipse Java EE and Web Tools Project (WTP) project files are provided. SeeDownload to obtain the example code. Visit the author's companion site (seeResources) for additional information about how to run the examples.

Section 2. Sample JSF CRUD application

This section introduces a simple CRUD application that you'll build on in subsequentsections to learn about:

• Each of JSF's standard HTML components

• Creating custom converters

• Working with validators

• Working with phase listeners

A contact-management application

The application you'll build in this section is a contact-management application that'ssimilar in structure to the calculator application in Part 1. As you can see in Figure 1,the application is a standard CRUD listing. It requires no navigation rules becausethe whole application uses a single view (the contacts.jsp page).

Figure 1. Contact-management sample application

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 3 of 52

Page 4: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

Figure 2 shows the basic flow through the application:

Figure 2. Contact-management sample application, link chase

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 4 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 5: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

This CRUD application consists of the following elements:

• ContactController: The JSF controller

• Contact: The model object that represents contact information

• ContactRepository: The model object that creates, reads, updates,and deletes Contact objects

• contacts.jsp: The JavaServer Pages (JSP) page that lays out the JSFcomponent tree for managing contacts

• faces-config.xml: The JSF configuration, where you declareContactController and ContactRepository as managed beans,and inject ContactRepository into ContactController

ContactController

ContactController backs the contacts.jsp page. Listing 1 shows the code forContactController:

Listing 1. ContactController

package com.arcmind.contact.controller;

import java.util.List;

import javax.faces.application.FacesMessage;import javax.faces.component.UICommand;import javax.faces.component.UIForm;import javax.faces.context.FacesContext;

import com.arcmind.contact.model.Contact;import com.arcmind.contact.model.ContactRepository;

public class ContactController {/** Contact Controller collaborates with contactRepository. */private ContactRepository contactRepository;

/** The current contact that is being edited. */private Contact contact = new Contact();

/** Contact to remove. */private Contact selectedContact;

/** The current form. */private UIForm form;

/** Add new link. */private UICommand addNewCommand;

/** Persist command. */private UICommand persistCommand;

/** For injection of collaborator. */public void setContactRepository(ContactRepository contactRepository) {

this.contactRepository = contactRepository;

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 5 of 52

Page 6: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

}

public void addNew() {form.setRendered(true);addNewCommand.setRendered(false);persistCommand.setValue("Add");

}

public void persist() {form.setRendered(false);addNewCommand.setRendered(true);if (contactRepository.persist(contact) == null) {

addStatusMessage("Added " + contact);} else {

addStatusMessage("Updated " + contact);}

}

public void remove() {contactRepository.remove(selectedContact);addStatusMessage("Removed " + selectedContact);

}

public void read() {contact = selectedContact;form.setRendered(true);addNewCommand.setRendered(false);addStatusMessage("Read " + contact);persistCommand.setValue("Update");

}

private void addStatusMessage(String message) {FacesContext.getCurrentInstance().addMessage(null,

new FacesMessage(FacesMessage.SEVERITY_INFO, message, null));}

//most getter/setter omitted

}

Listing 1 creates a CRUD GUI in fewer than 74 lines of code — not too bad.ContactController is managed in request scope, so a new Contact iscreated when a ContactController is instantiated. Three components — form(of type UIForm), addNewCommand (of type UICommand), and persistCommand(of type UICommand) — are bound to the ContactController.

The addNew() method ensures that:

• form is turned on so the user can enter a new contact —form.setRendered(true)

• addNewCommand is turned off —addNewCommand.setRendered(false)

• persistCommand's label is set to Add —persistCommand.setValue("Add")

The persist() method handles both updating an existing contact and adding anew contact using the contactRepository. The persist() method turns the

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 6 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 7: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

form off and the addNewCommand on. The remove() method removes the contactfrom the system using the contactRepository.

The read() method copies the selectedContact (of type Contact) to thecontact. (The contact — also of type Contact — is value bound to the form.)You might wonder where the selectedContact comes from. It is selected whenthe user clicks a contact in the contact listing. (This tutorial covers it when discussingthe JSP.) As in Part 1, addStatusMessage adds status messages so you candisplay them with <h:messages>.

Contacts view

The JSP page uses a <h:dataTable> (a component not covered in Part 1), asshown in Listing 2:

Listing 2. contacts.jsp

<?xml version="1.0" encoding="ISO-8859-1" ?><%@ taglib uri="http://java.sun.com/jsf/html" prefix="h"%><%@ taglib uri="http://java.sun.com/jsf/core" prefix="f"%>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml"><head><title>Contacts</title><link rel="stylesheet" type="text/css"

href="<%=request.getContextPath()%>/css/main.css" /></head>

<body><f:view>

<h4>Contacts</h4>

<h:messages infoClass="infoClass" errorClass="errorClass"layout="table" globalOnly="true" />

<h:form><h:commandLink binding="#{contactController.addNewCommand}"

action="#{contactController.addNew}" value="Add New..." /></h:form>

<h:form binding="#{contactController.form}" rendered="false"styleClass="form">

<h:inputHidden value="#{contactController.contact.id}" /><h:panelGrid columns="6">

<%-- First Name --%><h:outputLabel value="First Name" for="firstName" accesskey="f" /><h:inputText id="firstName" label="First Name" required="true"

value="#{contactController.contact.firstName}" size="10" /><h:message for="firstName" errorClass="errorClass" />

<%-- Last Name --%><h:outputLabel value="Last Name" for="lastName" accesskey="l" /><h:inputText id="lastName" required="true"

value="#{contactController.contact.lastName}" size="15" />

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 7 of 52

Page 8: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

<h:message for="lastName" errorClass="errorClass" /></h:panelGrid><h:panelGroup>

<h:commandButton binding="#{contactController.persistCommand}"action="#{contactController.persist}" />

</h:panelGroup></h:form><h:form>

<h:dataTable value="#{contactController.contacts}" var="contact"rowClasses="oddRow, evenRow"rendered="#{not empty contactController.contacts}"styleClass="contactTable" headerClass="headerTable"columnClasses="normal,centered"><h:column>

<f:facet name="header"><h:column>

<h:outputText value="Name" /></h:column>

</f:facet><h:outputText value="#{contact.lastName}, #{contact.firstName}" />

</h:column><h:column>

<f:facet name="header"><h:column>

<h:outputText value="Action" /></h:column>

</f:facet><h:panelGrid columns="2">

<h:commandLink value="remove" action="#{contactController.remove}"><f:setPropertyActionListener

target="#{contactController.selectedContact}" value="#{contact}" /></h:commandLink><h:commandLink value="edit" action="#{contactController.read}">

<f:setPropertyActionListenertarget="#{contactController.selectedContact}" value="#{contact}" />

</h:commandLink></h:panelGrid>

</h:column>

</h:dataTable></h:form>

</f:view></body>

</html>

The <h:dataTable> is value bound to display the contacts from thecontactController, using "#{contactController.contacts}". Eachcontact is mapped for the table using the var attribute: var="contact". Incontacts.jsp, the oddRow and evenRow styles (defined in a CSS file) are set intothe rowClasses attribute with rowClasses="oddRow, evenRow". This allowsthe <h:dataTable> to use alternating styles for the tables. Check the online<h:dataTable> documentation for a complete list of what can be done with<h:dataTable> and styles, because <h:dataTable> has quite a few capabilities.(See Resources for a link to the JSF API Javadocs.)

You can also set a style for the entire <h:dataTable> withstyleClass="contactTable" or for its header region withheaderClass="headerTable". Or you can do the same alternating styles forcolumns using the columnClasses attribute:

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 8 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 9: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

columnClasses="normal,centered". The <h:dataTable> is set up so itdoes not render if no contacts exist: rendered="#{not emptycontactController.contacts}". The <h:dataTable> component is veryversatile and easy to use.

Inside <h:dataTable>, you use <h:column>s to display the values in properties.Each column defines a header in a <f:facet> tag. A facet is a name componentthat another component uses. Then, after the <f:facet> and inside the<h:column> component, you use the <h:outputText> component, whichoutputs the contact's firstName and lastName properties.

Each row has a remove link and an edit link, each of which uses a<h:commandLink>. The remove commandLink is bound to thecontactController.remove method. The edit link is bound to thecontactController.read method. ThecontactController.selectedContact property is populated with the currentrow because of the way you configure the <f:setPropertyActionListener>.The <f:setPropertyActionListener> causes the current row's contact to becopied to selectedContact before the action method is invoked.

faces-config.xml for the contacts CRUD application

The faces-config.xml file wires the ContactRepository into theContactController, as shown in Listing 3:

Listing 3. faces-config.xml

<managed-bean><managed-bean-name>contactRepository</managed-bean-name><managed-bean-class>com.arcmind.contact.model.ContactRepository</managed-bean-class><managed-bean-scope>application</managed-bean-scope>

</managed-bean><managed-bean>

<managed-bean-name>contactController</managed-bean-name><managed-bean-class>

com.arcmind.contact.controller.ContactController</managed-bean-class><managed-bean-scope>request</managed-bean-scope><managed-property>

<property-name>contactRepository</property-name><property-class>

com.arcmind.contact.model.ContactRepository</property-class><value>#{contactRepository}</value>

</managed-property></managed-bean>

Notice that the contactRepository is in application scope and that it isinjected into contactController's contactRepository using the

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 9 of 52

Page 10: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

<managed-property> element. This technique lets you injectdependencies/collaborators into controllers, which aids in keeping the model andview separate, and it allows you to inject mock objects that can later be replacedwith real objects. On more than one occasion I've mocked a model object like theContactRepository and later replaced it with the real version after the GUI wascomplete.

The model of the application is pretty simple, as shown in Listings 4 and 5. Listing 4shows the Contact class:

Listing 4. Contact

package com.arcmind.contact.model;

public class Contact { private String firstName;private String lastName; protected long id;

public Contact(String firstName, String lastName) {this.firstName = firstName; this.lastName =lastName; }

public Contact() { }

public String getFirstName() { return firstName; }

public void setFirstName(String firstName) {this.firstName = firstName; }

public String getLastName() { return lastName; }

public void setLastName(String lastName) {this.lastName = lastName; }

@Override public int hashCode() { final int prime =31; int result = 1; result = prime * result + (int)(id ^ (id >>> 32)); return result; }

@Override public boolean equals(Object obj) { if(this == obj) return true; if (obj == null) returnfalse; if (getClass() != obj.getClass()) returnfalse; final Contact other = (Contact) obj; if (id!= other.id) return false; return true; }

@Override public String toString() { returnString.format("Contact: %s %s", firstName,lastName); }

public long getId() { return id; }

public void setId(long id) { this.id = id; }

}

Listing 5 shows the ContactRepository class, which simulates writing contacts toa database:

Listing 5. ContactRepository

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 10 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 11: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

package com.arcmind.contact.model;

import java.util.LinkedHashMap;import java.util.List;import java.util.ArrayList;import java.util.Map;

public class ContactRepository {private Map<Long, Contact> contacts = new LinkedHashMap<Long, Contact>();private static long counter = 1l;

public List<Contact> getContacts() {return new ArrayList<Contact>(contacts.values());

}

public synchronized Contact persist(Contact contact) {if (contact.id == 0) {

contact.id = counter++;}return contacts.put(contact.id, contact);

}

public synchronized void remove(Contact contact) {contacts.remove(contact.id);

}

}

What you have now is a pretty basic CRUD application. In the next section, you'llbuild on top of this base to learn how to use the different JSF components.

Section 3. Working with JSF components

In this section, you'll use a variety of JSF components to enhance the CRUDapplication:

• <f:subview>

• <h:selectOneMenu>

• <h:selectOneRadio>

• <h:selectBooleanCheckbox>

• <h:selectManyCheckbox>

• <h:inputTextarea>

Figure 3 shows how these components appear in the GUI:

Figure 3. Contact management with a slew of common JSF components

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 11 of 52

Page 12: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

Subviews

As you can imagine, it would be difficult to include all of your JSF components onone page. Fortunately, you can split JSF components into separate views using a<f:subview>, as shown in Listing 6:

Listing 6. Subviews contacts.jsp

<body><f:view>

<h3>Contacts (2nd version)</h3><h:messages infoClass="infoClass" errorClass="errorClass"

layout="table" globalOnly="true" />

<h:form><h:commandLink binding="#{contactController.addNewCommand}"

action="#{contactController.addNew}" value="Add New..." /></h:form>

<f:subview id="form"><jsp:include page="form.jsp" />

</f:subview>

<f:subview id="listing"><jsp:include page="listing.jsp" />

</f:subview>

</f:view></body>

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 12 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 13: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

You can use <f:subview>s in the parent page or the included page (not both). InJSF 1.2, the <f:subview> is optional. In older versions of JSF, it is required. SomeIDEs apparently expect <f:subview>s, so you might need to use them even if youare using JSF 1.2 or later.

Select one

In JSF, components are separated into two parts: the JSF component itself and arenderer that's responsible for displaying the component. The UISelectOnecomponent has several renderers. It backs HtmlSelectOneListbox,HtmlSelectOneMenu, and HtmlSelectOneRadio.

The contacts application (2nd version) uses <h:selectOneMenu>. For this to work,you add three new model objects: Group (shown in Listing 7), Tag, andContactType. You also add two new repository objects: GroupRepository andTagRepository, which are similar to ContactRepository. The ContactTypedoes not need a repository because it is an Enum. The Contact class now hasthree new properties for the group it is in (called group), the tags that areassociated with it (tags), and lastly its type.

Listing 7. Subviews contacts.jsp/form.jsp

<%-- Group --%><h:outputLabel value="Group" for="group" accesskey="g" /><h:selectOneMenu id="group" validatorMessage="required"

value="#{contactController.selectedGroupId}"><f:selectItems value="#{contactController.groups}" /><f:validateLongRange minimum="1" />

</h:selectOneMenu><h:message for="group" errorClass="errorClass" />

Note that the selectOneMenu uses the value attribute to bind theselectOneMenu to the selectedGroupId. The body of the selectOneMenucontains a <f:selectItems>, which is value bound to the groups property:value=#{contactController.groups}. You create the groups list in thebacking bean. The code for the selectedGroupId property and the groupsproperty is in Listing 8:

Listing 8. Building a list of groups

public class ContactController {...private GroupRepository groupRepository;...private Long selectedGroupId;...public List<SelectItem> getGroups() {

List<Group> groups = groupRepository.list();List<SelectItem> list = new ArrayList<SelectItem>(groups.size()+1);

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 13 of 52

Page 14: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

list.add(new SelectItem(Long.valueOf(-1L), "select one"));for (Group group : groups) {

SelectItem selectItem = new SelectItem(group.getId(), group.getName());list.add(selectItem);

}return list;

}//Other getter/setters removed...

The groups property returns a list of SelectItems. The SelectItem class isused to represent a single item in a list. It's used by both the UISelectMany andUISelectOne components. Notice that the getGroups method usesgroupRepository, which is a repository object that is injected just like thecontactRepository to get a list of groups. The groupRepository managesGroup domain objects. A Group represents a group. A Contact represents acontact. The getGroups() method creates a list of SelectItems using thegroup.id property as the value and the group.name property as the label.

Notice that you add a "select one" SelectItem with a value of -1. You use this todetermine if an item has been selected. If it has not, you pick up that it was notselected by using the <f:validateLongRange minimum="1" /> that is in theselectOneMenu (see Listing 7 again). Also note that the selectOneMenu usesvalidatorMessage="required" to display a short error message.

Note that Listing 8 calls the repository directly. If the repository was really talking to adatabase or cache, you'd want to make the call in the action method in order to doerror handling there and expose the results as selected items in the property.

When the form is submitted, the selectedGroupId is set. The persist()method that is bound to the update and create buttons uses selectedGroupId tolook up the group from the repository, as shown in Listing 9:

Listing 9. Updating persist() to use selectedGroupId

public class ContactController {...public void persist() {

/* Setup the group into contact. */contact.setGroup(groupRepository.lookup(selectedGroupId));

/* Turn form off, turn link on. */form.setRendered(false);addNewCommand.setRendered(true);

/* Add a status message. */if (contactRepository.persist(contact) == null) {

addStatusMessage("Added " + contact);} else {

addStatusMessage("Updated " + contact);}

}...public void read() {

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 14 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 15: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

/* Prepare selected contact. */contact = selectedContact;

/* Turn form on and the link off. */form.setRendered(true);addNewCommand.setRendered(false);

/* Prepare selected group id. */selectedGroupId = contact.getGroup().getId();

...this.selectedTagIds = tagIds.toArray(new Long[tags.size()]);

addStatusMessage("Read " + contact);persistCommand.setValue("Update");

}...

Notice that the Contact class adds a group property. Thus Contact has amany-to-one relationship with Group.

The read() method in Listing 9 initializes this.selectedTagIds so that it showsas selected in the view.

You've seen how you can handle a one-to-one relationship with <h:selectOne>.Contact also has a many-to-many relationship to Tags, represented with the tagsproperty of Contact. To handle this, use a <h:selectManyCheckbox>, as shownin Listing 10:

Listing 10. selectManyCheckbox for Contact.tags

<h:selectManyCheckbox id="tags"value="#{contactController.selectedTagIds}"><f:selectItems value="#{contactController.availableTags}" />

</h:selectManyCheckbox>

The main difference with the <h:selectManyCheckbox> is that it is bound to anarray of longs, instead of a single long, as shown in Listing 10.

The Java code is similar to Listing 8, except now you are dealing with an array ofselected IDs (longs), instead of a single long, as shown in Listing 11:

Listing 11. Java code to back the selectManyCheckbox

public class ContactController {...

private Long[] selectedTagIds;

private TagRepository tagRepository;...

public List<SelectItem> getAvailableTags() {List<Tag> tags = tagRepository.list();List<SelectItem> list = new ArrayList<SelectItem>(tags.size());for (Tag tag : tags) {

SelectItem selectItem = new SelectItem(tag.getId(), tag.getName());

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 15 of 52

Page 16: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

list.add(selectItem);}return list;

}...

public void persist() {...

/* Setup the tags into contact. */List<Tag> tags = new ArrayList<Tag>(selectedTagIds.length);for (Long selectedTagId : selectedTagIds) {

tags.add(tagRepository.lookup(selectedTagId));}contact.setTags(tags);

...}...

public void read() {...

/* Prepare selected tag IDs. */List<Tag> tags = contact.getTags();List<Long> tagIds = new ArrayList<Long>(tags.size());for (Tag tag : tags) {

tagIds.add(tag.getId());}this.selectedTagIds = tagIds.toArray(new Long[tags.size()]);...

}...

The persist() method uses the tagRepository.lookup() method to look upthe Tag domain objects based on the selectedTagIds, and then sets thecontact.tags property with the looked-up values. The read() method initializesthe selectedTagIds based on which Tags are in the contact.tags property.

The UISelectMany class backs <h:selectManyCheckbox>, but it also backs<h:selectManyListbox> and <h:selectManyMenu>. You can use theminstead of the <h:selectManyCheckbox>, as shown in Listings 12 and 13, andFigure 4.

Listing 12. <selectManyListbox>

<h:selectManyListbox id="tags" value="#{contactController.selectedTagIds}"><f:selectItems value="#{contactController.availableTags}"/>

</h:selectManyListbox>

Listing 13. <selectManyMenu>

<h:selectManyMenu id="tags" value="#{contactController.selectedTagIds}"><f:selectItems value="#{contactController.availableTags}"/>

</h:selectManyMenu>

Figure 4: <selectManyListbox> and <selectManyMenu>

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 16 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 17: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

Notice that the setup for all three tags is the same.

JSF 1.2 has converters for Enums, so you don't need to look up the values. You canuse them directly and bind to them without having an intermediate property in thecontroller as you had for tags and group (see Listing 8 and Listing 11). TheContact class has an enum property, as shown in Listing 14:

Listing 14. Contact using enum for type property

public enum ContactType {BUSINESS, PERSONAL;

public String toString () {return this.name().toLowerCase();

}}...public class Contact implements Serializable {

...private Group group;private List<Tag> tags;...private ContactType type = ContactType.PERSONAL;...

public ContactType getType() {return type;

}

public void setType(ContactType type) {this.type = type;

}...

}

Since JSF has a converter for Enums, you can bind directly to the type property, asshown in Listing 15:

Listing 15. Binding directly to contact.type

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 17 of 52

Page 18: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

<h:selectOneRadio id="type" value="#{contactController.contact.type}"><f:selectItem itemValue="PERSONAL" itemLabel="personal" /><f:selectItem itemValue="BUSINESS" itemLabel="business" />

</h:selectOneRadio>

Listing 15 also demonstrates the use of <f:selectItem>, which creates singlevalues. JSF's built-in converter for Enums expects the values to be the string values— the names, really, of the Enums. You can also use a <h:selectOneListbox>,as shown in Listing 16:

Listing 16. Using <h:selectOneListbox>

<h:selectOneListbox id="type" value="#{contactController.contact.type}"><f:selectItem itemValue="PERSONAL" itemLabel="personal"/><f:selectItem itemValue="BUSINESS" itemLabel="business"/>

</h:selectOneListbox>

The contact-management application's other components

The contacts CRUD application also demonstrates the use of<h:selectBooleanCheckbox> and <h:inputTextarea>, as shown in Listing17:

Listing 17. Using <h:selectBooleanCheckbox> and <h:textArea>

<h:inputHidden value="#{contactController.contact.id}" />...<%-- active --%><h:outputLabel value="Active" for="active" accesskey="a" /><h:selectBooleanCheckbox id="active"

value="#{contactController.contact.active}" /><h:message for="active" errorClass="errorClass" />

<%-- Description --%>...<h:outputLabel value="Description" for="description"

accesskey="d" style="font: large;" /><h:inputTextarea id="description" cols="80" rows="5"

value="#{contactController.contact.description}" /><h:message for="description" errorClass="errorClass" />

The <h:inputTextarea> has two additional attributes that are set withcols="80" rows="5". The bindings work as before.

Listing 18 shows the properties that you bind to in Listing 17:

Listing 18. Contact class

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 18 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 19: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

public class Contact implements Serializable {...

private String description;private boolean active;protected long id;...

public String getDescription() {return description;

}

public void setDescription(String description) {this.description = description;

}

public boolean isActive() {return active;

}

public void setActive(boolean active) {this.active = active;

}public long getId() {

return id;}

public void setId(long id) {this.id = id;

}...

}

You bind the id, description, type, firstName, and lastName properties fromContact directly to the UI. The group and tags properties can't be bound directlybecause they have no JSF converter. In a later section of this tutorial ("JSF dataconverters"), you'll learn about converters and create some for the application.Between now and then, let's take a brief a side trip into the JSF application life cycle.

Section 4. JSF application life cycle

You can (contrary to popular belief) write JSF applications without knowing everylittle detail of how the technology works; you can learn a tremendous amount by justgiving yourself a project and cobbling through it. But understanding certainfundamentals will make your development efforts much more rewarding — and a lotless time consuming. This section takes a detour away from the contacts applicationand walks you through the six phases of the JSF request-processing life cycle. Takea look at what happens in each phase and how the phases interconnect. Thisdiscussion will inform the work you do in the remaining sections of the tutorial.

Phases of the JSF application life cycle

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 19 of 52

Page 20: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

The six phases of the JSF application life cycle are:

1. Restore view

2. Apply request values; process events

3. Process validations; process events

4. Update model values; process events

5. Invoke application; process events

6. Render response

The six phases show the order in which JSF typically processes a form GUI. The listshows the phases in their likely order of execution with event processing at eachphase, but the JSF life cycle is hardly set in stone. You can change the order ofexecution by skipping phases or leaving the life cycle altogether. For example, if aninvalid request value is copied to a component, the current view would beredisplayed, and some of the phases might not execute.

You can also opt to leave JSF altogether, perhaps delegating to a servlet or anotherapplication framework. In this case, you could issue aFacesContext.responseComplete method invocation to redirect the user to adifferent page or Web resource, then use the request dispatcher (retrieved from therequest object in the FacesContext) to forward to an appropriate Web resource.Alternatively, you could call FacesContext.renderResponse to re-render theoriginal view.

The point is to let the life cycle structure your development efforts without feelingcompletely tied to it. You can alter the default life cycle when needed without fear ofbreaking your application. In most cases, you'll find that the JSF life cycle is worthadhering to because it's quite logical.

Forms must be validated before any application logic can be executed, and fielddata must be converted before being validated. Sticking to the life cycle frees you upto think about the details of validation and conversion, rather than the phases of therequest process itself. It's also important to note that other Web frameworks havesimilar life cycles; they just don't tend to be as well advertised or documented.

Some developers using JSF may never write a component or extend the framework,while others may focus on just those tasks. Although the JSF life cycle will be thesame for almost any project, you'll likely tap into it at different stages based on yourrole in the project. If you're concentrating more on the overall applicationdevelopment, you'll likely be concerned with the middle phases of the requestprocessing life cycle:

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 20 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 21: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

• Apply requests values

• Process validations

• Update model values

• Invoke application

If you're concentrating on JSF component development, you'll probably focus on thefirst and last phases of the life cycle:

• Restore view

• Render response

In the remainder of this section, you'll walk through each phase of the JSF requestprocessing life cycle, including event handling and validation. Before you get started,take a look at Figure 5, a diagram of the JSF application life cycle, for an overview:

Figure 5. JSF application life cycle

Phase 1: Restore view

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 21 of 52

Page 22: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

In the first phase of the JSF life cycle — restore view — a request comes throughthe FacesServlet servlet. The servlet examines the request and extracts the viewID, which is determined by the name of the JSP page.

The JSF framework controller uses the view ID to look up the components for thecurrent view. If the view doesn't already exist, the JSF controller creates it. If theview already exists, the JSF controller uses it. The view contains all the GUIcomponents.

This phase of the life cycle presents three view instances: new view, initial view, andpostback, with each one being handled differently. In the case of a new view, JSFbuilds the view of the Faces page and wires the event handlers and validators to thecomponents. The view is saved in a FacesContext object.

The FacesContext stores the state information JSF needs to manage the GUIcomponent's state for the current request. The FacesContext stores the view in itsviewRoot property; viewRoot contains all the JSF components for the currentview ID.

In the case of an initial view (the first time a page is loaded), JSF creates an emptyview. The empty view is populated as the JSP page is processed. From an initialview, JSF advances directly to the render response phase.

In the case of a postback (the user returns to a page he or she has previouslyaccessed), the view corresponding to the page already exists, so it needs only to berestored. In this case, JSF uses the existing view's state information to reconstructits state.

Phase 2: Apply request values

The purpose of the apply request values phase is for each component to retrieve itscurrent state. The components must first be retrieved or created from theFacesContext object, followed by their values. Component values are typicallyretrieved from the request parameters, although they can also be retrieved fromcookies or headers. The value from the request parameter for many components isstored in the component's submittedValue.

If a component's immediate event-handling property is set to true, the values areconverted to the proper type and validated (more on conversion in the next phase).The converted value is then stored in the component. If the value conversion orvalue validation fails, an error message is generated and queued in theFacesContext, where it will be displayed during the render response phase, alongwith any other validation errors.

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 22 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 23: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

Phase 3: Process validation

Conversion and validation normally happen in the process validation phase. Thecomponent converts the component's submittedValue and stores it. So if the fieldis bound to an Integer property (for example), the value is converted to anInteger. If the value conversion fails, an error message is generated and queuedin the FacesContext, where it will be displayed during the render response phase,along with any validation errors.

The first event handling of the life cycle takes place after the apply request valuesphase. At this stage, each component's values are validated against theapplication's validation rules. The validation rules can be predefined (shipped withJSF) or defined by the developer. Values entered by the user are compared to thevalidation rules. If an entered value is invalid, an error message is added toFacesContext, and the component is marked as invalid. If a component is markedas invalid, JSF advances to the render response phase and skips the rest of thephases, which will display the current view with the validation error messages. If novalidation errors occur, JSF advances to the update model values phase.

Phase 4: Update model values

The fourth phase of the JSF application life cycle — update model values — updatesthe server-side model's actual values by updating the properties of your managedbeans. Only bean properties that are bound to a component's value are updated.Notice that this phase happens after validation, so you can be sure that the valuescopied to your beans' properties are valid (at least at the form-field level; they couldstill be invalid at the business-rule level).

Phase 5: Invoke application

At the fifth phase of the life cycle — invoke application — the JSF controller invokesthe application to handle form submissions. The component values will have beenconverted, validated, and applied to the model objects, so you can now use them toexecute the application's business logic.

It is during this phase that your action-handler methods are called, such as thepersist() method and the read() methods in the sample application'sContactController.

At this phase, you also get to specify the next logical view for a given sequence ornumber of possible sequences. You do this by defining a specific outcome for asuccessful form submission and returning that outcome. For example: on successfuloutcome, move the user to the next page. For this navigation to work, you must

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 23 of 52

Page 24: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

create a mapping to the successful outcome as a navigation rule in thefaces-config.xml file. Once the navigation occurs, you move to the final phase of thelife cycle. JSF takes the object returned from the action method and calls itstoString() method. Then it uses this value as the outcome for the navigationrules. (Part 1 covers configuring navigation rules.)

Phase 6: Render response

In the sixth phase of the life cycle — render response — you display the view with allof its components in their current state.

Figure 6 is an object state diagram of the six phases of the JSF application life cycle,including validation and event handling:

Figure 6. The six-phase progression of the JSF application lifecycle

Section 5. JSF data converters

Conversion is the process of ensuring data is of the right object or type, hence youconvert string values into other types such as Date objects, primitive float, orFloat objects. You can use built-in converters or write custom converters. Thissection shows you JSF's standard converters and then covers custom converters indepth.

JSF's standard converters

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 24 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 25: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

JSF supplies many standard data converters, and most data conversions happenautomatically. Table 1 shows the converter IDs and corresponding implementationclasses JSF uses for simple data conversion.

Table 1. Standard JSF convertersConverter Implementation class

javax.faces.BigDecimal javax.faces.convert.BigDecimalConverter

javax.faces.BigInteger javax.faces.convert.BigIntegerConverter

javax.faces.Boolean javax.faces.convert.BooleanConverter

javax.faces.Byte javax.faces.convert.ByteConverter

javax.faces.Character javax.faces.convert.CharacterConverter

javax.faces.DateTime javax.faces.convert.DateTimeConverter

javax.faces.Double javax.faces.convert.DoubleConverter

javax.faces.Float javax.faces.convert.FloatConverter

Thus if you bind to an int or an Integer, the conversion happens automatically.Listing 19 shows a component for the contact-management sample applicationbinding directly to age. #{contactController.contact.age}:

Listing 19. Binding to age: JSF converts automatically

<%-- age --%><h:outputLabel value="Age" for="age" accesskey="age" /><h:inputText id="age" size="3" value="#{contactController.contact.age}"></h:inputText>

JSF does this for all primitive, wrapper, String, and Enum properties. Nice. It hasconverters for dates and numbers as well. Numbers can have many formats, so ithas a converter that allows you to describe the format that the end user will use.Ditto for dates. Listing 20 shows the use of JSF converters to convert a date using acustom format:

Although JSF handles primitives and the like quite nicely by default, when dealingwith date data, you must specify the <f:convertDateTime/> conversion tag. Thistag is based on the java.text package and uses short, long, and custom patterns.Listing 20 demonstrates how to use <f:convertDateTime/> to ensure the user'sbirth date is convertible into a date object, formatted as MM/yyyy (month/year). Seethe Java API documentation for java.text.SimpleDateFormat for a list ofpatterns (see Resources).

Listing 20. Specifying format for dates

<%-- birthDate --%>

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 25 of 52

Page 26: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

<h:outputLabel value="Birth Date" for="birthDate" accesskey="b" /><h:inputText id="birthDate" value="#{contactController.contact.birthDate}">

<f:convertDateTime pattern="MM/yyyy"/></h:inputText>

<h:message for="birthDate" errorClass="errorClass" />

Introducing JSF custom converters

Custom data conversion is necessary if you need to convert field data into anapplication-specific value object, as in the following examples:

• String to PhoneNumber object (PhoneNumber.areaCode,PhoneNumber.prefix, ...)

• String to Name object (Name.first, Name.last)

• String to ProductCode object(ProductCode.partNum,ProductCode.rev, ...)

• String to Group

• String to Tags

To create a custom converter, you must:

1. Implement the Converter interface (also known as.javax.faxes.convert.Converter).

2. Implement the getAsObject() method, which converts a field (string)into an object (for example, PhoneNumber).

3. Implement the getAsString method, which converts an object (forexample, PhoneNumber) into a string.

4. Register your custom converter in the Faces context.

You can see in Figure 7 how these steps fit into the JSF application life cycle:

Figure 7. Custom converter getAsObject() and getAsString() methods

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 26 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 27: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

In Figure 7, JSF calls the custom converter's getAsObject() method during theprocess validation phase. This is where the converter must convert the requeststring value into the desired object type and then return the object for storage in thecorresponding JSF component. When the value is rendered back to the view, JSFwill call the getAsString method in the render response phase. This means theconverter is also responsible for transforming the object data back into a stringrepresentation.

Implement the Converter interface

Recall that the sample application's Contact domain object has a many-to-onerelationship with Group and a many-to-many relationship with Tag. Previously (seeListing 9 and Listing 11) you did the conversion from id values to domain objects inthe ContactController. Instead of binding directly to domain properties in theview, you bound to id fields in the ContactController. If you use JSFconverters, you can get rid of a lot of that code and simplify the controller and view.

Listings 21 and 22 implement the Converter interface for the Group and Tagconverters, respectively:

Listing 21. Group converter implementing Converter interface

package com.arcmind.contact.converter;

import javax.faces.component.UIComponent;import javax.faces.context.FacesContext;import javax.faces.convert.Converter;import javax.faces.convert.ConverterException;import javax.faces.application.FacesMessage;

import com.arcmind.contact.model.Group;

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 27 of 52

Page 28: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

import com.arcmind.contact.model.GroupRepository;

public class GroupConverter implements Converter {

...}

Listing 22. Tag converter implementing Converter interface

package com.arcmind.contact.converter;

import javax.faces.component.UIComponent;import javax.faces.context.FacesContext;import javax.faces.convert.Converter;import com.arcmind.contact.model.Tag;import com.arcmind.contact.model.TagRepository;

public class TagConverter implements Converter {

...}

Implement the getAsObject() method

The next step is to implement the getAsObject() method, which converts a field(string) into an object. Listing 23 shows the getAsObject() method forGroupConverter:

Listing 23. getAsObject() method for GroupConverter

...public class GroupConverter implements Converter {

public Object getAsObject(FacesContext facesContext, UIComponent component,String value) {

GroupRepository repo = (GroupRepository) facesContext.getExternalContext().getApplicationMap().get("groupRepository");

Long id = Long.valueOf(value);if (id == -1L) {

throw new ConverterException(new FacesMessage(FacesMessage.SEVERITY_ERROR, "required", "required"));

}return repo.lookup(id);

}...

}

Notice that GroupConverter looks up the groupRepository and uses thegroupRepository to read the Group from the repository. Also, notice that itchecks to see if the value is -1L, and if it is, it puts out a required message bythrowing a new ConverterException.

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 28 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 29: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

The TagConverter is similar. It uses the tagRepository to look up the tagvalue. Listing 24 shows the getAsObject() method for TagConverter:

Listing 24. getAsObject() method for TagConverter

...

public class TagConverter implements Converter {

public Object getAsObject(FacesContext facesContext, UIComponent component,String value) {

TagRepository repo = (TagRepository) facesContext.getExternalContext().getApplicationMap().get("tagRepository");

return repo.lookup(Long.valueOf(value));}

...}

Neither converter does much error checking. If your repository object were actuallytalking to a database or cache server, you'd probably want to wrap thegetAsObject() method in a try/catch block, and add a FacesMessage withseverity of SEVERITY_FATAL if a problem occurs with the database — similarly tothe way you handle the -1L/required approach in the GroupConverter inListing 23.

Implement the getAsString method

JSF needs to display the currently selected value. It does this by calling thegetAsString() method of the Converter. Listing 25 implements thegetAsString() method for GroupConverter:

Listing 25. getAsString() method for GroupConverter

...public class GroupConverter implements Converter {

...

public String getAsString(FacesContext facesContext, UIComponent component,Object value) {

return value == null ? "-1" : "" + ((Group) value).getId();}

}

Listing 26 implements the getAsString() method for TagConverter:

Listing 26. getAsString() method for TagConverter

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 29 of 52

Page 30: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

...public class TagConverter implements Converter {

...

public String getAsString(FacesContext facesContext, UIComponent component,Object value) {

return value == null ? "-1" : "" + ((Tag) value).getId();}

}

Register your custom converter in the Faces context

After you write your converters, you need JSF to use them every time it sees a valuebinding that results in a Group or Tag. You do this by registering your converters inthe faces-config.xml file using the <converter> element, as shown in Listing 27:

Listing 27. Registering converters with faces-config.xml

<converter><converter-for-class>

com.arcmind.contact.model.Group</converter-for-class><converter-class>

com.arcmind.contact.converter.GroupConverter</converter-class>

</converter>

<converter><converter-for-class>

com.arcmind.contact.model.Tag</converter-for-class><converter-class>

com.arcmind.contact.converter.TagConverter</converter-class>

</converter>

Listing 27 specifies the converter class with the <converter-class> element andthe class you're providing conversion for with the <converter-for-class>element.

Making converters work with a list of tags

Sadly, the converters do not work with generics such as List<Tag>. JSF does notallow conversion to generic lists. (It should. The Java Persistence API [JPA] has noproblem handling List<Tag> for defining relationships. JPA and JSF 1.2 came outaround the same time for Java EE 5, so you'd think that both would supportgenerics.) To work around this problem, you can use arrays. Listing 28 shows theuse of a Tag array, instead of List<Tag>:

Listing 28. Using arrays instead of generics

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 30 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 31: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

public class Contact implements Serializable {...private List<Tag> tags;

public Tag[] getTags() {if (tags != null) {

return tags.toArray(new Tag[tags.size()]);} else {

return null;}

}

public void setTags(Tag[] tags) {this.tags = Arrays.asList(tags);

}

Section 6. JSF validators

The main purpose of conversion and validation is to ensure values have beenproperly sanitized before updating model data. Subsequently, when the time comesto invoke application methods to do something with that data, you can safely makecertain assumptions about the state of your model. Conversion and validation allowyou to focus on business logic rather than the tedious qualifications of input datasuch as null checks, length qualifiers, range boundaries, and so on.

It makes sense, then, that conversion and validation processes happen beforecomponent data is bound to your managed bean model in the update model data lifecycle phase. As you saw in the "JSF application life cycle" section, conversion andvalidation occur in the process validation phase — conversion first, then validation.

There are four forms of validation within JSF:

• Built-in validation components

• Application-level validation

• Validation methods in backing beans (inline)

• Custom validation components (which implement the Validatorinterface)

This section explains and demonstrates each of these forms.

Standard validation

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 31 of 52

Page 32: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

JSF supplies three standard validation components:

• DoubleRangeValidator: Component's local value must be numerictype; must be in range specified by minimum values, maximum values, orboth.

• LongRangeValidator: Component's local value must be numeric typeand convertible to long; must be in range specified by minimum values,maximum values, or both.

• LengthValidator: Type must be string; length must be in rangespecified by minimum values, maximum values, or both.

In the example application, a contact's age can be any valid integer. Because itdoesn't make sense to allow an age of, say, -2, you'll probably want to add somevalidation to the field. Listing 29 shows some simple validation code using<f:validateLongRange> for ensuring the model integrity of data in an age field:

Listing 29. Validating age for reasonable values using <f:validateLongRange>

<%-- age --%><h:outputLabel value="Age" for="age" accesskey="age" /><h:inputText id="age" size="3" value="#{contactController.contact.age}">

<f:validateLongRange minimum="0" maximum="150"/></h:inputText><h:message for="age" errorClass="errorClass" />

Once you have the age field sorted out, you might want to specify length restrictionson the First Name field, as shown in Listing 30.

Listing 30. Making sure firstName isn't too long or too short

<%-- First Name --%><h:outputLabel value="First Name" for="firstName" accesskey="f" /><h:inputText id="firstName" label="First Name" required="true"value="#{contactController.contact.firstName}" size="10" >

<f:validateLength minimum="2" maximum="25" /></h:inputText><h:message for="firstName" errorClass="errorClass" />

Although it works for many scenarios, JSF's built-in validation is somewhat limited.When you're dealing with e-mail validation, phone numbers, URLs, dates, and so on,it's sometimes better to write your own validator; that is discussed later in thissection. You can also use validators provided by Tomahawk, Shale,JSF-Validations, and Crank (see Resources).

Application-level validation

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 32 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 33: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

In concept, application-level validation is really business-logic validation. JSFseparates form- or field-level validation from business-logic validation. Basically,application-level validation entails adding code to the managed-bean methods thatuse the model to qualify the data already bound to your model. In the case of ashopping cart, form-level validation might validate whether a quantity entered isvalid, but you would need business-logic validation to check whether the user hadexceeded his or her credit limit. This is another example of the separation ofconcerns in JSF.

For example, let's say the user clicks a button that is bound to an action method,which is invoked during the invoke-application phase (refer back to Figure 5 fordetails). Prior to performing any manipulation of model data, which was presumablyupdated during the update model phase, you could add code that checks to see ifthe data entered is valid based on the application's business rules.

For instance, in the example application, the user clicks the Update/Add button,which is bound to the persist() method of the application controller. You couldadd validation code to the persist() method to determine whether thefirstName/lastName combination already exists in the system. In cases wherethe contact already exists, you could also add a message to the FacesContextand then direct JSF to stay on the current view by returning null for an outcome (ifnavigation rules apply to this action).

Let's revisit the contacts application and perform some application-level logic in thepersist() action method, as shown in Listings 31 and 32. Listing 31 shows theapplication-level validation logic in the controller:

Listing 31. Application-level validation logic in controller

public class ContactController {public String persist() {

/* Perform the application level validation. */try {

contact.validate();} catch (ContactValidationException contactValidationException) {

addErrorMessage(contactValidationException.getLocalizedMessage());return null;

}

/* Turn form off, turn link on. */form.setRendered(false);addNewCommand.setRendered(true);

/* Add a status message. */if (contactRepository.persist(contact) == null) {

addStatusMessage("Added " + contact);} else {

addStatusMessage("Updated " + contact);}return "contactPersisted";

}

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 33 of 52

Page 34: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

private void addErrorMessage(String message) {FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(

FacesMessage.SEVERITY_ERROR, message, null));}

In Listing 31, you can see that the persist() method calls validate() on thecontact object. It captures any exceptions and converts the exception errormessage into a FacesMessage. If an exception occurs, it returns an outcome ofnull, meaning stay on the current view and do not navigate to the next view.

The actual validation code is in the model — that is, the Contact class'svalidate()method, as shown in Listing 32. This is important: as you add morevalidation rules to the contact, you don't need to change the controller or the viewlayers.

Listing 32. Validation is in the model, not the controller

...public class Contact implements Serializable {

...public void validate() throws ContactValidationException {if (

(homePhoneNumber == null || "".equals(homePhoneNumber)) &&(workPhoneNumber == null || "".equals(workPhoneNumber)) &&(mobilePhoneNumber == null || "".equals(mobilePhoneNumber))

) {throw new ContactValidationException("At least one phone number" +

"must be set");

}}

Application-level validation is clearly straightforward and easy to do. Its advantagesare:

• Easy to implement

• No need for a separate class (custom validator)

• No need for page author to specify validator

The disadvantages of application-level validation are that it occurs after other formsof validation (standard, custom, and component), and error messages show up onlyafter other forms of validation have occurred.

Ultimately, application-level validation should be used only for circumstancesrequiring business-logic validation.

Custom validators in backing beans

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 34 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 35: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

You need to build your own custom validation components for data types that aren'tsupported by the standard JSF validators, including e-mail addresses and ZIPcodes. You also need to build your own validators in cases where you want explicitcontrol over the validation messages displayed to the end user. With JSF, you cancreate pluggable validation components that are reusable throughout your Webapplications.

As an alternative to creating a separate validator class, you can simply implementcustom validation in a backing bean method. This is better for applicationdevelopers. For instance, you might write a method in a managed bean to validatephone numbers, as shown in Listing 33:

Listing 33. Phone-number validation

public class ContactValidators {private static Pattern phoneMask;

static {String countryCode = "^[0-9]{1,2}";String areaCode = "(|-|\\(){1,2}[0-9]{3}(|-|\\)){1,2}";String prefix = "(|-)?[0-9]{3}";String number = "(|-)[0-9]{4}$";phoneMask = Pattern.compile(countryCode + areaCode + prefix + number);

}

public void validatePhone(FacesContext context, UIComponent component,Object value) throws ValidatorException {

String sValue = (String)value;

Matcher matcher = phoneMask.matcher(sValue);

if (!matcher.matches()) {FacesMessage message = new FacesMessage();message.setDetail("Phone number not valid");message.setSummary("Phone number not valid");message.setSeverity(FacesMessage.SEVERITY_ERROR);throw new ValidatorException(message);

}

}... //ADD MORE VALIDATION METHODS FOR THE APP HERE!

}

The ContactValidators class has a validatePhone() method. ThevalidatePhone() method uses the Java regex API to make sure that the stringentered is a valid phone number. If the value does not match the pattern, then thevalidatePhone() method throws a ValidatorException.

To use the ContactValidators class, you register it in the faces-config.xml file,as shown in Listing 34:

Listing 34. Registering ContactValidators as a managed bean

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 35 of 52

Page 36: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

<managed-bean><managed-bean-name>contactValidators</managed-bean-name><managed-bean-class>com.arcmind.contact.validators.ContactValidators</managed-bean-class><managed-bean-scope>application</managed-bean-scope></managed-bean>

To use the validator you use the validator attribute, as shown in Listing 35, forwork, home, and mobile phone-number entries:

Listing 35. Using the validator in the view with the validator attribute

<%-- Work --%><h:outputLabel value="Work" for="work" accesskey="w" /><h:inputText id="work"

value="#{contactController.contact.workPhoneNumber}" size="11"validator="#{contactValidators.validatePhone}" /><h:message for="work" errorClass="errorClass" /><%-- Home --%>

<h:outputLabel value="Home" for="home" accesskey="h" /><h:inputText id="home"

value="#{contactController.contact.homePhoneNumber}" size="11"validator="#{contactValidators.validatePhone}" />

<h:message for="home" errorClass="errorClass" /><%-- Mobile --%>

<h:outputLabel value="Mobile" for="mobile" accesskey="m" /><h:inputText id="mobile"

value="#{contactController.contact.mobilePhoneNumber}" size="11"validator="#{contactValidators.validatePhone}" />

<h:message for="mobile" errorClass="errorClass" />

As you can see, you bind the validatePhone() method to the <h:inputText>components: <h:inputText id="mobile" ...validator="#{contactValidators.validatePhone}".

Using a managed bean for validation is good for application developers. However, ifyou're developing a reusable framework or a reusable set of components, it might bebetter to create standalone custom validators.

Standalone custom validators

With JSF, you can create pluggable validation components that are reusablethroughout your Web applications.

To create a custom validator, you perform the following steps:

1. Create a class that implements the Validator interface(javax.faces.validator.Validator).

2. Implement the validate() method.

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 36 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 37: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

3. Register your custom validator in the faces-config.xml file.

4. Use the <f:validator/> tag in your JSPs.

Let's look at these steps one by one, including the example code for creating acustom validator.

Step 1: Implement the Validator interface

The first step is to implement the Validator interface, as shown in Listing 36:

Listing 36. Implement the Validator interface

package com.arcmind.validators;

import javax.faces.application.FacesMessage;import javax.faces.component.UIComponent;import javax.faces.context.FacesContext;import javax.faces.validator.Validator;import javax.faces.validator.ValidatorException;

import java.util.regex.Matcher;import java.util.regex.Pattern;

public class ZipCodeValidator implements Validator {

/** Accepts zip codes like 85710 */private static final String ZIP_REGEX = "[0-9]{5}";

/** Optionally accepts a plus 4 */private static final String PLUS4_OPTIONAL_REGEX = "([ |-]{1}[0-9]{4})?";

private static Pattern mask = null;

static {mask = Pattern.compile(ZIP_REGEX + PLUS4_OPTIONAL_REGEX);

}

Step 2: Implement the validate() method

Next, you need to implement the validate() method, as shown in Listing 37:

Listing 37. Implement the validate() method

public class ZipCodeValidator implements Validator {

...public void validate(FacesContext context, UIComponent component,

Object value) throws ValidatorException {

/* Get the string value of the current field */String zipField = (String) value;

/* Check to see if the value is a zip code */Matcher matcher = mask.matcher(zipField);

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 37 of 52

Page 38: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

if (!matcher.matches()) {

FacesMessage message = new FacesMessage();message.setDetail("Zip code not valid");message.setSummary("Zip code not valid");message.setSeverity(FacesMessage.SEVERITY_ERROR);throw new ValidatorException(message);

}

}}

Step 3: Register your custom validator

The code to register the custom validator with the FacesContext, as shown inListing 38, should look familiar to you by now:

Listing 38. Register your custom validator in faces-config.xml

<validator><validator-id>arcmind.zipCode</validator-id><validator-class>com.arcmind.validators.ZipCodeValidator</validator-class>

</validator>

Step 4: Use the <f:validator> tag in your JSPs

The <f:validator/> tag declares use of the zipCode validator, as shown inListing 39:

Listing 39. Use <f:validator> tag in your JSPs

<%-- zip --%><h:outputLabel value="Zip" for="zip" accesskey="zip" /><h:inputText id="zip" size="5"

value="#{contactController.contact.zip}"><f:validator validatorId="arcmind.zipCode"/>

</h:inputText><h:message for="zip" errorClass="errorClass" />

Overall, creating custom validators is fairly straightforward and makes validationreusable across many applications. The downside is that you do have to createanother class and manage validator registration within the faces context. However,you can take the implementation of your custom validator one step further and makeit look like built-in validation by creating a custom tag that uses the validator. Forcommon validation concerns, such as e-mail validation, this approach can supportdesign philosophies where code reuse and consistent application behavior are ofutmost importance.

Validation and conversion redux

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 38 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 39: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

By the time you get to the validation phase, the conversion has already happened. If,for example, you have an int property bound to an inputText field, then the fieldwill be converted before you get to validate it.

Suppose you have a PhoneNumber value object, and instead of using Strings tostore phone numbers in the Contact, you use PhoneNumber. Then the validationrule in Listing 33 for phone number would not make so much sense. In fact, thatvalidation rule only proved that the String was in the format of a phone number.That logic should be really be part of the converter, as demonstrated by Listing 40:

Listing 40. Conversion and validator redux: PhoneConverter

package com.arcmind.contact.converter;

import java.util.regex.Pattern;

import javax.faces.application.FacesMessage;import javax.faces.component.UIComponent;import javax.faces.context.FacesContext;import javax.faces.convert.Converter;import javax.faces.convert.ConverterException;

import com.arcmind.contact.model.PhoneNumber;

/*** @author Richard Hightower**/public class PhoneConverter implements Converter {

private static Pattern phoneMask;static {

String countryCode = "^[0-9]{1,2}";String areaCode = "( |-|\\(){1,2}[0-9]{3}( |-|\\)){1,2}";String prefix = "( |-)?[0-9]{3}";String number = "( |-)[0-9]{4}$";phoneMask = Pattern.compile(countryCode + areaCode + prefix + number);

}

public Object getAsObject(FacesContext context, UIComponent component,String value) {

System.out.println("PhoneConverter.getAsObject()");

if (value.isEmpty()) {return null;

}/* Before we parse, let's see if it really is a phone number. */if (!phoneMask.matcher(value).matches()) {

FacesMessage message = new FacesMessage();message.setDetail("Phone number not valid");message.setSummary("Phone number not valid");message.setSeverity(FacesMessage.SEVERITY_ERROR);throw new ConverterException(message);

}

/* Now let's parse the string and populate a phone number object. */PhoneNumber phone = new PhoneNumber();phone.setOriginal(value);String[] phoneComps = value.split("[ ,()-]");String countryCode = phoneComps[0];phone.setCountryCode(countryCode);

if ("1".equals(countryCode) && phoneComps.length == 4) {

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 39 of 52

Page 40: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

phone.setAreaCode(phoneComps[1]);phone.setPrefix(phoneComps[2]);phone.setNumber(phoneComps[3]);

} else if ("1".equals(countryCode) && phoneComps.length != 4) {throw new ConverterException(new FacesMessage(

"No Soup for you butter fingers!"));} else if (phoneComps.length == 1 && value.length() > 10){

phone.setCountryCode(value.substring(0,1));phone.setAreaCode(value.substring(1,4));phone.setPrefix(value.substring(4,7));phone.setNumber(value.substring(7));

} else {phone.setNumber(value);

}return phone;

}

public String getAsString(FacesContext context, UIComponent component,Object value) {

System.out.println("PhoneConverter.getAsString()");return value.toString();

}}

The nice thing about converters, unlike validators, is that you can register them infaces-config.xml (see Listing 27) so that the converter is tied to a particular class.Any time that class shows up in an expression language (EL) value binding, theconverter is used automatically; you don't need to add a <f:converter> to theJSP. The new phone number converter is automatically applied to PhoneNumbers,and you don't need to specify the converter in the view.

Since the old phone number validation is part of the phone number conversion, youmight ask what a phone number validator would look like now. This question can beanswered by writing a validator, shown in Listing 41, that proves a phone number isfrom Arizona:

Listing 41. Make sure the phone number is in AZ

package com.arcmind.contact.validators;

import javax.faces.application.FacesMessage;import javax.faces.component.UIComponent;import javax.faces.context.FacesContext;import javax.faces.validator.ValidatorException;

import com.arcmind.contact.model.PhoneNumber;

public class ContactValidators {

public void validatePhone(FacesContext context, UIComponent component,Object value) throws ValidatorException {

System.out.println("ContactValidators.validatePhone()");PhoneNumber phoneNumber = (PhoneNumber)value;

if (!phoneNumber.getAreaCode().equals("520")&& !phoneNumber.getAreaCode().equals("602")) {FacesMessage message = new FacesMessage();

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 40 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 41: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

message.setDetail("Arizona residents only");message.setSummary("Arizona residents only");message.setSeverity(FacesMessage.SEVERITY_ERROR);throw new ValidatorException(message);

}

}

}

Notice that this phone number validator, unlike the previous one, does not deal withStrings. By the time it gets called, the converter has already been called. Thus, thevalue is not a String but a PhoneNumber.

Section 7. Working with phase listeners

PhaseListener, according to the JSF API documentation (see Resources), is "aninterface implemented by objects that wish to be notified at the beginning and endingof processing for each standard phase of the request processing lifecycle" (SunMicrosystems Inc., 2006). Now that you've written some converters, validators, andaction methods, you'll write some phase listeners in this section. Prior to JSF 1.2,PhaseListeners were globally defined. In JSF 1.2, PhaseListener events canbe registered for at the view level or using the <f:phaseListenerbinding="..." /> tag.

Implementing phase listeners

To implement a PhaseListener, you implement the PhaseListener interface, asshown in Listing 42:

Listing 42. DebugPhaseListener

package com.arcmind.phase;

import javax.faces.event.PhaseEvent;import javax.faces.event.PhaseId;import javax.faces.event.PhaseListener;

@SuppressWarnings("serial")public class DebugPhaseListener implements PhaseListener {

public void beforePhase(PhaseEvent phaseEvent) {System.out.println("------ BEFORE PHASE " + phaseEvent.getPhaseId());

}

public void afterPhase(PhaseEvent phaseEvent) {System.out.println("------ AFTER PHASE " + phaseEvent.getPhaseId());

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 41 of 52

Page 42: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

if (phaseEvent.getPhaseId() == PhaseId.RENDER_RESPONSE) {System.out.println("REQUEST END\n\n");

}

}

public PhaseId getPhaseId() {return PhaseId.ANY_PHASE;

}

}

The PhaseListener notifies you before and after each JSF phase. You tell JSFwhich phases you are interested in with the getPhaseId method.PhaseId.ANY_PHASE implies that you want to be notified before and after eachphase. The DebugPhaseListener prints out the phase event name so you cansee where things happen. I've added System.out.println statements to everyvalidation, conversion, and action method. I also added a System.out.println tothe firstName property's getter/setter so you can see when it's accessed.

Next, you must register the phase listener in faces-config.xml, as shown in Listing43:

Listing 43. Registering DebugPhaseListener in faces-config.xml

<lifecycle><phase-listener>com.arcmind.phase.DebugPhaseListener</phase-listener>

</lifecycle>

Phase-listener output

Listing 44 shows what happens when you load the form for the first time:

Listing 44. Output of DebugPhaseListener when form first loads

------ BEFORE PHASE RESTORE_VIEW 1------ AFTER PHASE RESTORE_VIEW 1------ BEFORE PHASE RENDER_RESPONSE 6ContactController.getContacts()------ AFTER PHASE RENDER_RESPONSE 6REQUEST END

Based on the code in Listing 42, JSF sees that this is an initial request for a view,uses the JSP to build the view, and then goes immediately to the render responsephase. Notice that the controller's getContacts() method is called during therender response phase.

When you click the Add New link, you see the output shown in Listing 45:

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 42 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 43: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

Listing 45. Output of DebugPhaseListener after the addNew() method is called

------ BEFORE PHASE RESTORE_VIEW 1------ AFTER PHASE RESTORE_VIEW 1------ BEFORE PHASE APPLY_REQUEST_VALUES 2------ AFTER PHASE APPLY_REQUEST_VALUES 2------ BEFORE PHASE PROCESS_VALIDATIONS 3------ AFTER PHASE PROCESS_VALIDATIONS 3------ BEFORE PHASE UPDATE_MODEL_VALUES 4------ AFTER PHASE UPDATE_MODEL_VALUES 4------ BEFORE PHASE INVOKE_APPLICATION 5ContactController.addNew()------ AFTER PHASE INVOKE_APPLICATION 5------ BEFORE PHASE RENDER_RESPONSE 6Contact.getFirstName()ContactController.getGroups()ContactController.getGroups()ContactController.getAvailableTags()ContactController.getContacts()------ AFTER PHASE RENDER_RESPONSE 6REQUEST END

The addNew() method causes the form to render. Since the addNew() is apostback, JSF goes through all of the phases. Since the form was not renderedbefore the addNew() method is called, its fields are not processed before theaddNew() method is called.

Next, enter a bad ZIP code (such as aaa) and a bad phone number (such as aaa).Then select two tags and a group, and hit the Add button. You'll see the outputshown in Listing 46:

Listing 46. Output after entering a bad zip, bad phone, a group, and some tags

------ BEFORE PHASE RESTORE_VIEW 1------ AFTER PHASE RESTORE_VIEW 1------ BEFORE PHASE APPLY_REQUEST_VALUES 2------ AFTER PHASE APPLY_REQUEST_VALUES 2------ BEFORE PHASE PROCESS_VALIDATIONS 3GroupConverter.getAsObjectContactController.getAvailableGroups()ZipCodeValidator.validate()ContactValidators.validatePhone()TagConverter.getAsObjectTagConverter.getAsObjectContactController.getAvailableTags()ContactController.getAvailableTags()------ AFTER PHASE PROCESS_VALIDATIONS 3------ BEFORE PHASE RENDER_RESPONSE 6ContactController.getAvailableGroups()GroupConverter.getAsStringGroupConverter.getAsStringGroupConverter.getAsStringContactController.getAvailableTags()TagConverter.getAsStringTagConverter.getAsStringTagConverter.getAsStringTagConverter.getAsStringContactController.getContacts()------ AFTER PHASE RENDER_RESPONSE 6REQUEST END

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 43 of 52

Page 44: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

In the process validation phase, the GroupConverter.getAsObject() method iscalled for the group that was selected. TagConverter.getAsObject() is calledtwice, once for each tag that was selected. Both the custom validators you wrote —ZipCodeValidator.validate() andContactValidators.validatePhone() — are called in the process validationphase. In the render response phase the converters' getAsString() methods getcalled per object in the list for available groups and available tags.

Now you can imagine if ContactController.getAvailableGroups() andContactController.getAvailableTags() hit the database each time that youwould be hitting the database four times to get these lists. You would want to addsome logic so that you would hit the database only once per request. You could, forexample, load the data from the action only if it was not already loaded.

A discussion of JSF phases would not be complete without showing the output fromsubmitting a valid form, as shown in Listing 47:

Listing 47. All the way through

------ BEFORE PHASE RESTORE_VIEW 1------ AFTER PHASE RESTORE_VIEW 1------ BEFORE PHASE APPLY_REQUEST_VALUES 2------ AFTER PHASE APPLY_REQUEST_VALUES 2------ BEFORE PHASE PROCESS_VALIDATIONS 3GroupConverter.getAsObjectContactController.getAvailableGroups()ZipCodeValidator.validate()ContactValidators.validatePhone()ContactValidators.validatePhone()ContactValidators.validatePhone()TagConverter.getAsObjectTagConverter.getAsObjectContactController.getAvailableTags()ContactController.getAvailableTags()------ AFTER PHASE PROCESS_VALIDATIONS 3------ BEFORE PHASE UPDATE_MODEL_VALUES 4Contact.setFirstName()------ AFTER PHASE UPDATE_MODEL_VALUES 4------ BEFORE PHASE INVOKE_APPLICATION 5ContactController.persist()------ AFTER PHASE INVOKE_APPLICATION 5------ BEFORE PHASE RENDER_RESPONSE 6ContactController.getContacts()ContactController.getContacts()ContactController.getContacts()ContactController.getContacts()ContactController.getContacts()ContactController.getContacts()Contact.getFirstName()ContactController.getContacts()ContactController.getContacts()------ AFTER PHASE RENDER_RESPONSE 6REQUEST END

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 44 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 45: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

Listing 47 shows going through all phases of the request life cycle. Notice that youget to the INVOKE_APPLICATION phase and the persist() method is called:ContactController.persist().

Adding an object-level validation framework

You could use a PhaseListener to capture phase-listener events and change theway JSF handles requests. Suppose you want to add your own object-levelvalidation framework to JSF. You might use an interface like the one shown inListing 48:

Listing 48. Validator interface

package com.arcmind.contact.model;

public interface Validateable {public void validate() throws ValidationException;

}

Then you change your Contact object to implement this interface, as shown inListing 49:

Listing 49. Contact implementing validator interface

package com.arcmind.contact.model;

import java.io.Serializable;import java.util.Arrays;import java.util.Date;import java.util.List;

@SuppressWarnings("serial")public class Contact implements Serializable, Validateable {

...public void validate() throws ValidationException {

if (homePhoneNumber == null &&workPhoneNumber == null &&mobilePhoneNumber == null

) {throw new ValidationException("At least one phone number " +"must be set", "");

}}

...

You could create a PhaseListener that looks for Validateable objects andvalidates them. The PhaseListener could take the exception messages andconvert them into FacesMessages, but how would you know when the object needsto be validated? It seems that knowledge would only be in the controller class. Thus,to handle this in a generic way, you could create a superclass controller that creates

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 45 of 52

Page 46: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

an anonymous inner PhaseListener class that you could bind to, as shown inListing 50:

Listing 50. Base class with PhaseListener

package com.arcmind.contact.controller;

import java.io.Serializable;

import javax.faces.application.FacesMessage;import javax.faces.context.FacesContext;import javax.faces.event.PhaseEvent;import javax.faces.event.PhaseId;import javax.faces.event.PhaseListener;

import com.arcmind.contact.model.Validateable;import com.arcmind.contact.model.ValidationException;

@SuppressWarnings("serial")public abstract class AbstractCrudController implements Serializable {

private boolean edit;

public PhaseListener phaseListener = new PhaseListener() {

public void afterPhase(PhaseEvent event) {validate();

}

public void beforePhase(PhaseEvent event) {

}

public PhaseId getPhaseId() {return PhaseId.UPDATE_MODEL_VALUES;

}

};

abstract Object getFormObject(); //subclass defines this

private void validate() {Object form = getFormObject();if (! (form instanceof Validateable) || form == null) {

return;}Validateable validateable = (Validateable) form;try {

validateable.validate(); //validate object} catch (ValidationException validationException) {

FacesContext.getCurrentInstance().renderResponse(); //Do not invoke application.addErrorMessage(validationException.getMessage());

}}

public PhaseListener getPhaseListener() {return phaseListener;

}

protected void addErrorMessage(String message) {FacesContext.getCurrentInstance().addMessage(null, new FacesMessage(

FacesMessage.SEVERITY_ERROR, message, null));}

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 46 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 47: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

public boolean isEdit() {return edit;

}

public void setEdit(boolean edit) {this.edit = edit;

}

}

Notice that the PhaseListener listens only to UPDATE_MODEL_VALUES. It thencalls the validate() method, which checks to see if the form object is presentand is an instance of Validator before it calls validate() on the domain object.It then calls validate() and allows the model to do the validation. Also notice thatif the model validation fails, the validate() method of theAbstractCrudCOntroller callsFacesContext.getCurrentInstance().renderResponse(), which forcesJSF to skip the INVOKE_APPLICATION phase.

Now you'd change ContactController to subclassAbstractCrudController, as shown in Listing 51:

Listing 51. ContactController extends AbstractCrudController to providePhaseListener validation support

public class ContactController extends AbstractCrudController{...public void addNew() {

... same as before except sets the edit mode.super.setEdit(true);

}public void persist() {

... same as before except sets the edit mode.super.setEdit(false);

}

public void read() {... same as before except sets the edit mode.super.setEdit(true);

}

@OverrideObject getFormObject() {

if (super.isEdit()) {return this.contact;

} else {return null;

}}

}

Notice that Listing 51 implements the abstract getFormObject from theAbstractCrudController. It uses the edit mode so that it returns the contactobject only when the controller is in edit mode. In this way, the controller decides

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 47 of 52

Page 48: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

when the object should be validated.

Lastly, you need to register your phase listener, as shown in Listing 52:

Listing 52. Binding to the phaseListener property

<f:view><h3>Contacts (4th version)</h3><f:phaseListener binding="#{contactController.phaseListener}" />

With phase listeners, you can change how JSF handles requests, thereby extendingthe JSF framework to fit your needs.

Section 8. Conclusion

This tutorial covered JSF's request processing life cycle and demonstrated some ofthe essential features of its component model. It covered quite a bit when it comes toJSF conversion and validation. In fact, it has covered most of what you'll ever needto know about these processes to make them work in your applications (at least forthis version of JSF)! Of course, this tutorial couldn't cover everything. For example,you might want to check out Tomahawk, Crank, Shale, and jsf-comp for validatorcomponents not offered in JSF or discussed here (see Resources). And althoughthis tutorial discussed the most common conversion and validation techniques, thereare others this tutorial didn't include.

Something to keep in mind is that conversion and validation don't necessarily workwell together. Conversion converts strings into objects, whereas most of thestandard validators work on strings. Therefore, you must execute caution whenusing custom converters and validators together. For instance, this tutorial'sPhoneNumber object would not work with a length validator. In this case, you wouldeither have to write a custom validator as well, or simply include any specialvalidation logic in the custom converter. I prefer the latter option because it allowsyou simply to associate a custom converter (with built-in validation logic) with aspecific object type and have JSF handle that object type. JSF does this for youautomatically without making you specify any converter ids in the JSP. (Of course,some might call this lazy programming, and it isn't necessarily the best solution forall use cases.)

JSF provides a flexible, powerful, and pluggable framework for Web applicationdevelopment. In addition to standard converters and validators, JSF facilitatescustom implementations to accommodate both application and framework

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 48 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 49: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

developers alike. Ultimately, the conversion and validation strategy you choose is upto you. JSF allows you to easily and quickly get started (standard converters,validators, inline validation) during prototyping, and migrate to more sophisticatedproduction solutions (custom objects, custom messages) during later developmentphases. And throughout it all, the JSF application life cycle provides a reliableinfrastructure for consistently ensuring data-model integrity.

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 49 of 52

Page 50: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

Downloads

Description Name Size Download method

Sample code for this article jsf12-part2-sample_code.zip239KB HTTP

Information about download methods

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 50 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.

Page 51: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

Resources

Learn

• "Getting started with JavaServer Faces 1.2, Part 1: Building basic applications"(developerWorks, December 2007): Get an overview JSF's features, and learnhow to write a basic JSF application. Build and modify a simple calculatorapplication.

• Source code instructions: Find instructions on how to run this tutorial's sourcecode with Eclipse JEE, and with Tomcat and Maven 2.

• JSF Tag Library Documentation: Official documentation for the html and coretag libraries.

• JSF API: API Javadocs for the JSF specification.

• "Web Tier to Go With Java EE 5: Summary of New Features in JavaServerFaces 1.2 Technology": (Jennifer Ball and Ed Burns, java.sun.com, February2006): Read about JSF 1.2's new features.

• "Web Tier to Go With Java EE 5: Summary of New Features in JSP 2.1Technology" (Pierre Delisle and Jennifer Ball, java.sun.com, February 2006):Get an overview of the new features introduced in version 2.1 of JSPtechnology.

• java.text.SimpleDateFormat : Find a list of date-format patterns here.

• Crank: Crank is a master/detail, CRUD, and annotation-driven validationframework built with JPA, JSF, Facelets, and Ajax. Crank is a use-case analysisof what is possible with the new JSF 2.0 stack.

• JSF-comp: A component library for client-side validation.

• Apache Shale: A JSF-based Web application framework.

• Apache MyFaces Tomahawk: JSF-compatible components.

• Facelets: Facelets is a view technology that focuses on building JSF componenttrees.

• "Facelets fits JSF like a glove" (developerWorks, February 2006): In this article,get an introduction to Facelets' easy HTML-style templating and reusablecomposition components.

• "Advanced Facelets programming" (Richard Hightower, developerWorks, May2006): Read about advanced ways to bridge the gap between JSF and EL.

• Browse the technology bookstore for books on these and other technical topics.

• developerWorks Java technology zone: Hundreds of articles about every aspectof Java programming.

ibm.com/developerWorks developerWorks®

JSF life cycle, conversion, validation, and phase listeners© Copyright IBM Corporation 1994, 2008. All rights reserved. Page 51 of 52

Page 52: Getting started with JavaServer Faces 1.2, Part 2: JSF ... · This tutorial series covers how to get started with Java™ Server Faces (JSF) technology, a server-side framework that

Get products and technologies

• Build your next development project with IBM trial software, available fordownload directly from developerWorks.

Discuss

• Check out developerWorks blogs and get involved in the developerWorkscommunity.

About the author

Richard HightowerRick Hightower serves as chief technology officer for ArcMind Inc.. He is coauthor ofthe popular book Java Tools for Extreme Programming and coauthor of ProfessionalStruts and Struts Live . Rick is the founding developer on the Crank project, aJSF/Facelets, Ajax, CRUD framework for idiomatically developing GUIs.

Trademarks

Java and all Java-based trademarks are trademarks of Sun Microsystems, Inc. in theUnited States, other countries, or both.

developerWorks® ibm.com/developerWorks

JSF life cycle, conversion, validation, and phase listenersPage 52 of 52 © Copyright IBM Corporation 1994, 2008. All rights reserved.