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@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.

  • 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.

  • 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 VU 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).

  • 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

DeclarationResolution
@Size Collection getAddresses() { ... }SizeValidatorForCollection: direct match
@Size Collection<?> getAddresses() { ... }SizeValidatorForCollectionCollection is a direct supertype of Collection<?>
@Size Collection<Address> getAddresses() { ... }SizeValidatorForCollectionCollection is a direct supertype of Collection<Address>
@Size Set<Address> getAddresses() { ... }SizeValidatorForSet: direct supertype of Set<Address>
@Size SortedSet<Address> getAddresses() { ... }SizeValidatorForSetSet 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@lists.jboss.org
https://lists.jboss.org/mailman/listinfo/hibernate-dev