import java.time.*;
import jakarta.validation.constraints.*;
import lombok.Data;
@Data
public class User {
@Email
@NotBlank
private String email;
@NotBlank
private String password;
@NotNull
private String name;
/**
* Problem:
* <p>
* Assume the server is currently using the {@code Europe/Berlin} time zone.
* A user in New York (whose preferred time zone is {@code America/New_York})
* submits a request to set their birthday to [2024-02-15] at [2024-02-15T18:54:32-05:00].
* According to the business rules and the {@code @Past} constraint,
* this value (2024-02-15) should fail validation. However, it will pass because
* the validator refers to the {@code Europe/Berlin} clock [2024-02-16T00:54:32+01:00]
* when validating the {@code @Past} constraint.
*/
@Past
private LocalDate birthday;
private String address;
/**
* Each instance has a time zone set by the user
*/
@NotNull
private ZoneId preferredTimeZone = ZoneId.of("Etc/UTC");
public int getAge() {
// Omit some calculation code
return -1;
}
/**
* This {@code @TimeZoneForValidation} is the custom annotation I want to make.
* <p>
* If present on a class, the {@code @TimeZoneForValidation} annotation will be consulted by
* all date or time-type fields/parameters with date or time annotations in that class.
* <p>
* For example:
* <code>
* <pre>
* // Applies to all date/time annotations (@Past, @PastOrPresent, @Future, @FutureOrPresent) in the class
* @TimeZoneForValidation("Europe/Berlin")
* public class User {
* // ...
*
* @Past
* private LocalDate field1;
*
* // Higher priority, overrides the time zone declared on the class
* @Past
* @TimeZoneForValidation("Asia/Shanghai")
* private LocalDateTime field2;
*
* @Past
* private Date field3;
*
* // ...
* }
* </pre>
* </code>
* <p>
*
*
* {@code @TimeZoneForValidation} can also be applied to methods that return {@code Clock},
* {@code ZoneId}, or {@code TimeZone}. Its effect is the same as when applied to a class,
* but this approach provides greater power and flexibility in programming. For a given class,
* multiple {@code @TimeZoneForValidation} methods are not allowed, nor is it permitted to
* have both {@code @TimeZoneForValidation} methods and a {@code @TimeZoneForValidation} annotation
* on the class.
*/
@TimeZoneForValidation
public Clock getClockProvider() {
return Clock.system(getPreferredTimeZone());
}
}
Thank you for sharing your thoughts on how to approach
the design and your perspective on avoiding reflection.
Reflection is not strictly necessary, and perhaps there's
a more elegant way to achieve this without relying on it,
which I haven't discovered yet. I’ll explore that
direction further.
Kind regards,
Xi Minghui
-------- Message --------
Hello,
Maybe you could provide a bit more information on
your particular use case first?
> solution that dynamically decides which Clock
to provide based on the object currently being
validated
Now, as to the suggested interfaces.
`ClockProvider` is exposed to the user through
`ConstraintValidatorContext` in
`ConstraintValidator#isValid(..)`, which means that at
this point, it was already decided that the constraint
validator has to be executed (i.e. groups were already
evaluated, and constraints relevant for those groups
already picked, so you may or may not have access to
groups at this stage). The same applies to the
field/method parameters. On top of that, validation
providers may rely on other means than field/method
reflection types. Hence, I'd suggest avoiding
reflection types in the API if possible.
I'm also unaware of such a feature in any existing
validation providers, so it may make sense to
introduce something (if anything at all) there first
as an incubating feature, validate the assumptions,
get some user feedback and iterate on it before
introducing the spec change.
Have a nice day,
Marko
Sorry for my negligence, just having the
validation object is not enough, more information
is needed:
package jakarta.validation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.time.Clock;
public interface DynamicClockProvider {
/**
* Returns the clock which serves as the reference for {@code now}.
* <p>
* Ensure that the {@link Clock} used as the reference for {@code now} is obtained
* for each verification of {@code @Future}, {@code @FutureOrPresent}, {@code @Past},
* and {@code @PastOrPresent} constraints.
*
* @param fieldHolder if the validation object is a field value of a class, then this
* will be the instance that holds the value of this field
* @param field if the validation object is a field value of a class, then this will
* be the class's field reflection object
* @param method if the validation object is a method parameter or return, then this
* will be the reflection object of this method
* @param object object to validate
* @param groups the group or list of groups targeted for validation (defaults to
* {@link jakarta.validation.groups.Default Default})
* @param <T> the type of the object to validate
* @return the clock which serves as the reference for {@code now}; must not be
* {@code null}
*
* @since 4.0
*/
<T> Clock getClock(Object fieldHolder,
Field field,
Method method,
T object,
Class<?>... groups);
}
Kind regards,
Xi Minghui
On 10/15/2024 9:30 PM, Xi Minghui wrote:
Hi,
I'm trying to implement a solution that
dynamically decides which Clock to provide based
on the object currently being validated, but the
current ClockProvider
contract doesn't support this.
I thought about implementing it through the ThreadLocal
mechanism, but this would mean that validation
relies on threads, and to support asynchronous or
concurrent execution, more work would be required,
which would increase complexity. I believe the
most direct, effective, and simple approach is to
pass the current validation object to the ClockProvider,
like: change java.time.Clock
getClock() to <T> java.time.Clock
getClock(T object, Class<?>... groups).
Points to consider for the change:
- Keep functional interface: Ideally,
ClockProvider should still be usable with lambda
expressions.
- Consider backward compatibility: Think
about the pain of code migration, especially with
lambda method references.
Proposed Approach 1: Add a New Interface
package jakarta.validation;
import java.time.Clock;
public interface DynamicClockProvider {
/**
* Returns the clock which serves as the reference for {@code now}.
* <p>
* Ensure that the {@link Clock} used as the reference for {@code now} is obtained
* for each verification of {@code @Future}, {@code @FutureOrPresent}, {@code @Past},
* and {@code @PastOrPresent} constraints.
*
* @param object object to validate
* @param groups the group or list of groups targeted for validation (defaults to
* {@link jakarta.validation.groups.Default Default})
* @param <T> the type of the object to validate
* @return the clock which serves as the reference for {@code now}; must not be
* {@code null}
*
* @since 4.0
*/
<T> Clock getClock(T object, Class<?>... groups);
}
Proposed Approach 2: Update the ClockProvider
/*
* Jakarta Validation API
*
* License: Apache License, Version 2.0
*/
package jakarta.validation;
import java.time.Clock;
/**
* Contract for obtaining the {@link Clock} used as the reference for {@code now} when
* validating the {@code @Future}, {@code FutureOrPresent}, {@code Past}, and
* {@code @PastOrPresent} constraints.
* <p>
* The default implementation will return the current system time. Plugging in custom
* implementations may be useful for instance in batch applications which need to run with a
* specific logical date, e.g. with yesterday's date when re-running a failed batch job
* execution.
* <p>
* Implementations must be safe for access from several threads at the same time.
*
* @author Gunnar Morling
* @author Guillaume Smet
* @since 2.0
*/
public interface ClockProvider {
/**
* Returns the clock which serves as the reference for {@code now}.
* <p>
* Ensure that the {@link Clock} used as the reference for {@code now} is obtained
* for each verification of {@code @Future}, {@code @FutureOrPresent}, {@code @Past},
* and {@code @PastOrPresent} constraints.
*
* @param object object to validate
* @param groups the group or list of groups targeted for validation (defaults to
* {@link jakarta.validation.groups.Default Default})
* @param <T> the type of the object to validate
* @return the clock which serves as the reference for {@code now}; must not be
* {@code null}
*
* @since 4.0
*/
<T> Clock getClock(T object, Class<?>... groups);
}
What do you think of this? I would greatly
appreciate your feedback and any other ideas you
might have.
Kind regards,
Xi Minghui
_______________________________________________
bean-validation-dev mailing list
bean-validation-dev@xxxxxxxxxxx
To unsubscribe from this list, visit https://www.eclipse.org/mailman/listinfo/bean-validation-dev