Previous Section  < Day Day Up >  Next Section

7.1 Dealing with Syntax Errors in User Input

In Chapter 6, we created a few components, arranged them as a component tree in a view, rendered them to the browser, and saved the view state either in the response itself or on the server. Presumably, there's a user sitting there admiring our nice form with an input field and a button. Let's see what happens when she submits the form.

First, JSF must figure out which view to use for processing the request. This information is encoded in the URI itself. As you may recall from Chapter 6, we've decided to use the default extension mapping for JSF requests, so the request paths always end with a .faces suffix. The view ID is the context-relative path with .faces replaced by .jsp. So with a context-relative path like /test.faces, the view ID is /test.jsp. JSF uses this ID to locate the state information on the server, or retrieve it from the request data if it was previously passed to the client with the response. It rebuilds the component tree, and then calls restoreState() on all components so they can get back their old internal state.

Next, each component is asked to look for its value in the request. JSF calls a method called decode() on each component to accomplish this task. As with rendering, components typically delegate this task to an associated renderer; if a renderer was used to render the component, it's the one most likely to know how to find the component's value in the request.

For HTML, component values are sent as HTTP request parameter String values, with names corresponding to the component IDs. The decode() method saves this raw parameter value as the submitted value for the component.

When all components have had a chance to read their values from the request, JSF asks them to validate the new value by calling the validate() method. If a component is bound to a model property (via a value binding) of a different type than String, the first part of validation is trying to convert the value to the model property's data type. I mentioned in Chapter 6 that JSF provides a set of standard converters for numbers, Boolean values, and time values, and you can also register custom converters for other data types. A converter is a subclass of the javax.faces.convert.Converter class and must provide implementations of two abstract methods:

public Object getAsObject(javax.faces.context.FacesContext, 

    javax.faces.component.UIComponent component, String value);



public String getAsString(javax.faces.context.FacesContext, 

    javax.faces.component.UIComponent component, Object value);

Converters are registered with JSF either under a symbolic name or for a specific data type (i.e., a fully qualified class or interface name). You rarely need a custom converter, but how to register one is described in Appendix E. JSF register the converters in Table 7-1 and Table 7-2 by default, and they are sufficient for most applications.

Table 7-1. Standard converters registered by name

Symbolic name

Class name

javax.faces.DateTime

javax.faces.convert.DateTimeConverter

javax.faces.Number

javax.faces.convert.NumberConverter

Table 7-2. Standard converters registered by type

Type

Symbolic name

Class name

Boolean and boolean

javax.faces.Boolean

javax.faces.convert.BooleanConverter

Byte and byte

javax.faces.Byte

javax.faces.convert.ByteConverter

Character and char

javax.faces.Character

javax.faces.convert.CharacterConverter

Double and double

javax.faces.Double

javax.faces.convert.DoubleConverter

Float and float

javax.faces.Float

javax.faces.convert.FloatConverter

Integer and int

javax.faces.Integer

javax.faces.convert.IntegerConverter

Long and long

javax.faces.Long

javax.faces.convert.LongConverter

Short and short

javax.faces.Short

javax.faces.convert.ShortConverter

The converters in Table 7-1 can be configured and attached to a component, with JSF converter action elements in a JSP page as shown in Chapter 6. Behind the scenes, the tag handlers call the setConverter() method on a component, as shown in Example 7-1.

Example 7-1. Creating and configuring a converter
import javax.faces.application.Application;

import javax.faces.component.UIInput;

import javax.faces.context.FacesContext;

import javax.faces.convert.DateTimeConverter;

...

public class ConvertDateTimeTag extends TagSupport {

    private String dateStyle;

    ...



    public int doStartTag( ) throws JspException {

        ...

        FacesContext context = FacesContext.getCurrentInstance( )

        Application application = context.getApplication( );

        DateTimeConverter dateTimeConv = (DateTimeConverter)

            application.createConverter("javax.faces.DateTime");

        dateTimeConv.setDateStyle(dateStyle);

        input.setConverter(dateTimeConv);

        ...

    }

}

Pretty much everything in JSF is pluggable, with the concrete classes to use being declared in the faces-config.xml file (default classes are used if no replacements are defined). An abstract class named javax.faces.application.Application defines methods for getting hold of registered converters, among other things. The getApplication() method on FacesContext provides access to an instance of a concrete Application subclass. With access to the Application, the code in Example 7-1 asks it to create an instance of the converter class registered under the symbolic name javax.faces.DateTime, and then sets the converter's dateStyle property and tells the component to use the converter.

When a component is equipped with a converter, the validate( ) method[1] calls its getAsObject( ) method to convert the request parameter String value. If the component doesn't have a converter, the validate( ) method uses the data type of the property pointed to by the value binding to locate a matching converter, i.e., one of the standard converters in Table 7-2 or a custom converter registered for the type.

[1] The validate() method actually delegates the conversion to the renderer, if any, which in turn calls the converter. This allows a fancy renderer that represents a single value as more than one user interface element (e.g., a date represented by three select lists for year, month, and day) to be in control of converting the submitted value (or values, in this example).

Things can go bad at this stage, of course: maybe there's no converter for the data type or the conversion fails due to syntax errors. In either case, the validate() method marks the submitted value as invalid and adds an error message to a list maintained by the FacesContext. That's where the <h:messages> action introduced in Chapter 6 finds the messages and displays them on the page. The same list is used for error messages produced by validators, so we'll return to what a message looks like and how you can define custom messages after we've looked at the validation process.

If the conversion is successful, and there are no validators registered for the component, the converted value is saved as the component's local value and is later used to update the model property value.

    Previous Section  < Day Day Up >  Next Section