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.