8. Possible Variations and Extensions

8.1. Object-level constraint validation

As an example of a constraint that is not bound to a specific property, but must be checked by inspecting several properties of an object, we consider the validation of the attribute Author::dateOfDeath. First, any value for this attribute must be in the past, which can be specified with the @Past Bean Validation annotation, and second, any value of dateOfDeath must be after the dateOfBirth value of the object concerned. This object-level constraint cannot be expressed with a predefined Bean Validation annotation. We can express it with the help of a custom class-level annotation, like the following AuthorValidator annotation interface:

@Target( ElementType.TYPE)
@Retention( RetentionPolicy.RUNTIME)
@Constraint( validatedBy=AuthorValidatorImpl.class)
public @interface AuthorValidator {
  String message() default "Author data is invalid!";
  Class<?>[] groups() default {};
  Class<? extends Payload>[] payload() default {};
}

Compared to a property constraint annotation definition, there is only one difference, the parameter of the @Target annotation. While in the case of a property and method level custom constraint annotation the values are ElementType.FIELD and ElementType.METHOD, for the case of a class it must be ElementType.TYPE.

The corresponding implementation class, i.e., AuthorValidatorImpl, has the same structure as in the case of a property constraint annotation , but now, we can access all properties of an entity bean, so we can compare two or more properties when required. In our case, we have to compare the values of dateOfBirth and dateOfDeath in the isValid method:

public class AuthorValidatorImpl implements 
    ConstraintValidator< AuthorValidator, Author> {
  @Override
  public void initialize( AuthorValidator arg0) {}  
  @Override
  public boolean isValid( Author author, 
   ConstraintValidatorContext context) {
    if (author.getDateOfDeath() != null && 
        author.getDateOfBirth().after( author.getDateOfDeath())) {
      return false;
    }
    return true;
  }
}

Using class-level JPA validators in facelets requires a bit of tweaking because they are not directly supported by JSF. For the specific form field to be validated, we have to specify a controller method in charge of the validation, as the value of the @validator attribute:

<ui:composition template="/WEB-INF/templates/page.xhtml">
 <ui:define name="main">
  <h:form id="createAuthorForm">
   <div>
    <h:outputLabel for="dateOfDeath" value="Date of death: ">
     <h:inputText id="dateOfDeath" p:type="date" 
        value="#{author.dateOfDeath}" 
        validator="#{authorCtrl.checkDateOfDeath}">
      <f:convertDateTime pattern="yyyy-MM-dd" />
     </h:inputText>
    </h:outputLabel> 
    <h:message for="dateOfDeath" errorClass="error" />
   </div>
   <div>
    <h:commandButton value="Create" 
       action="#{authorCtrl.create( author.personId, author.name, 
       author.dateOfBirth, author.dateOfDeath)}"/>  
   </div>
  </h:form>
 </ui:define>
</ui:composition>

The controller method checkDateOfDeath has to invoke the Bean Validation API validator, catch the validation exceptions and translate them to exceptions of type javax.faces.validator.ValidatorException, which are then managed by JSF and displayed in the view. Its code is as follows:

public void checkDateOfDeath( FacesContext context, 
    UIComponent component, Object value) {
  boolean isCreateForm = (UIForm) context.getViewRoot().
      findComponent( "createAuthorForm") != null;
  String formName = isCreateForm ? "createAuthorForm:" :
      "updateAuthorForm:";
  UIInput personIdInput = isCreateForm ? 
      (UIInput) context.getViewRoot().findComponent( 
          formName + "personId") : null;
  UIOutput personIdOutput = isCreateForm ? null : 
      (UIOutput) context.getViewRoot().findComponent( 
          formName + "personId");
  UIInput nameInput = (UIInput) context.getViewRoot().
      findComponent( formName + "name");
  UIInput dateOfBirthInput = (UIInput) context.getViewRoot().
      findComponent( formName + "dateOfBirth");
  ValidatorFactory factory = 
      Validation.buildDefaultValidatorFactory();
  Validator validator = factory.getValidator();
  Author author = new Author();
  if (isCreateForm) {
    author.setPersonId( (Integer) personIdInput.getValue());
  } else {
    author.setPersonId( (Integer) personIdOutput.getValue());
  }
  author.setName( (String) nameInput.getValue());
  author.setDateOfBirth( (Date) dateOfBirthInput.getValue());
  author.setDateOfDeath( (Date) value);
  Set<ConstraintViolation<Author>> constraintViolations = 
      validator.validate( author);
  for (ConstraintViolation<Author> cv : constraintViolations) {
    if (cv.getMessage().contains("date of death")) {
      throw new ValidatorException( new FacesMessage( 
        FacesMessage.SEVERITY_ERROR, cv.getMessage(), 
        cv.getMessage()));
    } 
  }
}

While the method looks complicated, it is responsible for the following simple tasks:

  • get access to form data and extract the user input values with the help of the context.getViewRoot().findComponent method. Notice that the component name has the pattern: formName:formElementName.

  • create the Author instance and set the corresponding data as extracted from the form, by using the FacesContext instance provided by the JSF specific validator method

  • manually invoke the Bean Validation API validator by using the javax.validation.Validator class.

  • loop trough the validator exception, select the ones which corresponds to the custom validated field and map them to javax.faces.validator.ValidatorException exceptions. The selection can be made by looking for specific data in the exception message.

As a result, the custom Bean Validation class validator is not used, and the facelet is able to render the corresponding error messages when the validation fails, in the same way as is possible for single property validation situations.

8.2. JSF custom validators

An alternative approach to object-level validation is using JSF custom validators. They have the advantage that they are directly supported in facelets, but the downside of this approach is that it violates the onion architecture principle by defining business rules in the UI instead of defining them in the model..

For our example, the validator for the Author class that is responsible for validating dateOfDeath by comparing it with dateOfBirth is shown below:

@FacesValidator( "AuthorValidator") 
public class AuthorValidator implements Validator {
  @Override
  public void validate( FacesContext context, UIComponent component, 
      Object value) throws ValidatorException  {
    Date dateOfDeath  = (Date)value;
    boolean isCreateForm = (UIForm) context.getViewRoot().
        findComponent( "createAuthorForm") != null;
    String formName = isCreateForm ? "createAuthorForm:" 
      : "updateAuthorForm:";
    UIInput dateOfBirthInput = (UIInput) context.getViewRoot().
        findComponent( formName + "dateOfBirth");
    Date dateOfBirth = (Date)dateOfBirth.getValue();  
    if (dateOfBirth.after( dateOfDeath)) {
      throw new ValidatorException ( new FacesMessage( 
          "The date of death should be after the date of birth!"));
    }
  }
}

Then, in the facelet, for the corresponding field, the validator has to be specified:

<h:outputLabel for="dateOfDeath" value="Date of death: " />
<h:inputText id="dateOfDeath" p:type="date" 
  value="#{author.dateOfDeath}">
  <f:validator validatorId = "AuthorValidator" />
  <f:convertDateTime pattern="yyyy-MM-dd" />
</h:inputText>
<h:message for="dateOfDeath" errorClass="error" />