Wednesday, February 24, 2010

Validar formularios JSF 2.0 con Bean Validation 1.0

En la entrada anterior se ilustro a través de un ejemplo las ventajas de implementar los backing beans JSF 2.0 como clases manejadas por CDI 1.0.

En esta entrada vamos a ver como integrar una tercera tecnología que forma parte de mi pila tecnológica ideal para desarrollo de aplicaciones web Java EE. Esta tecnología se define en el JSR 303 y se conoce como Bean Validation 1.0.

Bean Validation define un modelo de meta-datos basado en anotaciones y una API para realizar validaciones de JavaBeans.

Siguiendo la misma temática que en las entradas anteriores, voy a utilizar un pequeño ejemplo para demostrar las ventajas de Bean Validation.

El código fuente completo del ejemplo JSF-BeanValidation.zip, esta disponible en la plataforma kenai.com. Se creo un proyecto exclusivamente para albergar todos los ejemplos utilizados en este blog.

Aplicación de Ejemplo

La aplicación de ejemplo esta compuesta de tan solo una pantalla. Esta pantalla es un formulario que se utiliza para ingresar usuarios. El usuario solamente tiene dos campos, username y mail.

La aplicacion tiene tres componentes, el primero es una pagina Facelets que es la que presenta el formulario, los otros dos componentes son clases Java. Una representa al backing bean JSF y la otra es el modelo del usuario.

A continuación se ilustra la sección relevante del código fuente de la pagina


<h:form>
<h:panelGrid columns="3">
<h:outputLabel value="Username:" for="username"/>
<h:inputText id="username" value="#{userController.user.username}"/>
<h:message for="username" errorStyle="color: red" infoStyle="color: green"/>
<h:outputLabel value="E-mail:" for="email"/>
<h:inputText id="email" value="#{userController.user.email}"/>
<h:message for="email" errorStyle="color: red" infoStyle="color: green"/>
<h:commandLink action="#{userController.saveUser}" value="Save"/>
</h:panelGrid>
</h:form>
<h:panelGrid>
<h:messages globalOnly="true" errorStyle="color: red" infoStyle="color: green"/>
</h:panelGrid>


Como se puede ver, las cajas de texto están asociadas a campos de una instancia de la clase User.
Se accede a esta instancia a través de un campo del backing bean JSF userController.

El formulario cuenta con un botón que permite dar de alta al usuario. Para mantener a la aplicación lo mas simple posible, realmente no se van a almacenar los usuarios en ningún repositorio de datos, solamente se va a validar el modelo utilizando Bean Validation 1.0.

En una aplicación real, probablemente se utilizaría Java Persistence API 2.0 para realizar la persistencia, en entradas posteriores vamos a ver ejemplos de como integrar JPA con el resto de las tecnologías que vimos hasta ahora.

Validaciones con Bean Validation 1.0

La mayoría de las aplicaciones requieren validar la entrada de datos para permitir que el modelo de datos sea consistente.

Por ejemplo, si tenemos un formulario de alta de usuario en una aplicacion web, deberíamos validar que los datos ingresados sean los correctos. Además, tendríamos que notificar cuando un dato es incorrecto para que este pueda volver a ser ingresado correctamente.

En la practica, varios programadores forman parte del grupo encargado del desarrollo de una aplicación.

El desarrollador encargado de la lógica de negocios de un ingreso de usuario, no puede suponer que el desarrollador del formulario de alta de usuario realizó la validación de los campos en la capa de presentación.
Suponer esto puede hacer que la aplicación no sea robusta, ya que los datos ingresados podrían no ser correctos.
Por lo tanto el desarrollador de la lógica de negocios tiene que volver a validar que los campos sean correctos y este mismo escenario se repite para el desarrollador encargado de la capa de acceso a datos.

Es así como uno termina teniendo validaciones en cada capa de la aplicación como muestra la siguiente figura:



Este proceso no solo es propenso a errores, sino que duplica código y hace que la manutención del software sea mas difícil.

Por lo tanto, Bean Validation 1.0 es un esfuerzo por tener un único esquema de validación que se propague por todas las capas de la aplicacion.

La validación se realiza a nivel de modelo y se especifica de manera declarativa.

Bean Validation 1.0 tiene dos componentes:


  • Un modelo declarativo y extensible de validación basado en anotaciones

  • API estándar de consulta de meta-datos y validación en tiempo de ejecución



Como se comento anteriormente, para definir las restricciones de los campos se utilizan anotaciones, Bean Validation incluye varias anotaciones estándar que se encuentran en el paquete javax.validation.constraints. Las anotaciones estándar son:

  • @AssertFalse: El campo Boolean tiene que ser false.
  • @AssertTrue: El campo Boolean tiene que ser true.
  • @DecimalMax: El campo tiene que ser un numero cuyo valor sea menor o igual a un máximo especificado.
  • @DecimalMin: El campo tiene que ser un numero cuyo valor sea mayor o igual a un mínimo especificado.
  • @Digits: El campo tiene que ser un numero cuyo valor se encuentre en un rango definido.
  • @Future: El campo tiene que ser una fecha en un futuro.
  • @Max: El campo tiene que ser un numero cuyo valor sea menor o igual a un máximo especificado.
  • @Min: El campo tiene que ser un numero cuyo valor sea mayor o igual a un mínimo especificado.
  • @NotNull: El campo no puede tener valor null.
  • @Null: El campo tiene que tener valor null.
  • @Past: El campo tiene que ser una fecha en el pasado.
  • @Pattern: La cadena tiene que coincidir con el patrón de expresión regular especificado.
  • @Size: El tamaño del campo tiene que estar dentro de un limite especificado (incluyente).


En la aplicación de ejemplo, la clase User utiliza Bean Validation para validar los campos, en el siguiente cuadro se ilustra el código fuente de la clase User:


public class User {

@NotNull
@Size(min=1,max=30)
private String username;

@Size(max=100)
@Pattern(regexp=".+@.+\\.[a-z]+")
private String email;

/*
getters y setters
omitidos
*/
}


La clase tiene dos campos username y email. Las validaciones especifican las siguientes restricciones para cada campo:

  • username: El campo no puede ser nulo (@NotNull) y su tamaño tiene que estar dentro del limite 1 a 30 (@Size(min=1,max=30).
    En el caso de una cadena, el tamaño se evalúa como longitud de la cadena, aunque tiene semántica distinta para otros tipos de datos (Collection, Array, Map, etc).
  • email: El tamaño del campo no puede ser mayor a 100 (@Size(max=100). Además, la cadena tiene que coincidir con el patrón definido por una expresión regular (@Pattern(regexp=".+@.+\\.[a-z]+")). Esta expresión regular especifica el formato válido para las direcciones de correo electrónico.


Como se comento anteriormente, Bean Validation esta compuesto de dos elementos. El primero es un modelo de meta-datos basado en anotaciones que se utiliza para decorar las clases y definir restricciones en los campos. Esto es lo que acabamos de ver.

El segundo componente es la API de consulta de meta-datos y validación. Esta API es la que se utiliza para verificar si una instancia de una clase cumple con todos los requisitos definidos con las anotaciones de Bean Validation.

Para validar las instancias de los JavaBeans se utiliza una implementación que implemente la interfaz:


javax.validation.Validator


La interfaz tiene un método validate() que es el que se utiliza para validar las instancias. Se obtiene una instancia de la implementación a través de un método factory:


Validator validator = Validation.buildDefaultValidatorFactory().getValidator();


A continuación, mostramos un ejemplo de como verificar si un usuario cumple con todas las restricciones. De no cumplir con alguna restricción, imprime en la salida estándar el nombre del campo que no cumple con la restricción y su respectivo mensaje de error.


Set<constraintviolation><user>> constraintViolations = Validation.buildDefaultValidatorFactory().getValidator().validate(this.user);

for(ConstraintViolation<user> constraintViolation : constraintViolations){
System.out.println(constraintViolation.getPropertyPath());
System.out.println(constraintViolation.getMessage());
}


Invocar explícitamente el método validate() debería ser la excepción y no la regla, ya que la idea es definir la validación en el modelo y utilizarla en todas las capas. Por lo tanto la mayoría de los frameworks de capa de presentación, tendrían que integrar automáticamente el mecanismo de validación de Bean Validation.

JavaServer Faces 2.0 lo integra automáticamente. Lo único que uno tiene que hacer es definir la validación en el JavaBean que contiene los campos asociados a los componentes GUI JSF y definir tags <message> o <messages> que procesen los mensajes de error.

Por ejemplo, en el formulario de alta de usuarios, hay una caja de texto asociada al campo username de la clase User:


<h:inputText id="username" value="#{userController.user.username}"/>
<h:message for="username" errorStyle="color: red" infoStyle="color: green"/>


Además, hay un tag <message> que esta asociado al <inputText> asociado al campo username.

Por lo tanto, si ocurre un error de validación en ese campo, JSF actualiza automáticamente el componente UI JSF correcto con el mensaje de error correspondiente.

Si queremos que los mensajes de error sean globales, entonces tenemos que definir un tag <messages> que no este asociado a ningún componente GUI.


<h:messages globalOnly="true" errorStyle="color: red" infoStyle="color: green"/>


Entonces, todos los mensajes de error de validación van a ser agregados a este componente UI, sin importar cual campo es el que no cumple con sus restricciones de validación.

Eso es todo lo que necesitamos para implementar validaciones de modelo en nuestras paginas JavaServer Faces 2.0.

Al hacer clic en el botón:


<h:commandLink action="#{userController.saveUser}" value="Save"/>


Primero se va a realizar la validación del modelo y si algún campo no cumple con sus restricciones de validación, se va a notificar mostrando los mensajes de error en los tags de mensajes.
Solamente si se cumplen todos los requisitos de validación, se va a ejecutar el método saveUser().

En esta entrada vimos un uso básico de Bean Validation, en próximas entradas vamos a ver conceptos mas avanzados como tener varios grupos de validación, crear restricciones de validación propias y el uso de Bean Validation para validar clases persistibles a través de JPA 2.0.