Validation how-to

Follow this quick tutorial for a simple example on how to use VRaptor's programmatic validation.

It finishes explaining why we have chosen not to implement an annotation based validation procedure (yet).

Please also check the hibernate validator plugin which offers even more powerful.

Person - The model

We will play around with a model class called Person. Create the following class:

package org.vraptor.examples.validation;

public class Person {

        private String name;

        private String address;

        private Long preferredNumber;
        
        // getters and setters

        @Override
        public String toString() {
                return String.format("[Person %s,%s,%d]", name, address,
                                preferredNumber);
        }

}

The URL: person.add.logic

Now let's go a step further and configure the add person business logic url.

Let's name this component person and the business logic represented by the method add by add.

It means that when the url person.add.logic is called, our component will be instantiated, the person field filled with the request's parameters and after that the method add is called.

package org.vraptor.examples.validation;

import org.vraptor.annotations.Component;
import org.vraptor.annotations.Logic;
import org.vraptor.annotations.Parameter;

@Component
public class PersonLogic {

        public void add(Person p) {
                System.out.printf("Adding %s to the database!%n", p);
        }

}

But that's VRaptor simple example!

Yes, it is. Now let's validate our person:

package org.vraptor.examples.validation;

import org.vraptor.annotations.Component;
import org.vraptor.validator.*;
import org.vraptor.i18n.*;

@Component
public class PersonLogic {

        public void add(Person p) {
                System.out.printf("Adding %s to the database!%n", p);
        }
        
        /**
          * Validates the add method
          */
        public void validateAdd(ValidationErrors errors, Person p) {
        
                // checks if null, empty or trimmed empty
                if(StringValidation.isBlank(p.getName())) {
                        errors.add(new Message("name","empty_name"));
                }
                
                // checks if null or empty
                if(StringValidation.isEmpty(p.getAddress())) {
                        errors.add(new Message("address","empty_address"));
                }
                
                // checks if null (empty or invalid number) when using java.lang.Long
                if(p.getPreferredNumber()==null) {
                        errors.add(new Message("number","empty_number"));
                }
                
        }

}

person/add.ok.jsp means everything went ok

Your logic method is called only if it's validation method is called and doesn't register any error!

This means: if no error happens, errors.size()==0, therefore vraptor's goes to person/add.ok.jsp:

<html>
You have created a new person named ${person.name},
        who lives at ${person.address} and his/her
        preferred number is ${person.preferredNumber}.
</html>

person/add.invalid.jsp means there was some validation errors

If errors.size()!=0 this means that some error has occurred... vraptor's method return is invalid which redirects to component/logic.invalid.jsp, in our example: person/add.invalid.jsp:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>

<html>
There was some validation problems:
<div id="errors">
<ul>
        <c:forEach var="error" items="${errors.iterator}">
                <li>${error.key}</li>
        </c:forEach>
</ul>
</div>
</html>

Index page: /index.jsp

Let's create a simple index.jsp page that contains the desired form:

<html>
<form action="person.add.logic" method="get">
        Name: <input name="person.name"/><br/>
        Address: <input name="person.address"/><br/>
        Preferred number: <input name="person.preferredNumber"/><br/>
        <input type="submit"/>
</form>
</html>

Error path

You can use the path property from an error to discover which was the path generated the validation error.

Ready

We are ready. just test it!

So how did we get that set up?

  1. create a method called validateLogicName which receives ValidationErrors as an argument.
  2. implement the programmatic validation
  3. create the result invalidation page (or go back to the form, as the next section shows).

Optional: goes back to form

Let's configure vraptor to redirect back to the form if some validation problem occurrs.

First create the views.properties file in your classpath (java source or resources directory).

We shall map componentName.logic.invalid to any file we desire... so let's redirect it to our form:

person.add.invalid = /index.jsp

And we change the index.jsp file to display the default values:

<html>
<form action="person.add.logic" method="get">
        Name: <input name="person.name" value="${person.name}"/><br/>
        Address: <input name="person.address" value="${person.address}"/><br/>
        Preferred number: <input name="person.preferredNumber" value="${person.preferredNumber}"/><br/>
        <input type="submit"/>
</form>
</html>

Optional: i18n

Error messages should use internationalization, so use the fmt tag to get something like:

<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>

<div id="errors">
<ul>
        <c:forEach var="error" items="${errors.iterator}">
                <li><fmt:message key="${error.key}"/></li>
        </c:forEach>
</ul>
</div>

Don't forget to setup the resource bundle! (read the fmt jstl documentation or download vraptor's blank application).

Default validation messages

VRaptor already includes some default validation messages. You should add these messages to your properties file.

no_converter_found = No converter was found.
invalid_converter = Unable to instantiate converter.

invalid_date = Invalid date.
invalid_time = Invalid time.
invalid_value = Invalid value.
invalid_number = Invalid number.
invalid_character = Invalid character.

Fixed messages

The base message interface is called ValidationMessage while the i18n class is Message (due to compatibility issues).

If you want to mix i18n messages with fixed messages you can instantiate the FixedMessage class.

Both classes implement the method isAlreadyLocalized: Message returns false, while FixedMessage returns true, therefore you can easily mix them in your view:

<c:if test="${error.alreadyLocalized}">
        ${error.key}
</c:if>
<c:if test="${not error.alreadyLocalized}">
        <fmt:message key="${error.key}"/>
</c:if>

Annotations

Hibernate Validator is already supported by VRaptor.