Creating a Custom Validator
If the standard validators don't perform the validation checking you need, you can easily create a custom validator to validate user input. As explained in Validation Model (page 291), there are two ways to implement validation code:
Writing a Method to Perform Validation explains how to implement a backing bean method to perform validation. The rest of this section explains how to implement the
Validatorinterface.If you choose to implement the
Validatorinterface and you want to allow the page author to configure the validator's attributes from the page, you also must create a custom tag for registering the validator on a component.If you prefer to configure the attributes in the
Validatorimplementation, you can forgo creating a custom tag and instead let the page author register the validator on a component using thevalidatortag, as described in Using a Custom Validator (page 375).You can also create a backing bean property that accepts and returns the
Validatorimplementation you create as described in Writing Properties Bound to Converters, Listeners, or Validators. The page author can use thevalidatortag's binding attribute to bind theValidatorimplementation to the backing bean property.Usually, you will want to display an error message when data fails validation. You need to store these error messages in resource bundle, as described in Creating a Resource Bundle.
After creating the resource bundle, you have two ways to make the messages available to the application. You can queue the error messages onto the
FacesContextprogrammatically. Or, you can have the application architect register the error messages using the application configuration resource file, as explained in Registering Custom Error Messages (page 461).The Duke's Bookstore application uses a general-purpose custom validator (called
FormatValidator) that validates input data against a format pattern that is specified in the custom validator tag. This validator is used with the Credit Card Number field on thebookcashier.jsppage. Here is the custom validator tag:<bookstore:formatValidator formatPatterns="9999999999999999|9999 9999 9999 9999| 9999-9999-9999-9999"/>According to this validator, the data entered in the field must be either:
The rest of this section describes how this validator is implemented and how to create a custom tag so that the page author can register the validator on a component.
Implementing the Validator Interface
A
Validatorimplementation must contain a constructor, a set of accessor methods for any attributes on the tag, and avalidatemethod, which overrides thevalidatemethod of theValidatorinterface.The
FormatValidatorclass also defines accessor methods for setting theformatPatternsattribute, which specifies the acceptable format patterns for input into the fields. In addition, the class overrides thevalidatemethod of theValidatorinterface. This method validates the input and also accesses the custom error messages to be displayed when theStringis invalid.The
validatemethod performs the actual validation of the data. It takes theFacesContextinstance, the component whose data needs to be validated, and the value that needs to be validated. A validator can validate only data of a component that implementsEditableValueHolder.Here is the
validatemethod fromFormatValidator:public void validate(FacesContext context, UIComponent component, Object toValidate) { boolean valid = false; String value = null; if ((context == null) || (component == null)) { throw new NullPointerException(); } if (!(component instanceof UIInput)) { return; } if ( null == formatPatternsList || null == toValidate) { return; } value = toValidate.toString(); //validate the value against the list of valid patterns. Iterator patternIt = formatPatternsList.iterator(); while (patternIt.hasNext()) { valid = isFormatValid( ((String)patternIt.next()), value); if (valid) { break; } } if ( !valid ) { FacesMessage errMsg = MessageFactory.getMessage(context, FORMAT_INVALID_MESSAGE_ID, (new Object[] {formatPatterns})); throw new ValidatorException(errMsg); } }This method gets the local value of the component and converts it to a
String. It then iterates over theformatPatternsListlist, which is the list of acceptable patterns as specified in theformatPatternsattribute of the custom validator tag.While iterating over the list, this method checks the pattern of the component's local value against the patterns in the list. If the pattern of the local value does not match any pattern in the list, this method generates an error message. It then passes the message to the constructor of
ValidatorException. Eventually the message is queued onto theFacesContextinstance so that the message is displayed on the page during the render response phase.The error messages are retrieved from the
Applicationinstance byMessageFactory. An application that creates its own custom messages must provide a class, such asMessageFactory, that retrieves the messages from theApplicationinstance. When creating your own application, you can simply copy theMessageFactoryclass from the Duke's Bookstore application to your application.The
getMessage(FacesContext, String, Object)method ofMessageFactorytakes aFacesContext, a staticStringthat represents the key into thePropertiesfile, and the format pattern as anObject. The key corresponds to the static message ID in theFormatValidatorclass:When the error message is displayed, the format pattern will be substituted for the
{0}in the error message, which, in English, isJavaServer Faces applications can save the state of validators and components on either the client or the server. Specifying Where State Is Saved (page 476) explains how to configure your application to save state on either the client or the server.
If your JavaServer Faces application saves state on the client (which is the default), you need to make the
Validatorimplementation implementStateHolderas well asValidator. In addition to implementingStateHolder, theValidatorimplementation needs to implement thesaveState(FacesContext)andrestoreState(FacesContext, Object)methods ofStateHolder. With these methods, theValidatorimplementation tells the JavaServer Faces implementation which attributes of theValidatorimplementation to save and restore across multiple requests.To save a set of values, you must implement the
saveState(FacesContext)method. This method is called during the render response phase, during which the state of the response is saved for processing on subsequent requests. When implementing thesaveState(FacesContext)method, you need to create an array of objects and add the values of the attributes you want to save to the array. Here is thesaveState(FacesContext)method fromFormatValidator:public Object saveState(FacesContext context) { Object values[] = new Object[2]; values[0] = formatPatterns; values[1] = formatPatternsList; return (values); }To restore the state saved with the
saveState(FacesContext)method in preparation for the next postback, theValidatorimplementation implementsrestoreState(FacesContext, Object). TherestoreState(FacesContext, Object)method takes theFacesContextinstance and anObjectinstance, which represents the array that is holding the state for theValidatorimplementation. This method sets theValidatorimplementation's properties to the values saved in theObjectarray. Here is therestoreState(FacesContext, Object)method fromFormatValidator:public void restoreState(FacesContext context, Object state) { Object values[] = (Object[]) state; formatPatterns = (String) values[0]; formatPatternsList = (ArrayList) values[1]; }As part of implementing
StateHolder, the customValidatorimplementation must also override theisTransientandsetTransient(boolean)methods ofStateHolder. By default,transientValueis false, which means that theValidatorimplementation will have its state information saved and restored. Here are theisTransientandsetTransient(boolean)methods ofFormatValidator:private boolean transientValue = false; public boolean isTransient() { return (this.transientValue); } public void setTransient(boolean transientValue) { this.transientValue = transientValue; }Saving and Restoring State (page 435) describes how a custom component must implement the
saveState(FacesContext)andrestoreState(FacesContext, Object)methods.Creating a Custom Tag
If you implemented a
Validatorinterface rather than implementing a backing bean method that performs the validation, you need to do one of the following:
- Allow the page author to specify the
Validatorimplementation to use with thevalidatortag. In this case, theValidatorimplementation must define its own properties. Using a Custom Validator (page 375) explains how to use thevalidatortag.- Create a custom tag that provides attributes for configuring the properties of the validator from the page. Because the
Validatorimplementation from the preceding section does not define its attributes, the application developer must create a custom tag so that the page author can define the format patterns in the tag.To create a custom tag, you need to do two things:
Using a Custom Validator (page 375) explains how to use the custom validator tag on the page.
Writing the Tag Handler
The tag handler associated with a custom validator tag must extend the
ValidatorELTagclass. This class is the base class for all custom tag handlers that createValidatorinstances and register them on UI components. TheFormatValidatorTagclass registers theFormatValidatorinstance onto the component.The
FormatValidatorTagtag handler class does the following:The
formatPatternsattribute of thefomatValidatortag supports literals and value expressions. Therefore, the accessor method for this attribute in theFormatValidatorTagclass must accept and return an instance ofValueExpression:protected ValueExpression formatPatterns = null;public void setFormatPatterns(ValueExpression fmtPatterns){ formatPatterns = fmtPatterns; }Finally, the
createValidatormethod creates an instance ofFormatValidator, extracts the value from theformatPatternsattribute's value expression and sets theformatPatternsproperty ofFormatValidatorto this value:the formatPatterns property of FormatValidator to this value:
protected Validator createValidator() throws JspException { FacesContext facesContext = FacesContext.getCurrentInstance(); FormatValidator result = null; if(validatorID != null){ result = (FormatValidator) facesContext.getApplication() .createValidator(validatorID); } String patterns = null; if (formatPatterns != null) { if (!formatPatterns.isLiteralText()) { patterns = (String) formatPatterns.getValue(facesContext.getELContext()); } else { patterns = formatPatterns.getExpressionString(); }Writing the Tag Library Descriptor
To define a tag, you declare it in a tag library descriptor (TLD), which is an XML document that describes a tag library. A TLD contains information about a library and each tag contained in it. See Tag Library Descriptors (page 220) for more information about TLDs.
The custom validator tag is defined in
bookstore.tld, located in<INSTALL>/javaeetutorial5/examples/web/bookstore6/web/directory. It contains a tag definition forformatValidator:<tag> <name>formatValidator</name> ... <tag-class> com.sun.bookstore6.taglib.FormatValidatorTag</tag-class> <attribute> <name>formatPatterns</name> <required>true</required> <deferred-value> <type>String</type> </deferred-value> </attribute> </tag>The
nameelement defines the name of the tag as it must be used in the page. Thetag-classelement defines the tag handler class. The attribute elements define each of the tag's attributes. The formatPatterns attribute is the only attribute that the tag supports. Thedeferred-valueelement indicates that theformatPatternsattribute accepts deferred value expressions. Thetypeelement says that the expression resolves to a property of typeString.