Validator for multiple fieldsin JSF

Introduction

Validators in JSF are nice. They, however, have its shortcomings. They will by default validate only one field at once. There is no standard way to attach one validator to multiple fields. Although there are some situations where you want this kind of functionality. For example validating the password confirmation field, validating the range of two numeric values (e.g. the one have to be lesser than the other), validating correctness of day, month and year fields, etcetera.

The cleanest solution would be to create a custom component which renders two or more components and use a specific validator for that, but that would involve more work. The easiest solution is to attach the validator to the last component of the group (components are rendered, validated, converted and updated in the same order as you define them in the JSF view) and pass the client ID of the other component(s) as unique f:attribute facet(s) along the last component. Then in the validator you can get the desired component(s) by the client ID using UIViewRoot#findComponent().

Basic example

This example demonstrates a basic registration form with one username field and two password fields. The value of the second password field should equal to the value of the first password field before the action method may be invoked. The stuff is tested in a Java EE 5.0 environment with Tomcat 6.0 with Servlet 2.5, JSP 2.1 and JSF 1.2_07 (currently called Mojarra by the way!).

Here is the relevant JSF code. Note the f:attribute of the last password field, its value should contain the client ID of the first password field. Also note that the last password field doesn't have any valuebinding to the backing bean as this is unnecessary in this specific case.

<h:form id="register">
<h:panelGrid columns="3">
<h:outputLabel for="username" value="Username" />
<h:inputText id="username" value="#{myBean.username}" required="true" />
<h:message for="username" style="color: red;" />

<h:outputLabel for="password" value="Password" />
<h:inputSecret id="password" value="#{myBean.password}" required="true" />
<h:message for="password" style="color: red;" />

<h:outputLabel for="confirm" value="Confirm password" />
<h:inputSecret id="confirm" required="true">
<f:validator validatorId="passwordValidator" />
<f:attribute name="passwordId" value="register:password" />
</h:inputSecret>
<h:message for="confirm" style="color: red;" />

<h:panelGroup />
<h:commandButton value="Register" action="#{myBean.register}" />
<h:message for="register" style="color: green;" />
</h:panelGrid>
</h:form>

And now the validator code:

package mypackage;

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

public class PasswordValidator implements Validator {

// Actions -


public void validate(FacesContext context, UIComponent component, Object value)
throws ValidatorException
{
// Obtain the client ID of the first password field from f:attribute.
String passwordId = (String) component.getAttributes().get("passwordId");

// Find the actual JSF component for the client ID.
UIInput passwordInput = (UIInput) context.getViewRoot().findComponent(passwordId);

// Get its value, the entered password of the first field.
String password = (String) passwordInput.getValue();

// Cast the value of the entered password of the second field back to String.
String confirm = (String) value;

// Check if the first password is actually entered and compare it with second password.
if (password != null && password.length() != 0 && !password.equals(confirm)) {
throw new ValidatorException(new FacesMessage("Passwords are not equal."));
}

// You can even validate the minimum password length here and throw accordingly.
// Or, if you're smart, calculate the password strength and throw accordingly ;)
}

}

The appropriate test backing bean look like:

package mypackage;

import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;

public class MyBean {

// Init -

private String username;
private String password;

// Actions -

public void register() {

// Just for debug. Don't do this in real! Hash the password, save to DB and forget it ;)
System.out.println("Username: " + username);
System.out.println("Password: " + password);

// Show succes message.
FacesContext.getCurrentInstance().addMessage("register", new FacesMessage("Succes!"));
}

// Getters -----------

public String getUsername() {
return username;
}

public String getPassword() {
return password;
}

// Setters

public void setUsername(String username) {
this.username = username;
}

public void setPassword(String password) {
this.password = password;
}

}

Finally the relevant part of the faces-config.xml:

<validator>
<validator-id>passwordValidator</validator-id>
<validator-class>mypackage.PasswordValidator</validator-class>
</validator>

<managed-bean>
<managed-bean-name>myBean</managed-bean-name>
<managed-bean-class>mypackage.MyBean</managed-bean-class>
<managed-bean-scope>request</managed-bean-scope>
</managed-bean>

0 comments: