viernes, 6 de julio de 2012

Validaciones de datos de formularios con Apache Tapestry

Apache Tapestry
En una aplicación web todo dato procedente del usuario a través de un formulario debe ser validado para impedir introducir datos erroneos en la base de datos, posiblemente creando inconsistencias, fallos en la aplicación o peor aún creando problemas de seguridad. En una aplicación web las validaciones pueden ser realizadas en el navegador del cliente mendiante javascript y en el el lado del servidor. Las validaciones deberían hacerse siempre en el lado del servidor ya que las validaciones que pongamos en el lado del cliente podrían no realizarse, un ejemplo de esto es si el cliente tiene el javascript desactivado pero no es la única forma de que nos puedan llegar datos erroneos al servidor. Por tanto, las validaciones del cliente son opcionales y pueden ser útiles para no enviar datos al servidor que podemos detectar que son inválidos en el cliente con lo que evitamos tráfico pero nuestra aplicación no debería confiar únicamente en ellas.

Las validaciones en Tapestry en el servidor pueden ser realizadas de varias formas:
  • De forma declarativa
  • Con anotaciones
  • Mediante eventos

Validaciones de forma declarativa

Quizá esta es la forma más sencilla y se aplica en el momento de usar los componentes de formulario en las plantillas de los componentes.

//...(parte de un archivo .tml)

<t:label for="telefono">: <t:textfield t:id="telefonoField" value="producto" validate="regexp=^[a-zA-Z]{3,9}$" label="Producto"/>
<t:label for="cantidad">: <t:textfield t:id="cantidadField" value="cantidad" validate="required,min=0,max=120" label="Cantidad"/>
...
En el campo cantidad se están aplicando varias validaciones: el campo es requerido (required), el valor mínimo aceptado es 0 (min), el valor máximo aceptado es 120 (max). En el campo producto se usa como validación una expresión regular en la que el valor debe tener entre 3 y 9 caracteres. Otros validadores que incorpora Tapestry por defecto y que podemos usar son maxLength, minLength o email. Si estos validadores con los que viene Tapestry no se ajustan a lo que necesitamos podemos definir nuevos y usarlos de la misma manera.

Validaciones con anotaciones

Las validaciones con anotaciones se aplican en las propiedades que los componentes del formulario usan como valor a mostrar y a cambiar cuando se envía el formulario. Siguiendo el ejemplo anterior con anotaciones sería:

//...(parte de un archivo .java)

@Validate("^[a-zA-Z]{3,9}$")
@Property
private String producto;

@Validate("required,min=0,max=120")
@Property
private Integer cantidad;

...

Validaciones con eventos

Los componentes de Tapestry pueden lanzar eventos (cuyos manejadores siguen la forma on[Evento]From[Componente]). En el caso de los componentes de formulario estos lanzan (entre otros) un evento (validate) para que podamos hacer alguna validación personalizada que no esté entre los validadores por defecto. Nuevamente siguiendo el ejemplo anterior si no dispusiesemos los validadores de Tapestry con anotaciones sería:

//...(parte de un archivo .java de un componente)

void onValidateFromProductoField() throws ValidationException {
 if (!Pattern.matches("^[a-zA-Z]{3,9}$", producto)) {
  throw new ValidationException("El producto no es válido.");
 }
}

void onValidateFromCantidadField() throws ValidationException {
 if (cantidad == null || cantidad < 0 || cantidad > 120) {
  throw new ValidationException("La cantidad no es válida.");
 }
}

...

Validaciones dependientes

En las aplicaciones los datos suelen estar relacionados y algunas combinaciones de datos no son posibles. Los validadores nos permiten comprobar los datos individualmente pero si los datos están relacionados pueden no ser suficientes. Los componentes Form lanzan un evento (validate) para que hagamos este tipo de comprobaciones. Suponiendo que un determinado producto tiene una cantidad mínima a solicitar podríamos validar esta dependencia entre estas propiedades con:

//...(parte de un archivo .java de un componente)

@Component
private TextField productoField, cantidadField;

@Component
private Form datosForm;

@Property
private String producto;

@Property
private Integer cantidad;

void onValidateFromDatosForm() throws ValidationException {
 ValidationTracker tracker = datosForm.getDefaultTracker();

 // Comprobar si los datos recogidos de producto y cantidad son correctos
 if (tracker.inError(productoField) || tracker.inError(cantidadField)) {
  return;
 }

 // Realizar las validaciones dependiente entre los campos
 if (producto.equals("pegatina") && cantidad < 10) {
  tracker.recordError("La cantidad mínima de pegatinas es de 10.");
 }
}

...

Tapestry realiza validaciones en el lado del cliente por defecto en cada unos de los campos cuando se lanza el evento blur de los mismos, si no quisieramos realizar las validaciones en el cliente al componente form debemos pasarle al parametro clientValidation el valor «none». También podriamos hacer que las validaciones se realizasen en el lado del cliente en el momento de enviar el formulario y no en cada blur del componente con el valor «submit». El enumerado ClientValidation define los posibles valores que disponemos.

...
<t:form t:id="form" clientValidation="none">
...
</t:form>
...

Como se puede ver las validaciones son sencillas (basta con añadir un parámetro a un componente), flexibles (hay varias formas de hacerlas según prefiramos) y extensibles (se puede personalizar las validaciones y crear nuevas usándolas como las propias de Tapestry). En otra entrada explicaré como definir nuevas validaciones que podemos usar como las propias de Tapestry, como personalizar la decoración de los campos cuando hay errores en ellos y como crear macros de validaciones si hay una combinación de ellas que usamos de forma repetida.

Referencia:
http://tapestry.apache.org/forms-and-validation.html
http://tapestry.apache.org/bean-validation.html
Extender las validaciones de Apache Tapestry
Documentación sobre Apache Tapestry