En la parte I de este artículo vimos cómo usar las anotaciones de Bean Validation para validar atributos y parámetros. ¿Qué pasa si queremos hacer validaciones que no están implementadas por el estándar?

Supongamos que tenemos la clase Empleado:

public class Empleado {
   private LocalDate fechaIngreso; 
   private LocalDate fechaEgreso;
}

¿Cómo podríamos validar con una anotación que fechaEgreso sea mayor que fechaIngreso?
Para esto deberíamos crear una anotación propia.

Creando la anotación

Nuestra anotación se vería así:

@Target(ElementType.TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = FechaIngresoMenorQueFechaEgresoValidator.class)
public @interface FechaIngresoMenorQueFechaEgreso {
 String message() default "la fecha ingreso debe ser menor a la fecha de egreso";
 Class<?>[] groups() default {};
 Class<? extends Payload>[] payload() default {};
}


- @Target le indica al compilador dónde se puede utilizar la anotación. Usamos ElementType.TYPE para indicar que se usa a nivel de clase.
- @Retention le indica al compilador que esta anotación debe estar disponible en tiempo de ejecución, para que sea leída por Bean Validation.
- @Constraint le indica a Bean Validation que al encontrar esta anotación debe validar utilizando la clase configurada en validatedBy

Validando la anotación

Para validar nuestra anotación debemos crear una clase que extienda de ConstraintValidator e implementar el método isValid:

public class FechaIngresoMenorQueFechaEgresoValidator implements ConstraintValidator<FechaEgresoMenorFechaIngreso, empleado> {
    @Override
    public boolean isValid(empleado empleado, ConstraintValidatorContext context) {
        return empleado.getFechaEgreso().isAfter(empleado.getFechaIngreso());
    }
}


Usando la anotación

Para validar nuestro Empleado le agregamos la anotación a nivel de clase:

@FechaIngresoMenorQueFechaEgreso
public class Empleado {
    private LocalDate fechaIngreso;
    private LocalDate fechaEgreso;
}

Creando una validación más compleja

Supongamos que ahora nuestro Empleado tiene dos nuevos atributos y debemos hacer una validación similar:

@FechaIngresoMenorQueFechaEgreso
public class Empleado {
    private LocalDate fechaIngreso;
    private LocalDate fechaEgreso;
    private BigDecimal sueldoNeto;
    private BigDecimal sueldoBruto;
}

Podríamos hacer otra anotación llamada SueldoNetoMenorQueSueldoBruto, pero eso sería muy aburrido...
Un desafío entretenido sería hacer una validación genérica que nos sirva para comparar pares de atributos de distintos tipo de datos:

@Target(TYPE)
@Retention(RUNTIME)
@Constraint(validatedBy = MenorQueValidator.class)
@Documented
public @interface MenorQue {
   String message() default "{atributoMenor} debe ser menor que {atributoMayor}";
   Class<?>[] groups() default {};
   Class<? extends Payload>[] payload() default {};
   String atributoMenor(); 
   String atributoMayor();
}


Y nuestro validador sería así:

public class MenorQueValidator implements ConstraintValidator<MenorQue, Object> {
    private String atributoMenor;
    private String atributoMayor;
    @Override
    public void initialize(final MenorQue annotation) {
        atributoMenor = annotation.atributoMenor();
        atributoMayor = annotation.atributoMayor();
    }
    @Override
    public boolean isValid(final Object objeto, final ConstraintValidatorContext context) {
        BeanWrapper beanWrapper = new BeanWrapperImpl(objeto);
        Comparable valorMenor = (Comparable) beanWrapper.getPropertyValue(atributoMenor);
        Comparable valorMayor = (Comparable) beanWrapper.getPropertyValue(atributoMayor);
        return valorMenor != null && valorMenor.compareTo(valorMayor) < 0; 
    }
}

Conclusión

La creación de nuestras propias anotaciones nos permite extender el comportamiento de Bean Validation y lograr validar completamente nuestras entidades de forma declarativa. De esta manera las entidades no ingresan a la capa de negocio en un estado inválido.

Mandanos tus sugerencias

Ayudanos con ideas para los artículos de este blog a contacto@somospnt.com

¡Seguínos en nuestras redes sociales para enterarte de los últimos posts!