I would suggest a model type holding the parameters since
and until
with a custom bean validation (using Lombok but you can also write getters and setters). The defaults are now field initializers:
@Ordered({"since", "until"})
@Data
public class DateRange {
@NotNull
@PastOrPresent
private LocalDate since = DEFAULT_SINCE_DATE;
@NotNull
private LocalDate until = LocalDate.now().plusDays(1);
}
@GetMapping(value="/api/test")
@ResponseBody
public Result test(@Valid DateRange dateFilter) {
// Do stuff
}
To make the validation work, you need a custom bean validation constraint:
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Constraint(validatedBy = OrderedValidator.class)
public @interface Ordered {
/** The property names with comparable values in the expected order. **/
String[] value();
String message() default "{com.stackoverflow.validation.Ordered.message}";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
And the validator to check the constraint (sorry, litte generics hell to make it work for any kind of Comparable
values instead of LocaleDate
only):
public class OrderedValidator implements ConstraintValidator<Ordered, Object>
{
private String[] properties;
@Override
public void initialize(Ordered constraintAnnotation) {
if (constraintAnnotation.value().length < 2) {
throw new IllegalArgumentException("at least two properties needed to define an order");
}
properties = constraintAnnotation.value();
}
@Override
public boolean isValid(Object value, ConstraintValidatorContext context) {
return isValid(value));
}
private <T extends Comparable<? super T>> boolean isValid(Object value)
{
List<T> values = getProperties(value);
return isSorted(values);
}
private <T extends Comparable<? super T>> List<T> getProperties(Object value)
{
BeanWrapperImpl bean = new BeanWrapperImpl(value);
return Stream.of(properties)
.map(bean::getPropertyDescriptor)
.map(pd -> this.<T>getProperty(pd, value))
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
// See https://stackoverflow.com/a/3047160/12890
private <T extends Comparable<? super T>> boolean isSorted(Iterable<T> iterable) {
Iterator<T> iter = iterable.iterator();
if (!iter.hasNext()) {
return true;
}
T t = iter.next();
while (iter.hasNext()) {
T t2 = iter.next();
if (t.compareTo(t2) > 0) {
return false;
}
t = t2;
}
return true;
}
@SuppressWarnings("unchecked")
private <T extends Comparable<? super T>> T getProperty(PropertyDescriptor prop, Object bean) {
try {
return prop.getReadMethod() == null ? null : (T)prop.getReadMethod().invoke(bean);
} catch (ReflectiveOperationException noAccess) {
return null;
}
}
}