[hibernate-dev] [Bean Validation] Type-safe Validator and resolution algorithm
Emmanuel Bernard
emmanuel at hibernate.org
Thu Jan 29 00:46:39 EST 2009
Here is the proposal I integrated in the spec. Main changes are in the
email, I cannot attach the whole spec (too big for the mailing list)
but if you ask me I will send it to you by email.
Review very much welcome.
2.1. Constraint annotation
A constraint on a JavaBean is expressed through one or more
annotations. An annotation is considered a constraint definition if
its retention policy contains RUNTIME and if the annotation itself is
annotated with javax.validation.Constraint.
/**
* Link between a constraint annotation and its constraint validation
implementations.
* <p/>
* A given constraint annotation should be annotated by a @Constraint
* annotation which refers to its list of constraint validation
implementation.
*
* @author Emmanuel Bernard (emmanuel at hibernate.org)
* @author Gavin King
* @author Hardy Ferentschik
*/
@Documented
@Target({ ANNOTATION_TYPE })
@Retention(RUNTIME)
public @interface Constraint {
/**
* ConstraintValidator classes must reference distinct target types.
* If two validators refer to the same type, an exception will occur
*
* @return aray of ConstraintValidator classes implementing the
constraint
*/
public Class<? extends ConstraintValidator<?,?>>[] validatedBy();
}
Constraint annotations can target any of the following ElementTypes:
FIELD for constrained attributes
METHOD for constrained getters
TYPE for constrained beans
ANNOTATION_TYPE for constraints composing other constraints
While other ElementTypes are not forbidden, the provider does not have
to recognize and process constraints placed on such types.
Since a given constraint definition applies to one or more specific
Java types, the JavaDoc for the constraint annotation should clearly
state which types are supported. Applying a constraint annotation to
an incompatible type will raise a
UnexpectedTypeForConstraintException. Care should be taken on defining
the list of ConstraintValidator. The type resolution algorithm (see
Section 3.5.3, “ConstraintValidator resolution algorithm”) could lead
to exceptions due to ambiguity.
[...]
2.4. Constraint validation implementation
A constraint validation implementation performs the validation of a
given constraint annotation for a given type. The implementation
classes are specified by the validatedBy element of the at Contraint
annotation that decorates the constraint definition. The constraint
validation implementation implements the ConstraintValidator interface.
Example 2.10. ConstraintValidator interface
/**
* Defines the logic to validate a given constraint A
* for a given object type T.
* Implementations must comply to the following restriction:
* T must resolve to a non parameterized type
*
* @author Emmanuel Bernard
* @author Hardy Ferentschik
*/
public interface ConstraintValidator<A extends Annotation, T> {
/**
* Validator parameters for a given constraint definition
* Annotations parameters are passed as key/value into parameters
* <p/>
* This method is guaranteed to be called before any of the other
Constraint
* implementation methods
*
* @param constraintAnnotation parameters for a given constraint
definition
*/
void initialize(A constraintAnnotation);
/**
* Implement the validation constraint.
* <code>object</code> state must not be changed by a Constraint
implementation
*
* @param object object to validate
* @param constraintValidatorContext context in which the constraint
is evaluated
*
* @return false if <code>object</code> does not pass the constraint
*/
boolean isValid(T object, ConstraintValidatorContext
constraintValidatorContext);
}
Some restrictions apply on the generic type T (used in the isValid
method). T must resolve in a non parameterized type:
the type is not using generics
because the raw type is used instead of the generic version
Warning
Should we support unbounded wildcards?
Here are some examples of valid definitions in Example 2.11, “Valid
ConstraintValidator definitions”.
Example 2.11. Valid ConstraintValidator definitions
//String is not making use of generics
public class SizeValidatorForString implements<Size, String> {...}
//Collection uses generics but the raw type is used
public class SizeValidatorForCollection implements<Size, Collection>
{...}
And some invalid definitions in Example 2.12, “Invalid
ConstraintValidator definitions”.
Example 2.12. Invalid ConstraintValidator definitions
//parameterized type
public class SizeValidatorForString implements<Size,
Collection<String> {...}
//parameterized type using unbounded wildcard
public class SizeValidatorForCollection implements<Size, Collection<?
>> {...}
//parameterized type using bounded wildcard
public class SizeValidatorForCollection implements<Size, Collection<?
extends Address>> {...}
Note
This restriction is not a theoretical limitation and a future version
of the specification will likely allow that in the future.
[...]
3.5.3. ConstraintValidator resolution algorithm
A constraint is associated to one or more ConstraintValidator
implementations. Each ConstraintValidator<A, T> accepts the type T.
The ConstraintValidator executed depends on the type declared by the
target hosting the constraint. For a given constraint evaluation, a
single ConstraintValidator is considered.
If the constraint is hosted on a class or an interface, the targeted
type is the class or the interface. If the constraint is hosted on a
class attribute, the type of the attribute is the targeted type. If
the constraint is hosted on a getter, the return type of the getter is
the targeted type.
The rules written below describe formally the following statement: the
ConstraintValidator chosen to validate a declared type T is the one
where the type supported by theConstraintValidator is a supertype of T
and where there is no other ConstraintValidator whose supported type
is a supertype of T and not a supertype of the
chosenConstraintValidator supported type.
When validating a constraint A placed on a target declaring the type
T, the following resolution rules apply.
Primitive types are considered equivalent to their respective
primitive wrapper class.
A ConstraintValidator<A, U> is said to be compliant with T if T is a
subtype of U (according to the Java Language Specification 3rd edition
chapter 4.10 Subtyping). Note that T is a subtype of U if T = U.
If no ConstraintValidator compliant with T is found amongst the
ConstraintValidators listed by the constraint A, a
UnexpectedTypeForConstraintException is raised.
A ConstraintValidator<A, U> compliant with T is considered strictly
more specific than a ConstraintValidator<A, V> compliant with T if U
is a strict subtype of V. U is a strict subtype of V if U is a subtype
of V and U != V (according to theJava Language Specification 3rd
edition chapter 4.10 Subtyping).
A ConstraintValidator<A, U> compliant with T is considered maximally
specific if no other ConstraintValidator<A, V> compliant with T is
strictly more specific thanConstraintValidator<A, U>.
If more than one maximally specific ConstraintValidator is found, a
AmbiguousConstraintUsageException is raised.
Note
While the Java compiler itself cannot determine if a constraint
declaration will lead to a UnexpectedTypeForConstraintException or
aAmbiguousConstraintUsageException, rules can be statically checked. A
tool such as an IDE or a Java 6 annotation processor can apply these
rules and prevent a compilation in case of ambiguity. The
specification encourages Bean Validation provider to provide such a
tool to their users.
Let's see a couple of declaration their respective ConstraintValidator
resolution. Assuming the following definitions:
[...]
@Constraint(validatedBy={
SizeValidatorForCollection.class,
SizeValidatorForSet.class,
SizeValidatorForSerializable.class })
public @interface Size { ...}
public class SizeValidatorForCollection implements
ConstraintValidator<Size, Collection> { ... }
public class SizeValidatorForSet implements ConstraintValidator<Size,
Set> { ... }
public class SizeValidatorForSerializable implements
ConstraintValidator<Size, Serializable> { ... }
public interface SerializableCollection extends Serializable,
Collection {}
The following resolutions occur.
Table 3.1. Resolution of ConstraintValidator for various constraints
declarations
Declaration Resolution
@Size Collection getAddresses() { ... } SizeValidatorForCollection:
direct match
@Size Collection<?> getAddresses() { ... } SizeValidatorForCollection:
Collection is a direct supertype of Collection<?>
@Size Collection<Address> getAddresses() { ... }
SizeValidatorForCollection: Collection is a direct supertype of
Collection<Address>
@Size Set<Address> getAddresses() { ... } SizeValidatorForSet: direct
supertype of Set<Address>
@Size SortedSet<Address> getAddresses() { ... } SizeValidatorForSet:
Set is the closest supertype of SortedSet<Address>
@Size SerializableCollection getAddresses() { ... }
AmbiguousConstraintUsageExceptionSerializableCollection is a subtype
of both Collection and Serializable and neitherCollection nor
Serializable are subtypes of each other.
@Size String getName() { ... } UnexpectedTypeForConstraintException
none of the ConstraintValidator types are supertypes of String.
[...]
public interface ConstraintDescriptor {
/**
* Returns the annotation describing the constraint declaration.
* If a composing constraint, parameter values are reflecting
* the overridden parameters from the main constraint
*
* @return The annotation for this constraint.
*/
Annotation getAnnotation();
/**
* @return The groups the constraint is applied on.
*/
Set<Class<?>> getGroups();
/**
* @return the constraint validation implementation class
*/
Class<? extends ConstraintValidator<?,?>>[]
getConstraintValidatorClasses();
On Jan 23, 2009, at 15:13, Emmanuel Bernard wrote:
> Today, ConstraintValidator accepts Object and the implementation is
> responsible for handling the casting and raise an exception if the
> type is not supported.
>
> The idea is to get type-safe validator and a discovery mechanism to
> associate a validator to a given runtime type being validated. The
> resolution is not 100% complete, you might end up with:
> - no valid validator
> - no way to decide between two or more validators
> and get exceptions at runtime.
>
> I would really like feedback on it as we could arrange some of these
> rules to not fail (at least in the latter case):
> - do we want this type-safe + auto resolution algorithm strategy or
> should we stick with the untyped solution
> - do we want to fail in case of ambiguity or rather choose the
> first compatible provider?
>
> Personally I find the feature quite elegant and the metadata can be
> used by tools to warn a user if a constraint does not match the
> static type it is applied on.
>
> API and declaration
>
> A more typesafe proposal would be:
>
> public interface ConstraintValidator<A extends Annotation, T> {
> void initialize(A annotation);
> boolean isValid(T object, ConstraintValidationContext context);
> }
>
> public class StringSizeValidator implements
> ConstraintValidator<Size, String> {
> ...
> }
>
> public class CollectionSizeValidator implements
> ConstraintValidator<Size, Collection> {
> ...
> }
>
> public class MapSizeValidator implements ConstraintValidator<Size,
> Map> {
> ...
> }
>
> public class ArraySizeValidator implements ConstraintValidator<Size,
> Object[]> {
> ...
> }
>
>
> @Constraint(validatedBy={
> StringSizeValidator.class, CollectionSizeValidator.class,
> MapSizeValidator.class, ArraySizeValidator.class} )
> public @interface Size { ... }
>
> We then need to decide at runtime, which validator needs to be
> executed.
> Here is a proposed algorithm heavily based on the Java Language
> Specification (boy it's hard to read it).
>
> Resolution algorithm
> The type T of a ConstraintValidator must not make direct use of
> generics itself.
>
> This is because the algorithm compare the actual type at runtime and
> such notion would be lost. So you can do ConstraintValidator<Size,
> Collection> but not ConstraintValidator<Size, Collection<String>>.
>
> For a given runtime object t of type T about to be validated for a
> given constraint:
> if t is null the first validator in the array of available
> validators is used.
> otherwise
>
> A validator accepting U is said to be compliant with the type T if T
> is a subtype of U according to the JLS chapter 4.10.
>
> If no compliant validator is found for T, a
> UnexpectedTypeForConstraint exception is raised.
> If a validator is taking the responsibility to dispatch types (ie
> ConstraintValidator<Size,Object>), it must use the same exception in
> the same circumstances.
>
> If one compliant validator is found, it is used.
>
> If more than one compliant validator is found, the one with the most
> specific parameter is used. The informal intuition is that one
> parameter is more specific than another if any assignment handled by
> the first parameter could be passed on to the other one without a
> compile-time type error.
>
> Of a given type T, a compliant validator accepting U is considered
> maximally specific if no other T compliant validator accepting V is
> such as V is a strict subtype of U. String subtype is defined by JSL
> chapter 4.10.
>
> If more than one maximally specific validator is found, a
> AmbiguousValidatorException is raised.
>
> WDYT?
> _______________________________________________
> hibernate-dev mailing list
> hibernate-dev at lists.jboss.org
> https://lists.jboss.org/mailman/listinfo/hibernate-dev
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.jboss.org/pipermail/hibernate-dev/attachments/20090129/921cb9cb/attachment.html
More information about the hibernate-dev
mailing list