Estou criando uma anotação para validar as datas de início/término, quero garantir que elas não se sobreponham.
Anotação:
@Documented
@Target({ ElementType.TYPE, ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = { NonOverlappingDatesValidator.class })
public @interface NonOverlappingDates {
String start();
String end();
String message() default "OVERLAPPING_OF_DATES";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
Validador:
public class NonOverlappingDatesValidator implements ConstraintValidator<NonOverlappingDates, Object> {
private String start;
private String end;
/**
* Initialize.
*/
@Override
public void initialize(NonOverlappingDates nonOverlappingDates) {
start = nonOverlappingDates.start();
end = nonOverlappingDates.end();
}
/**
* Checks if is valid.
*/
@Override
public boolean isValid(Object instanceToValidate, ConstraintValidatorContext constraintValidatorContext) {
Class<?> classToValidate = instanceToValidate.getClass();
LocalDateTime startDate;
LocalDateTime endDate;
try {
startDate = getLocalDateTimeFromFieldName(classToValidate, instanceToValidate, start);
endDate = getLocalDateTimeFromFieldName(classToValidate, instanceToValidate, end);
} catch (ReflectionException e) {
throw new RuntimeException(e);
}
return DateTimeUtils.isBefore(startDate, endDate);
}
private LocalDateTime getLocalDateTimeFromFieldName(Class<?> classToValidate, Object instanceToValidate, String fieldName) throws ReflectionException {
Method getter = ReflectionUtils.getPublicGetterOfFieldName(classToValidate, fieldName);
Object value = ReflectionUtils.invokeMethod(instanceToValidate, getter);
if (value instanceof LocalDateTime localDateTime) {
return localDateTime;
} else {
throw new RuntimeException(fieldName+" is not a LocalDateTime");
}
}
}
DTO a ser validado:
@Data
@NonOverlappingDates(start = "startDate", end = "endDate")
public class IdentityCardDTO {
@JsonProperty
private Long id;
@JsonProperty
private String name;
@JsonProperty
private String surname;
@JsonProperty
@JsonSerialize(using = LocalDateSerializer.class)
@JsonDeserialize(using = LocalDateDeserializer.class)
private LocalDate dateOfBirth;
@JsonProperty
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime startDate;
@JsonProperty
@JsonSerialize(using = LocalDateTimeSerializer.class)
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
private LocalDateTime endDate;
}
Gostaria de saber se ao invés de passar o nome dos campos e depois usar o Reflection para recuperar o valor, é possível passar diretamente o valor dos campos que quero validar.
Você pode obter esse efeito com a abstração dos dados que precisa validar. Você precisa de uma interface com métodos para obter os dados de que precisa. Digamos:
Não é um nome muito bom, mas ok para fins de exemplo. Em seguida, habilite seu validador real para validar instâncias dessa interface, não
Object
como é agora. Assim você também pode simplificar bastante:NonOverlappingDatesValidator
agora pode validar qualquer implementação de classeDatesData
.IdentityCardDTO
torna-se:Isso simplifica
NonOverlappingDates
também, já que você não precisa mais de métodosstart()
eend()
.