openxava / documentación / Lección 19: Alternativas de validación

Curso: 1. Primeros pasos | 2. Modelo básico del dominio (1) | 3. Modelo básico del dominio (2) | 4. Refinar la interfaz de usuario | 5. Desarrollo ágil | 6. Herencia de superclases mapedas | 7. Herencia de entidades | 8. Herencia de vistas | 9. Propiedades Java | 10. Propiedades calculadas | 11. @DefaultValueCalculator en colecciones | 12. @Calculation y totales de colección | 13. @DefaultValueCalculator desde archivo | 14. Evolución del esquema manual | 15. Cálculo de valor por defecto multiusuario | 16. Sincronizar propiedades persistentes y calculadas | 17. Lógica desde la base de datos | 18. Validando con @EntityValidator | 19. Alternativas de validación | 20. Validación al borrar  21. Anotación Bean Validation propia | 22. Llamada REST desde una validación  | 23. Atributos en anotaciones  | 24. Refinar el comportamiento predefinido | 25. Comportamiento y lógica de negocio | 26. Referencias y colecciones | A. Arquitectura y filosofía | B. Java Persistence API | C. Anotaciones | D. Pruebas automáticas

Tabla de contenidos

Lección 19: Alternativas de validación
Validar con métodos de retrollamada JPA
Validar en el setter
Validar con Bean Validation
Resumen
En la lección anterior vistes como validar usando @EntityValidator. En esta lección vas a aprender más metodos para ejecutar esta misma validación con mucho menos esfuerzo.

Si no te gustan los videos sigue las instrucciones a continuación.

Validar con métodos de retrollamada JPA

Vamos a probar otra forma más sencilla de hacer esta validación, simplemente moviendo la lógica de validación desde la clase validador a la misma entidad Pedido, en este caso a un método @PrePersist y @PreUpdate.
Lo primero es eliminar la clase ValidadorEntregadoParaEstarEnFactura de tu proyecto. También quita la anotación @EntityValidator de tu entidad Pedido:
// @EntityValidator( // Eliminar '@EntityValidator'
//    value=com.tuempresa.facturacion.validadores.ValidadorEntregadoParaEstarEnFactura.class,
//    properties= {
//        @PropertyValue(name="anyo"),
//        @PropertyValue(name="numero"),
//        @PropertyValue(name="factura"),
//        @PropertyValue(name="entregado")
// })
public class Pedido extends DocumentoComercial {
Acabamos de eliminar la validación. Ahora, vamos a añadirla de nuevo, pero ahora dentro de la misma clase Pedido. Escribe el método validar() que se muestra a continuación dentro de tu clase Pedido:
@PrePersist @PreUpdate // Antes de crear o modificar
private void validar() throws Exception {
    if (factura != null && !isEntregado()) { // La lógica de validación
        // La excepción de validación del entorno Bean Validation
        throw new javax.validation.ValidationException(
            XavaResources.getString( // Para leer un mensaje i18n
                "pedido_debe_estar_entregado",
                getAnyo(),
                getNumero())
        );
    }
}
Antes de grabar un pedido esta validación se ejecutará, si falla una ValidationException será lanzada. Esta excepción es del marco de validación Bean Validation, de esta forma OpenXava sabe que es una excepción de validación. Así con solo un método dentro de tu entidad tienes la validación hecha.
Sólo está permitido un método @PrePersist y un método @PreUpdate por entidad, por eso antes de ejecutar el código de arriba has de comentar las anotaciones @PrePersist y @PreUpdate que tenías en recalcularDiasEntrega(), de esta manera:
// @PrePersist @PreUpdate // Comenta estas anotaciones
private void recalcularDiasEntrega()() {
    setDiasEntrega(getDiasEntregaEstimados());
}
No te preocupes, descomentaremos estas anotaciones más adelante. Aunque JPA sólo permita un método @PrePersist/@PreUpdate siempre tenemos la opción de crear un único método de retrollamada desde el cual llamar a todos los demás métodos que necesitemos, pero esto no hace falta en nuestro caso, porque no vamos a quedarnos con este estilo de validación como definitivo.
Ahora, intentar añadir pedidos no entregados a una factura y verás los errores de validación, como en nuestro primer ejemplo.

Validar en el setter

Otra alternativa para hacer tu validación es poner tu lógica de validación dentro del método setter. Es un enfoque simple y llano.
Para probarlo, primero vuelve a poner las anotaciones @PrePersist y @PreUpdate en el método recalcularDiasEntrega(), también quita el método validar() de tu entidad Pedido:
@PrePersist @PreUpdate // Añádelas de nuevo
private void recalcularDiasEntrega() {
    setDiasEntrega(getDiasEntregaEstimados());
}	
	
// Quita el método validar()
	
// @PrePersist @PreUpdate // Antes de crear o modificar
// private void validar() throws Exception {
//     if (factura != null && !isEntregado()) { // La lógica de validación
//         // La excepción de validación del entorno Bean Validation
//         throw new javax.validation.ValidationException(
//             XavaResources.getString( // Para leer un mensaje i18n
//                 "pedido_debe_estar_entregado",
//                 getAnyo(),
//                 getNumero())
//         );
//     }
// }    
Después añade el método setter setFactura() a Pedido:
public void setFactura(Factura factura) {
    if (factura != null && !isEntregado()) { // La lógica de validación
        // La excepción de validación del entorno Bean Validation
        throw new javax.validation.ValidationException(
            XavaResources.getString( // Para leer un mensaje i18n
                "pedido_debe_estar_entregado",
                getAnyo(),
                getNumero())
        );
    }
    this.factura = factura; // La asignación típica del setter
}
Esto funciona exactamente como las dos opciones anteriores. Es parecida a la alternativa del @PrePersist/@PreUpdate, solo que no depende de JPA, es una implementación básica de Java.

Validar con Bean Validation

Como opción final vamos a hacer la más breve. Consiste en poner tu lógica de validación dentro de un método booleano anotado con la anotación de Bean Validation @AssertTrue.
Para implementar esta alternativa primero quita el método setFactura():
// Quita el método setter

// public void setFactura(Factura factura) {
//    if (factura != null && !isEntregado()) { // La lógica de validación
//        // La excepción de validación del entorno Bean Validation
//        throw new javax.validation.ValidationException(
//            XavaResources.getString( // Para leer un mensaje i18n
//                "pedido_debe_estar_entregado",
//                getAnyo(),
//                getNumero())
//        );
//    }
//    this.factura = factura; // La asignación típica del setter
// }
Después, añade isEntregadoParaEstarEnFactura() a tu entidad Pedido, como se muestra a continuación:
@AssertTrue(  // Antes de grabar confirma que el método devuelve true, si no lanza una excepción
    message="pedido_debe_estar_entregado" // Mensaje de error en caso retorne false
)
private boolean isEntregadoParaEstarEnFactura() { // ...
    return factura == null || isEntregado(); // La lógica de validación
}
En las formas anteriores de validación nuestro mensaje de error era construído mediante dos argumentos, anyo y numero, que en nuestro archivo i18n son representados por {0}/{1} respectivamente. Para el caso de validación con @AssertTrue no podemos pasar estos dos argumentos para construir nuestro mensaje de error, sino que podemos declarar propiedades y propiedades calificadas del bean validado en la definición del mensaje, para eso cambia en facturacion-messages_es.properties la entrada:
pedido_debe_estar_entregado=Pedido {0}/{1} debe estar entregado para ser añadido a una Factura
Por:
pedido_debe_estar_entregado=Pedido {anyo}/{numero} debe estar entregado para ser añadido a una Factura
Fíjate que hemos cambiado {0}/{1} por {anyo}/{numero}. OpenXava llenará {anyo}/{numero} con los valores de anyo y numero que tenga el Pedido que está siendo actualizado y no cumple la condición de validación.
Esta es la forma más simple de validar, porque solo anotamos el método con la validación, y es el entorno Bean Validation el responsable de llamar este método al grabar y lanzar la excepción correspondiente si la validación no pasa.

Resumen

En esta lección has aprendido varias formas de hacer validación en una aplicación OpenXava. En la siguiente lección vas a ver como validar al borrar y con eso habrás explorado todos los tipos de validaciones.

Descargar código fuente de esta lección

¿Problemas con la lección? Pregunta en el foro ¿Ha ido bien? Ve a la leccion 20