You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bval.apache.org by rm...@apache.org on 2013/07/01 12:06:21 UTC

svn commit: r1498347 [2/7] - in /bval/branches/bval-11: ./ bval-core/src/main/java/org/apache/bval/ bval-core/src/main/java/org/apache/bval/model/ bval-core/src/main/java/org/apache/bval/util/ bval-extras/ bval-guice/ bval-guice/src/main/java/org/apach...

Modified: bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/AnnotationConstraintBuilder.java
URL: http://svn.apache.org/viewvc/bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/AnnotationConstraintBuilder.java?rev=1498347&r1=1498346&r2=1498347&view=diff
==============================================================================
--- bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/AnnotationConstraintBuilder.java (original)
+++ bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/AnnotationConstraintBuilder.java Mon Jul  1 10:06:18 2013
@@ -18,6 +18,23 @@
  */
 package org.apache.bval.jsr303;
 
+import org.apache.bval.jsr303.groups.GroupsComputer;
+import org.apache.bval.jsr303.util.SecureActions;
+import org.apache.bval.jsr303.xml.AnnotationProxyBuilder;
+import org.apache.bval.util.AccessStrategy;
+
+import javax.annotation.processing.SupportedAnnotationTypes;
+import javax.validation.Constraint;
+import javax.validation.ConstraintDeclarationException;
+import javax.validation.ConstraintDefinitionException;
+import javax.validation.ConstraintTarget;
+import javax.validation.ConstraintValidator;
+import javax.validation.ConstraintValidatorContext;
+import javax.validation.OverridesAttribute;
+import javax.validation.Payload;
+import javax.validation.ReportAsSingleViolation;
+import javax.validation.constraintvalidation.SupportedValidationTarget;
+import javax.validation.constraintvalidation.ValidationTarget;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
@@ -34,16 +51,6 @@ import java.util.Set;
 import java.util.logging.Level;
 import java.util.logging.Logger;
 
-import javax.validation.ConstraintDeclarationException;
-import javax.validation.ConstraintValidator;
-import javax.validation.OverridesAttribute;
-import javax.validation.Payload;
-import javax.validation.ReportAsSingleViolation;
-
-import org.apache.bval.jsr303.groups.GroupsComputer;
-import org.apache.bval.jsr303.xml.AnnotationProxyBuilder;
-import org.apache.bval.util.AccessStrategy;
-
 /**
  * Description: helper class that builds a {@link ConstraintValidation} or its
  * composite constraint validations by parsing the jsr303-annotations and
@@ -65,12 +72,13 @@ final class AnnotationConstraintBuilder<
      * @param access
      */
     public AnnotationConstraintBuilder(Class<? extends ConstraintValidator<A, ?>>[] validatorClasses,
-        ConstraintValidator<A, ?> constraintValidator, A annotation, Class<?> owner, AccessStrategy access) {
+        ConstraintValidator<A, ?> constraintValidator, A annotation, Class<?> owner, AccessStrategy access,
+        ConstraintTarget target, RuntimeException missingValidatorException) {
         boolean reportFromComposite =
             annotation != null && annotation.annotationType().isAnnotationPresent(ReportAsSingleViolation.class);
         constraintValidation =
             new ConstraintValidation<A>(validatorClasses, constraintValidator, annotation, owner, access,
-                reportFromComposite);
+                reportFromComposite, target, missingValidatorException);
         buildFromAnnotation();
     }
 
@@ -84,26 +92,91 @@ final class AnnotationConstraintBuilder<
                         // checked by TCK-Tests)
                         if (method.getParameterTypes().length == 0) {
                             try {
-                                if (ConstraintAnnotationAttributes.PAYLOAD.getAttributeName().equals(method.getName())) {
+                                final String name = method.getName();
+                                if (ConstraintAnnotationAttributes.PAYLOAD.getAttributeName().equals(name)) {
                                     buildPayload(method);
-                                } else if (ConstraintAnnotationAttributes.GROUPS.getAttributeName().equals(
-                                    method.getName())) {
+                                } else if (ConstraintAnnotationAttributes.GROUPS.getAttributeName().equals(name)) {
                                     buildGroups(method);
+                                } else if (ConstraintAnnotationAttributes.VALIDATION_APPLIES_TO.getAttributeName().equals(name)) {
+                                    buildValidationAppliesTo(method);
+                                } else if (name.startsWith("valid")) {
+                                    throw new ConstraintDefinitionException("constraints parameters can't start with valid: " + name);
                                 } else {
-                                    constraintValidation.getAttributes().put(method.getName(),
-                                        method.invoke(constraintValidation.getAnnotation()));
+                                    constraintValidation.getAttributes().put(name, method.invoke(constraintValidation.getAnnotation()));
                                 }
-                            } catch (Exception e) { // do nothing
+                            } catch (final ConstraintDefinitionException cde) {
+                                throw cde;
+                            } catch (final Exception e) { // do nothing
                                 log.log(Level.WARNING, String.format("Error processing annotation: %s ", constraintValidation.getAnnotation()), e);
                             }
                         }
                     }
+
+                    // valid validationAppliesTo
+                    final Constraint annotation = constraintValidation.getAnnotation().annotationType().getAnnotation(Constraint.class);
+                    if (annotation == null) {
+                        return null;
+                    }
+
+                    final Pair validationTarget = computeValidationTarget(annotation.validatedBy());
+                    for (final Annotation a : constraintValidation.getAnnotation().annotationType().getAnnotations()) {
+                        final Constraint inheritedConstraint = a.annotationType().getAnnotation(Constraint.class);
+                        if (inheritedConstraint != null && !a.annotationType().getName().startsWith("javax.validation.constraints.")) {
+                            final Pair validationTargetInherited = computeValidationTarget(inheritedConstraint.validatedBy());
+                            if ((validationTarget.a > 0 && validationTargetInherited.b > 0 && validationTarget.b == 0)
+                                    || (validationTarget.b > 0 && validationTargetInherited.a > 0 && validationTarget.a == 0)) {
+                                throw new ConstraintDefinitionException("Parent and child constraint have different targets");
+                            }
+                        }
+                    }
+
                     return null;
                 }
             });
         }
     }
 
+    private Pair computeValidationTarget(final Class<?>[] validators) {
+        int param = 0;
+        int annotatedElt = 0;
+
+        for (final Class<?> validator : validators) {
+            final SupportedValidationTarget supportedAnnotationTypes = validator.getAnnotation(SupportedValidationTarget.class);
+            if (supportedAnnotationTypes != null) {
+                final List<ValidationTarget> values = Arrays.asList(supportedAnnotationTypes.value());
+                if (values.contains(ValidationTarget.PARAMETERS)) {
+                    param++;
+                }
+                if (values.contains(ValidationTarget.ANNOTATED_ELEMENT)) {
+                    annotatedElt++;
+                }
+            } else {
+                annotatedElt++;
+            }
+        }
+
+        if (annotatedElt == 0 && param >= 1 && constraintValidation.getValidationAppliesTo() != null) { // pure cross param
+            throw new ConstraintDefinitionException("pure cross parameter constraints shouldn't get validationAppliesTo attribute");
+        } else {
+            if (param >= 1 && annotatedElt >= 1 && constraintValidation.getValidationAppliesTo() == null) { // generic and cross param
+                throw new ConstraintDefinitionException("cross parameter AND generic constraints should get validationAppliesTo attribute");
+            } else if (param == 0 && constraintValidation.getValidationAppliesTo() != null) { // pure generic
+                throw new ConstraintDefinitionException("pure generic constraints shouldn't get validationAppliesTo attribute");
+            }
+        }
+
+        return new Pair(annotatedElt, param);
+    }
+
+    private void buildValidationAppliesTo(final Method method) throws InvocationTargetException, IllegalAccessException {
+        final Object validationAppliesTo = method.invoke(constraintValidation.getAnnotation());
+        if (ConstraintTarget.class.isInstance(validationAppliesTo)) {
+            constraintValidation.setValidationAppliesTo(ConstraintTarget.class.cast(validationAppliesTo));
+        } else {
+            throw new ConstraintDefinitionException("validationAppliesTo type is " + ConstraintTarget.class.getName());
+        }
+    }
+
     private void buildGroups(Method method) throws IllegalAccessException, InvocationTargetException {
         Object raw = method.invoke(constraintValidation.getAnnotation());
         Class<?>[] garr;
@@ -150,6 +223,11 @@ final class AnnotationConstraintBuilder<
      */
     public void addComposed(ConstraintValidation<?> composite) {
         applyOverridesAttributes(composite);
+
+        if (constraintValidation.getValidationAppliesTo() != null) {
+            composite.setValidationAppliesTo(constraintValidation.getValidationAppliesTo());
+        }
+
         constraintValidation.addComposed(composite); // add AFTER apply()
     }
 
@@ -273,4 +351,14 @@ final class AnnotationConstraintBuilder<
             return action.run();
         }
     }
+
+    private static class Pair {
+        private int a;
+        private int b;
+
+        private Pair(int a, int b) {
+            this.a = a;
+            this.b = b;
+        }
+    }
 }

Modified: bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/AnnotationProcessor.java
URL: http://svn.apache.org/viewvc/bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/AnnotationProcessor.java?rev=1498347&r1=1498346&r2=1498347&view=diff
==============================================================================
--- bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/AnnotationProcessor.java (original)
+++ bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/AnnotationProcessor.java Mon Jul  1 10:06:18 2013
@@ -18,6 +18,29 @@
  */
 package org.apache.bval.jsr303;
 
+import org.apache.bval.jsr303.groups.Group;
+import org.apache.bval.jsr303.groups.GroupConversionDescriptorImpl;
+import org.apache.bval.jsr303.util.ConstraintDefinitionValidator;
+import org.apache.bval.jsr303.util.SecureActions;
+import org.apache.bval.model.Features;
+import org.apache.bval.model.Meta;
+import org.apache.bval.model.MetaBean;
+import org.apache.bval.util.AccessStrategy;
+import org.apache.bval.util.PropertyAccess;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.reflect.TypeUtils;
+
+import javax.validation.Constraint;
+import javax.validation.ConstraintDefinitionException;
+import javax.validation.ConstraintValidator;
+import javax.validation.UnexpectedTypeException;
+import javax.validation.Valid;
+import javax.validation.ValidationException;
+import javax.validation.constraintvalidation.SupportedValidationTarget;
+import javax.validation.constraintvalidation.ValidationTarget;
+import javax.validation.groups.ConvertGroup;
+import javax.validation.groups.Default;
 import java.lang.annotation.Annotation;
 import java.lang.reflect.AnnotatedElement;
 import java.lang.reflect.Array;
@@ -25,28 +48,14 @@ import java.lang.reflect.GenericArrayTyp
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Type;
 import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.HashMap;
+import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
-import javax.validation.Constraint;
-import javax.validation.ConstraintValidator;
-import javax.validation.UnexpectedTypeException;
-import javax.validation.Valid;
-import javax.validation.ValidationException;
-import javax.validation.groups.Default;
-
-import org.apache.bval.jsr303.util.ConstraintDefinitionValidator;
-import org.apache.bval.jsr303.util.SecureActions;
-import org.apache.bval.model.Features;
-import org.apache.bval.model.MetaBean;
-import org.apache.bval.model.MetaProperty;
-import org.apache.bval.util.AccessStrategy;
-import org.apache.commons.lang3.ArrayUtils;
-import org.apache.commons.lang3.ClassUtils;
-import org.apache.commons.lang3.reflect.TypeUtils;
-
 /**
  * Description: implements uniform handling of JSR303 {@link Constraint}
  * annotations, including composed constraints and the resolution of
@@ -82,12 +91,12 @@ public final class AnnotationProcessor {
      * @throws IllegalAccessException
      * @throws InvocationTargetException
      */
-    public boolean processAnnotations(MetaProperty prop, Class<?> owner, AnnotatedElement element,
+    public boolean processAnnotations(Meta prop, Class<?> owner, AnnotatedElement element,
         AccessStrategy access, AppendValidation appender) throws IllegalAccessException, InvocationTargetException {
 
         boolean changed = false;
         for (Annotation annotation : element.getDeclaredAnnotations()) {
-            changed |= processAnnotation(annotation, prop, owner, access, appender);
+            changed |= processAnnotation(annotation, prop, owner, access, appender, true);
         }
         return changed;
     }
@@ -109,7 +118,7 @@ public final class AnnotationProcessor {
      */
     public final <A extends Annotation> boolean processAnnotation(A annotation, Class<?> owner, AppendValidation appender)
         throws IllegalAccessException, InvocationTargetException {
-        return processAnnotation(annotation, null, owner, null, appender);
+        return processAnnotation(annotation, null, owner, null, appender, true);
     }
 
     /**
@@ -131,11 +140,24 @@ public final class AnnotationProcessor {
      * @throws IllegalAccessException
      * @throws InvocationTargetException
      */
-    public <A extends Annotation> boolean processAnnotation(A annotation, MetaProperty prop, Class<?> owner,
-        AccessStrategy access, AppendValidation appender) throws IllegalAccessException, InvocationTargetException {
+    public <A extends Annotation> boolean processAnnotation(A annotation, Meta prop, Class<?> owner,
+        AccessStrategy access, AppendValidation appender, boolean reflection) throws IllegalAccessException, InvocationTargetException {
         if (annotation instanceof Valid) {
             return addAccessStrategy(prop, access);
         }
+
+        if (ConvertGroup.class.isInstance(annotation) || ConvertGroup.List.class.isInstance(annotation)) {
+            if (!reflection) {
+                Collection<Annotation> annotations = prop.getFeature(Jsr303Features.Property.ANNOTATIONS_TO_PROCESS);
+                if (annotations == null) {
+                    annotations = new ArrayList<Annotation>();
+                    prop.putFeature(Jsr303Features.Property.ANNOTATIONS_TO_PROCESS, annotations);
+                }
+                annotations.add(annotation);
+            }
+            return true;
+        }
+
         /**
          * An annotation is considered a constraint definition if its retention
          * policy contains RUNTIME and if the annotation itself is annotated
@@ -154,12 +176,11 @@ public final class AnnotationProcessor {
          * annotated by @Constraint) whose value element has a return type of an
          * array of constraint annotations in a special way.
          */
-        Object result =
-            SecureActions.getAnnotationValue(annotation, ConstraintAnnotationAttributes.VALUE.getAttributeName());
+        final Object result = SecureActions.getAnnotationValue(annotation, ConstraintAnnotationAttributes.VALUE.getAttributeName());
         if (result instanceof Annotation[]) {
             boolean changed = false;
-            for (Annotation each : (Annotation[]) result) {
-                changed |= processAnnotation(each, prop, owner, access, appender);
+            for (final Annotation each : (Annotation[]) result) {
+                changed |= processAnnotation(each, prop, owner, access, appender, reflection);
             }
             return changed;
         }
@@ -174,7 +195,7 @@ public final class AnnotationProcessor {
      * @param access
      * @return whether anything took place.
      */
-    public boolean addAccessStrategy(MetaProperty prop, AccessStrategy access) {
+    public boolean addAccessStrategy(Meta prop, AccessStrategy access) {
         if (prop == null) {
             return false;
         }
@@ -223,7 +244,7 @@ public final class AnnotationProcessor {
      * 
      * @param annotation
      *            constraint annotation
-     * @param constraintClasses
+     * @param rawConstraintClasses
      *            known {@link ConstraintValidator} implementation classes for
      *            <code>annotation</code>
      * @param prop
@@ -238,13 +259,23 @@ public final class AnnotationProcessor {
      * @throws InvocationTargetException
      */
     private <A extends Annotation> boolean applyConstraint(A annotation,
-        Class<? extends ConstraintValidator<A, ?>>[] constraintClasses, MetaProperty prop, Class<?> owner,
+        Class<? extends ConstraintValidator<A, ?>>[] rawConstraintClasses, Meta prop, Class<?> owner,
         AccessStrategy access, AppendValidation appender) throws IllegalAccessException, InvocationTargetException {
 
-        final ConstraintValidator<A, ?> validator =
-            getConstraintValidator(annotation, constraintClasses, owner, access);
+        final Class<? extends ConstraintValidator<A, ?>>[] constraintClasses = select(rawConstraintClasses, access);
+        if (constraintClasses != null && constraintClasses.length == 0 && rawConstraintClasses.length > 0) {
+            return false;
+        }
+
+        RuntimeException missingValidatorException = null;
+        ConstraintValidator<A, ?> validator = null;
+        try {
+            validator = getConstraintValidator(annotation, constraintClasses, owner, access);
+        } catch (final RuntimeException e) {
+            missingValidatorException = e;
+        }
         final AnnotationConstraintBuilder<A> builder =
-            new AnnotationConstraintBuilder<A>(constraintClasses, validator, annotation, owner, access);
+            new AnnotationConstraintBuilder<A>(constraintClasses, validator, annotation, owner, access, null, missingValidatorException);
 
         // JSR-303 3.4.4: Add implicit groups
         if (prop != null && prop.getParentMetaBean() != null) {
@@ -284,10 +315,36 @@ public final class AnnotationProcessor {
         return true;
     }
 
+    private static <A extends Annotation> Class<? extends ConstraintValidator<A, ?>>[] select(
+            final Class<? extends ConstraintValidator<A, ?>>[] rawConstraintClasses, final AccessStrategy access) {
+        final boolean isReturn = ReturnAccess.class.isInstance(access);
+        final boolean isParam = ParametersAccess.class.isInstance(access);
+        if (rawConstraintClasses != null && (isReturn || isParam)) {
+            final Collection<Class<? extends ConstraintValidator<A, ?>>> selected = new ArrayList<Class<? extends ConstraintValidator<A, ?>>>();
+            for (final Class<? extends ConstraintValidator<A, ?>> constraint : rawConstraintClasses) {
+                final SupportedValidationTarget target = constraint.getAnnotation(SupportedValidationTarget.class);
+                if (target == null && isReturn) {
+                    selected.add(constraint);
+                } else if (target != null) {
+                    for (final ValidationTarget validationTarget : target.value()) {
+                        if (isReturn && ValidationTarget.ANNOTATED_ELEMENT.equals(validationTarget)) {
+                            selected.add(constraint);
+                        } else if (isParam && ValidationTarget.PARAMETERS.equals(validationTarget)) {
+                            selected.add(constraint);
+                        }
+                    }
+            }
+            }
+            return selected.toArray(new Class[selected.size()]);
+        }
+        return rawConstraintClasses;
+    }
+
     private <A extends Annotation, T> ConstraintValidator<A, ? super T> getConstraintValidator(A annotation,
         Class<? extends ConstraintValidator<A, ?>>[] constraintClasses, Class<?> owner, AccessStrategy access) {
         if (constraintClasses != null && constraintClasses.length > 0) {
             Type type = determineTargetedType(owner, access);
+
             /**
              * spec says in chapter 3.5.3.: The ConstraintValidator chosen to
              * validate a declared type T is the one where the type supported by
@@ -296,20 +353,31 @@ public final class AnnotationProcessor {
              * T and not a supertype of the chosen ConstraintValidator supported
              * type.
              */
-            Map<Type, Class<? extends ConstraintValidator<A, ?>>> validatorTypes =
-                getValidatorsTypes(constraintClasses);
+            final Map<Type, Collection<Class<? extends ConstraintValidator<A, ?>>>> validatorTypes = getValidatorsTypes(constraintClasses);
+            reduceTarget(validatorTypes, access);
+
             final List<Type> assignableTypes = new ArrayList<Type>(constraintClasses.length);
             fillAssignableTypes(type, validatorTypes.keySet(), assignableTypes);
             reduceAssignableTypes(assignableTypes);
             checkOneType(assignableTypes, type, owner, annotation, access);
 
+            if ((type == Object.class || type == Object[].class) && validatorTypes.containsKey(Object.class) && validatorTypes.containsKey(Object[].class)) {
+                throw new ConstraintDefinitionException("Only a validator for Object or Object[] should be provided for cross parameter validators");
+            }
+
+            final Collection<Class<? extends ConstraintValidator<A, ?>>> key = validatorTypes.get(assignableTypes.get(0));
+            if (key.size() > 1) {
+                final String message = "Factory returned " + key.size() + " validators";
+                if (ParametersAccess.class.isInstance(access)) { // cross parameter
+                    throw new ConstraintDefinitionException(message);
+                }
+                throw new UnexpectedTypeException(message);
+            }
+
             @SuppressWarnings("unchecked")
-            final ConstraintValidator<A, ? super T> validator =
-                (ConstraintValidator<A, ? super T>) factoryContext.getConstraintValidatorFactory()
-                    .getInstance(validatorTypes.get(assignableTypes.get(0)));
+            final ConstraintValidator<A, ? super T> validator = (ConstraintValidator<A, ? super T>) factoryContext.getConstraintValidatorFactory().getInstance(key.iterator().next());
             if (validator == null) {
-                throw new ValidationException("Factory returned null validator for: "
-                    + validatorTypes.get(assignableTypes.get(0)));
+                throw new ValidationException("Factory returned null validator for: " + key);
 
             }
             return validator;
@@ -318,15 +386,43 @@ public final class AnnotationProcessor {
         return null;
     }
 
+    private <A extends Annotation> void reduceTarget(final Map<Type, Collection<Class<? extends ConstraintValidator<A, ?>>>> validator, final AccessStrategy access) {
+        for (final Map.Entry<Type, Collection<Class<? extends ConstraintValidator<A, ?>>>> entry : validator.entrySet()) {
+            final Collection<Class<? extends ConstraintValidator<A, ?>>> validators = entry.getValue();
+            final Iterator<Class<? extends ConstraintValidator<A, ?>>> it = validators.iterator();
+            while (it.hasNext()) {
+                final Type v = it.next();
+                if (!Class.class.isInstance(v)) {
+                    continue; // TODO: handle this case
+                }
+
+                final Class<?> clazz = Class.class.cast(v);
+                final SupportedValidationTarget target = clazz.getAnnotation(SupportedValidationTarget.class);
+                if (target != null) {
+                    final Collection<ValidationTarget> targets = Arrays.asList(target.value());
+                    final boolean isParameter = ParameterAccess.class.isInstance(access) || ParametersAccess.class.isInstance(access);
+                    if ((isParameter && !targets.contains(ValidationTarget.PARAMETERS))
+                            || (!isParameter && !targets.contains(ValidationTarget.ANNOTATED_ELEMENT))) {
+                        it.remove();
+                    }
+                }
+            }
+            if (validators.isEmpty()) {
+                validator.remove(entry.getKey());
+            }
+        }
+    }
+
     private static void checkOneType(List<Type> types, Type targetType, Class<?> owner, Annotation anno,
         AccessStrategy access) {
 
         if (types.isEmpty()) {
-            StringBuilder buf =
-                new StringBuilder().append("No validator could be found for type ").append(stringForType(targetType))
-                    .append(". See: @").append(anno.annotationType().getSimpleName()).append(" at ").append(
-                        stringForLocation(owner, access));
-            throw new UnexpectedTypeException(buf.toString());
+            final String message = "No validator could be found for type " + stringForType(targetType)
+                    + ". See: @" + anno.annotationType().getSimpleName() + " at " + stringForLocation(owner, access);
+            if (Object[].class.equals(targetType)) { // cross parameter
+                throw new ConstraintDefinitionException(message);
+            }
+            throw new UnexpectedTypeException(message);
         } else if (types.size() > 1) {
             StringBuilder buf = new StringBuilder();
             buf.append("Ambiguous validators for type ");
@@ -380,7 +476,7 @@ public final class AnnotationProcessor {
     }
 
     private static void fillAssignableTypes(Type type, Set<Type> validatorsTypes, List<Type> suitableTypes) {
-        for (Type validatorType : validatorsTypes) {
+        for (final Type validatorType : validatorsTypes) {
             if (org.apache.commons.lang3.reflect.TypeUtils.isAssignable(type, validatorType)
                 && !suitableTypes.contains(validatorType)) {
                 suitableTypes.add(validatorType);
@@ -426,14 +522,11 @@ public final class AnnotationProcessor {
      * @param constraintValidatorClasses
      * @return {@link Map} of {@link Type} : {@link ConstraintValidator} subtype
      */
-    private static <A extends Annotation> Map<Type, Class<? extends ConstraintValidator<A, ?>>> getValidatorsTypes(
+    private static <A extends Annotation> Map<Type, Collection<Class<? extends ConstraintValidator<A, ?>>>> getValidatorsTypes(
         Class<? extends ConstraintValidator<A, ?>>[] constraintValidatorClasses) {
-        Map<Type, Class<? extends ConstraintValidator<A, ?>>> validatorsTypes =
-            new HashMap<Type, Class<? extends ConstraintValidator<A, ?>>>();
+        final Map<Type, Collection<Class<? extends ConstraintValidator<A, ?>>>> validatorsTypes = new HashMap<Type, Collection<Class<? extends ConstraintValidator<A, ?>>>>();
         for (Class<? extends ConstraintValidator<A, ?>> validatorType : constraintValidatorClasses) {
-            Type validatedType =
-                TypeUtils.getTypeArguments(validatorType, ConstraintValidator.class).get(
-                    ConstraintValidator.class.getTypeParameters()[1]);
+            Type validatedType = TypeUtils.getTypeArguments(validatorType, ConstraintValidator.class).get(ConstraintValidator.class.getTypeParameters()[1]);
             if (validatedType == null) {
                 throw new ValidationException(String.format("Could not detect validated type for %s", validatorType));
             }
@@ -443,7 +536,10 @@ public final class AnnotationProcessor {
                     validatedType = Array.newInstance((Class<?>) componentType, 0).getClass();
                 }
             }
-            validatorsTypes.put(validatedType, validatorType);
+            if (!validatorsTypes.containsKey(validatedType)) {
+                validatorsTypes.put(validatedType, new ArrayList<Class<? extends ConstraintValidator<A, ?>>>());
+            }
+            validatorsTypes.get(validatedType).add(validatorType);
         }
         return validatorsTypes;
     }

Modified: bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/ApacheFactoryContext.java
URL: http://svn.apache.org/viewvc/bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/ApacheFactoryContext.java?rev=1498347&r1=1498346&r2=1498347&view=diff
==============================================================================
--- bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/ApacheFactoryContext.java (original)
+++ bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/ApacheFactoryContext.java Mon Jul  1 10:06:18 2013
@@ -26,6 +26,7 @@ import java.util.List;
 
 import javax.validation.ConstraintValidatorFactory;
 import javax.validation.MessageInterpolator;
+import javax.validation.ParameterNameProvider;
 import javax.validation.TraversableResolver;
 import javax.validation.ValidationException;
 import javax.validation.Validator;
@@ -53,6 +54,7 @@ public class ApacheFactoryContext implem
 
     private MessageInterpolator messageInterpolator;
     private TraversableResolver traversableResolver;
+    private ParameterNameProvider parameterNameProvider;
     private ConstraintValidatorFactory constraintValidatorFactory;
 
     /**
@@ -119,6 +121,11 @@ public class ApacheFactoryContext implem
         return this;
     }
 
+    public ValidatorContext parameterNameProvider(ParameterNameProvider parameterNameProvider) {
+        this.parameterNameProvider = parameterNameProvider;
+        return this;
+    }
+
     /**
      * Get the {@link ConstraintValidatorFactory}.
      * 
@@ -159,6 +166,10 @@ public class ApacheFactoryContext implem
         return traversableResolver == null ? factory.getTraversableResolver() : traversableResolver;
     }
 
+    public ParameterNameProvider getParameterNameProvider() {
+        return parameterNameProvider == null ? factory.getParameterNameProvider() : parameterNameProvider;
+    }
+
     /**
      * Create MetaBeanManager that uses factories:
      * <ol>

Modified: bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/ApacheValidatorConfiguration.java
URL: http://svn.apache.org/viewvc/bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/ApacheValidatorConfiguration.java?rev=1498347&r1=1498346&r2=1498347&view=diff
==============================================================================
--- bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/ApacheValidatorConfiguration.java (original)
+++ bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/ApacheValidatorConfiguration.java Mon Jul  1 10:06:18 2013
@@ -87,5 +87,7 @@ public interface ApacheValidatorConfigur
          * </ol>
          */
          String METABEAN_FACTORY_CLASSNAMES = "apache.bval.metabean-factory-classnames";
+
+        String EXECUTABLE_VALIDATION_TYPES = "apache.bval.executable-validation.types";
     }
 }

Modified: bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/ApacheValidatorFactory.java
URL: http://svn.apache.org/viewvc/bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/ApacheValidatorFactory.java?rev=1498347&r1=1498346&r2=1498347&view=diff
==============================================================================
--- bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/ApacheValidatorFactory.java (original)
+++ bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/ApacheValidatorFactory.java Mon Jul  1 10:06:18 2013
@@ -18,31 +18,35 @@
  */
 package org.apache.bval.jsr303;
 
-import java.lang.annotation.Annotation;
-import java.lang.reflect.Modifier;
-import java.security.AccessController;
-import java.security.PrivilegedAction;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
+import org.apache.bval.jsr303.xml.AnnotationIgnores;
+import org.apache.bval.jsr303.xml.MetaConstraint;
+import org.apache.bval.jsr303.xml.ValidationMappingParser;
+import org.apache.bval.util.AccessStrategy;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils;
 
 import javax.validation.ConstraintValidatorFactory;
 import javax.validation.MessageInterpolator;
+import javax.validation.ParameterNameProvider;
 import javax.validation.TraversableResolver;
 import javax.validation.Validation;
 import javax.validation.ValidationException;
 import javax.validation.Validator;
 import javax.validation.ValidatorFactory;
+import javax.validation.executable.ExecutableType;
 import javax.validation.spi.ConfigurationState;
-
-import org.apache.bval.jsr303.xml.AnnotationIgnores;
-import org.apache.bval.jsr303.xml.MetaConstraint;
-import org.apache.bval.jsr303.xml.ValidationMappingParser;
-import org.apache.bval.util.AccessStrategy;
-import org.apache.commons.lang3.ArrayUtils;
-import org.apache.commons.lang3.ClassUtils;
+import java.io.Closeable;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CopyOnWriteArrayList;
 
 /**
  * Description: a factory is a complete configurated object that can create
@@ -51,12 +55,12 @@ import org.apache.commons.lang3.ClassUti
  */
 public class ApacheValidatorFactory implements ValidatorFactory, Cloneable {
     private static volatile ApacheValidatorFactory DEFAULT_FACTORY;
-    private static final ConstraintDefaults defaultConstraints =
-            new ConstraintDefaults();
+    private static final ConstraintDefaults defaultConstraints = new ConstraintDefaults();
 
     private MessageInterpolator messageResolver;
     private TraversableResolver traversableResolver;
     private ConstraintValidatorFactory constraintValidatorFactory;
+    private ParameterNameProvider parameterNameProvider;
     private final Map<String, String> properties;
 
     /**
@@ -72,6 +76,9 @@ public class ApacheValidatorFactory impl
     private final Map<Class<?>, List<AccessStrategy>> validAccesses;
     private final Map<Class<?>, List<MetaConstraint<?, ? extends Annotation>>> constraintMap;
 
+    private final Collection<ExecutableType> executableTypes = new CopyOnWriteArrayList<ExecutableType>();
+    private final Collection<Closeable> toClose = new ArrayList<Closeable>();
+
     /**
      * Convenience method to retrieve a default global ApacheValidatorFactory
      *
@@ -79,9 +86,8 @@ public class ApacheValidatorFactory impl
      */
     public static synchronized ApacheValidatorFactory getDefault() {
         if (DEFAULT_FACTORY == null) {
-            DEFAULT_FACTORY =
-                Validation.byProvider(ApacheValidationProvider.class).configure().buildValidatorFactory()
-                    .unwrap(ApacheValidatorFactory.class);
+            DEFAULT_FACTORY = Validation.byProvider(ApacheValidationProvider.class).configure()
+                    .buildValidatorFactory().unwrap(ApacheValidatorFactory.class);
         }
         return DEFAULT_FACTORY;
     }
@@ -102,8 +108,7 @@ public class ApacheValidatorFactory impl
         properties = new HashMap<String, String>();
         defaultSequences = new HashMap<Class<?>, Class<?>[]>();
         validAccesses = new HashMap<Class<?>, List<AccessStrategy>>();
-        constraintMap =
-                new HashMap<Class<?>, List<MetaConstraint<?, ? extends Annotation>>>();
+        constraintMap = new HashMap<Class<?>, List<MetaConstraint<?, ? extends Annotation>>>();
         configure(configurationState);
     }
 
@@ -115,12 +120,25 @@ public class ApacheValidatorFactory impl
      */
     protected void configure(ConfigurationState configuration) {
         getProperties().putAll(configuration.getProperties());
+        setParameterNameProvider(configuration.getParameterNameProvider());
         setMessageInterpolator(configuration.getMessageInterpolator());
         setTraversableResolver(configuration.getTraversableResolver());
-        setConstraintValidatorFactory(configuration
-                .getConstraintValidatorFactory());
-        ValidationMappingParser parser = new ValidationMappingParser(this);
-        parser.processMappingConfig(configuration.getMappingStreams());
+        setConstraintValidatorFactory(configuration.getConstraintValidatorFactory());
+
+        if (ConfigurationImpl.class.isInstance(configuration)) {
+            final ConfigurationImpl impl = ConfigurationImpl.class.cast(configuration);
+            executableTypes.addAll(impl.getExecutableValidation());
+            toClose.add(impl.getClosable());
+        } else {
+            final String executableTypesStr = getProperties().get(ApacheValidatorConfiguration.Properties.EXECUTABLE_VALIDATION_TYPES);
+            if (executableTypesStr != null && !executableTypesStr.isEmpty()) {
+                for (final String s : executableTypesStr.split(",")) {
+                    executableTypes.add(ExecutableType.valueOf(s.trim()));
+                }
+            }
+        }
+
+        new ValidationMappingParser(this).processMappingConfig(configuration.getMappingStreams());
     }
 
     /**
@@ -169,7 +187,9 @@ public class ApacheValidatorFactory impl
      * @param messageResolver
      */
     public final void setMessageInterpolator(MessageInterpolator messageResolver) {
-        this.messageResolver = messageResolver;
+        if (messageResolver != null) {
+            this.messageResolver = messageResolver;
+        }
     }
 
     /**
@@ -186,7 +206,15 @@ public class ApacheValidatorFactory impl
      */
     public final void setTraversableResolver(
             TraversableResolver traversableResolver) {
-        this.traversableResolver = traversableResolver;
+        if (traversableResolver != null) {
+            this.traversableResolver = traversableResolver;
+        }
+    }
+
+    public void setParameterNameProvider(final ParameterNameProvider parameterNameProvider) {
+        if (parameterNameProvider != null) {
+            this.parameterNameProvider = parameterNameProvider;
+        }
     }
 
     /**
@@ -203,7 +231,12 @@ public class ApacheValidatorFactory impl
      */
     public final void setConstraintValidatorFactory(
             ConstraintValidatorFactory constraintValidatorFactory) {
-        this.constraintValidatorFactory = constraintValidatorFactory;
+        if (constraintValidatorFactory != null) {
+            this.constraintValidatorFactory = constraintValidatorFactory;
+            if (DefaultConstraintValidatorFactory.class.isInstance(constraintValidatorFactory)) {
+                toClose.add(Closeable.class.cast(constraintValidatorFactory));
+            }
+        }
     }
 
     /**
@@ -213,6 +246,21 @@ public class ApacheValidatorFactory impl
         return constraintValidatorFactory;
     }
 
+    public ParameterNameProvider getParameterNameProvider() {
+        return parameterNameProvider;
+    }
+
+    public void close() {
+        try {
+            for (final Closeable c : toClose) {
+                c.close();
+            }
+            toClose.clear();
+        } catch (final Exception e) {
+            // no-op
+        }
+    }
+
     /**
      * Return an object of the specified type to allow access to the
      * provider-specific API. If the Bean Validation provider implementation
@@ -224,9 +272,7 @@ public class ApacheValidatorFactory impl
      */
     public <T> T unwrap(final Class<T> type) {
         if (type.isInstance(this)) {
-            @SuppressWarnings("unchecked")
-            T result = (T) this;
-            return result;
+            return (T) this;
         }
 
         // FIXME 2011-03-27 jw:
@@ -297,8 +343,8 @@ public class ApacheValidatorFactory impl
      * @param beanClass
      * @param metaConstraint
      */
-    public void addMetaConstraint(Class<?> beanClass,
-                                  MetaConstraint<?, ?> metaConstraint) {
+    public void addMetaConstraint(final Class<?> beanClass,
+                                  final MetaConstraint<?, ?> metaConstraint) {
         List<MetaConstraint<?, ? extends Annotation>> slot;
         synchronized (constraintMap) {
             slot = constraintMap.get(beanClass);
@@ -339,6 +385,10 @@ public class ApacheValidatorFactory impl
         defaultSequences.put(beanClass, safeArray(groupSequence));
     }
 
+    public Collection<ExecutableType> getExecutableTypes() {
+        return executableTypes;
+    }
+
     /**
      * Retrieve the runtime constraint configuration for a given class.
      *

Added: bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/AppendValidationToList.java
URL: http://svn.apache.org/viewvc/bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/AppendValidationToList.java?rev=1498347&view=auto
==============================================================================
--- bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/AppendValidationToList.java (added)
+++ bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/AppendValidationToList.java Mon Jul  1 10:06:18 2013
@@ -0,0 +1,51 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one or more
+ *  contributor license agreements.  See the NOTICE file distributed with
+ *  this work for additional information regarding copyright ownership.
+ *  The ASF licenses this file to You under the Apache License, Version 2.0
+ *  (the "License"); you may not use this file except in compliance with
+ *  the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+package org.apache.bval.jsr303;
+
+
+import java.lang.annotation.Annotation;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Description: {@link org.apache.bval.jsr303.AppendValidation} implementation that acts as an intermediate
+ * cache of validations for further processing.<br/>
+ */
+public class AppendValidationToList extends BaseAppendValidation {
+    private final List<ConstraintValidation<?>> validations = new ArrayList<ConstraintValidation<?>>();
+
+    /**
+     * Create a new AppendValidationToList instance.
+     */
+    public AppendValidationToList() {
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public <T extends Annotation> void performAppend(ConstraintValidation<T> validation) {
+        validations.add(validation);
+    }
+
+    /**
+     * Get the list of cached validations.
+     * @return {@link java.util.List} of {@link org.apache.bval.jsr303.ConstraintValidation}
+     */
+    public List<ConstraintValidation<?>> getValidations() {
+        return validations;
+    }
+}

Modified: bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/BeanDescriptorImpl.java
URL: http://svn.apache.org/viewvc/bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/BeanDescriptorImpl.java?rev=1498347&r1=1498346&r2=1498347&view=diff
==============================================================================
--- bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/BeanDescriptorImpl.java (original)
+++ bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/BeanDescriptorImpl.java Mon Jul  1 10:06:18 2013
@@ -18,35 +18,149 @@
  */
 package org.apache.bval.jsr303;
 
+import org.apache.bval.Validate;
+import org.apache.bval.jsr303.groups.Group;
+import org.apache.bval.jsr303.groups.GroupConversionDescriptorImpl;
+import org.apache.bval.jsr303.util.ClassHelper;
+import org.apache.bval.jsr303.util.SecureActions;
 import org.apache.bval.model.Features;
 import org.apache.bval.model.MetaBean;
+import org.apache.bval.model.MetaConstructor;
+import org.apache.bval.model.MetaMethod;
+import org.apache.bval.model.MetaParameter;
 import org.apache.bval.model.MetaProperty;
+import org.apache.bval.model.Validation;
+import org.apache.bval.util.AccessStrategy;
+import org.apache.commons.lang3.ClassUtils;
 
+import javax.validation.Constraint;
+import javax.validation.ConstraintDeclarationException;
+import javax.validation.ConstraintTarget;
+import javax.validation.Valid;
+import javax.validation.groups.ConvertGroup;
 import javax.validation.metadata.BeanDescriptor;
+import javax.validation.metadata.ConstructorDescriptor;
+import javax.validation.metadata.ExecutableDescriptor;
+import javax.validation.metadata.GroupConversionDescriptor;
+import javax.validation.metadata.MethodDescriptor;
+import javax.validation.metadata.MethodType;
+import javax.validation.metadata.ParameterDescriptor;
 import javax.validation.metadata.PropertyDescriptor;
+import javax.validation.metadata.ReturnValueDescriptor;
+import java.beans.Introspector;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.HashSet;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 import java.util.Set;
+import java.util.concurrent.CopyOnWriteArraySet;
 
 /**
  * Description: Implements {@link BeanDescriptor}.<br/>
  */
 public class BeanDescriptorImpl extends ElementDescriptorImpl implements BeanDescriptor {
+    private static final CopyOnWriteArraySet<ConstraintValidation<?>> NO_CONSTRAINTS = new CopyOnWriteArraySet<ConstraintValidation<?>>();
     /**
      * The {@link ApacheFactoryContext} (not) used by this
      * {@link BeanDescriptorImpl}
      */
     protected final ApacheFactoryContext factoryContext;
+    private final AnnotationProcessor annotationProcessor;
+    private Set<ConstructorDescriptor> constrainedConstructors = new CopyOnWriteArraySet<ConstructorDescriptor>();
+    private Map<Method, MethodDescriptor> methodConstraints = new HashMap<Method, MethodDescriptor>();
+    private Set<MethodDescriptor> containedMethods = new CopyOnWriteArraySet<MethodDescriptor>();
+    private Map<Constructor<?>, ConstructorDescriptor> contructorConstraints = new HashMap<Constructor<?>, ConstructorDescriptor>();
 
-    /**
-     * Create a new BeanDescriptorImpl instance.
-     * 
-     * @param factoryContext
-     * @param metaBean
-     */
     protected BeanDescriptorImpl(ApacheFactoryContext factoryContext, MetaBean metaBean) {
         super(metaBean, metaBean.getBeanClass(), metaBean.getValidations());
         this.factoryContext = factoryContext;
+        this.annotationProcessor = new AnnotationProcessor(factoryContext);
+
+        buildExecutableDescriptors();
+    }
+
+    private static void addGroupConvertion(final MetaProperty prop, final PropertyDescriptorImpl edesc) {
+        boolean fieldFound = false;
+        boolean methodFound = false;
+        Class<?> current = prop.getParentMetaBean().getBeanClass();
+        while (current != null && current != Object.class && (!methodFound || !fieldFound)) {
+            if (!fieldFound) {
+                final Field field = SecureActions.getDeclaredField(current, prop.getName()).run();
+                if (field != null) {
+                    final ConvertGroup.List convertGroupList = field.getAnnotation(ConvertGroup.List.class);
+                    if (convertGroupList != null) {
+                        for (final ConvertGroup convertGroup : convertGroupList.value()) {
+                            edesc.addGroupConversion(new GroupConversionDescriptorImpl(new Group(convertGroup.from()), new Group(convertGroup.to())));
+                        }
+                    }
+
+                    final ConvertGroup convertGroup = field.getAnnotation(ConvertGroup.class);
+                    if (convertGroup != null) {
+                        edesc.addGroupConversion(new GroupConversionDescriptorImpl(new Group(convertGroup.from()), new Group(convertGroup.to())));
+                    }
+                    fieldFound = true;
+                }
+            }
+
+            if (!methodFound) {
+                final String name = Character.toUpperCase(prop.getName().charAt(0)) + prop.getName().substring(1);
+                for (final Method method : Arrays.asList(
+                        SecureActions.getDeclaredMethod(current, "is" + name).run(),
+                        SecureActions.getDeclaredMethod(current, "get" + name).run())) {
+
+                    if (method != null) {
+                        final ConvertGroup.List convertGroupList = method.getAnnotation(ConvertGroup.List.class);
+                        if (convertGroupList != null) {
+                            for (final ConvertGroup convertGroup : convertGroupList.value()) {
+                                edesc.addGroupConversion(new GroupConversionDescriptorImpl(new Group(convertGroup.from()), new Group(convertGroup.to())));
+                            }
+                        }
+
+                        final ConvertGroup convertGroup = method.getAnnotation(ConvertGroup.class);
+                        if (convertGroup != null) {
+                            edesc.addGroupConversion(new GroupConversionDescriptorImpl(new Group(convertGroup.from()), new Group(convertGroup.to())));
+                        }
+
+                        methodFound = true;
+                        break;
+                    }
+                }
+            }
+
+            current = current.getSuperclass();
+        }
+
+        final Collection<Annotation> annotations = prop.getFeature(Jsr303Features.Property.ANNOTATIONS_TO_PROCESS);
+        if (annotations != null) {
+            for (final Annotation a : annotations) {
+                if (ConvertGroup.List.class.isInstance(a)) {
+                    for (final ConvertGroup convertGroup : ConvertGroup.List.class.cast(a).value()) {
+                        edesc.addGroupConversion(new GroupConversionDescriptorImpl(new Group(convertGroup.from()), new Group(convertGroup.to())));
+                    }
+                }
+
+                if (ConvertGroup.class.isInstance(a)) {
+                    final ConvertGroup convertGroup = ConvertGroup.class.cast(a);
+                    edesc.addGroupConversion(new GroupConversionDescriptorImpl(new Group(convertGroup.from()), new Group(convertGroup.to())));
+                }
+            }
+            annotations.clear();
+        }
+
+        if (!edesc.getGroupConversions().isEmpty() && !edesc.isCascaded()) {
+            throw new ConstraintDeclarationException("@Valid is needed for group conversion");
+        }
     }
 
     /**
@@ -56,7 +170,7 @@ public class BeanDescriptorImpl extends 
      * <li>a constraint is hosted on one of the bean properties, OR</li>
      * <li>a bean property is marked for cascade (<code>@Valid</code>)</li>
      * </ul>
-     * 
+     *
      * @return true if the bean involves validation
      */
     public boolean isBeanConstrained() {
@@ -84,9 +198,8 @@ public class BeanDescriptorImpl extends 
      * either the property does not exist or has no constraint. The returned
      * object (and associated objects including ConstraintDescriptors) are
      * immutable.
-     * 
-     * @param propertyName
-     *            property evaluated
+     *
+     * @param propertyName property evaluated
      */
     public PropertyDescriptor getConstraintsForProperty(String propertyName) {
         if (propertyName == null || propertyName.trim().length() == 0) {
@@ -106,6 +219,7 @@ public class BeanDescriptorImpl extends 
         PropertyDescriptorImpl edesc = prop.getFeature(Jsr303Features.Property.PropertyDescriptor);
         if (edesc == null) {
             edesc = new PropertyDescriptorImpl(prop);
+            addGroupConvertion(prop, edesc);
             prop.putFeature(Jsr303Features.Property.PropertyDescriptor, edesc);
         }
         return edesc;
@@ -113,24 +227,591 @@ public class BeanDescriptorImpl extends 
 
     /**
      * {@inheritDoc}
-     * 
+     *
      * @return the property descriptors having at least a constraint defined
      */
     public Set<PropertyDescriptor> getConstrainedProperties() {
         Set<PropertyDescriptor> validatedProperties = new HashSet<PropertyDescriptor>();
         for (MetaProperty prop : metaBean.getProperties()) {
             if (prop.getValidations().length > 0
-                || (prop.getMetaBean() != null || prop.getFeature(Features.Property.REF_CASCADE) != null)) {
+                    || (prop.getMetaBean() != null || prop.getFeature(Features.Property.REF_CASCADE) != null)) {
                 validatedProperties.add(getPropertyDescriptor(prop));
             }
         }
         return Collections.unmodifiableSet(validatedProperties);
     }
 
+    public MethodDescriptor getConstraintsForMethod(String methodName, Class<?>... parameterTypes) {
+        if (methodName == null) {
+            throw new IllegalArgumentException("Method name can't be null");
+        }
+
+        Class<?> beanClass = metaBean.getBeanClass();
+        Method method = null;
+        do {
+            try {
+                method = beanClass.getDeclaredMethod(methodName, parameterTypes);
+                break;
+            } catch (final NoSuchMethodException e) {
+                // no-op
+            }
+            beanClass = beanClass.getSuperclass();
+        } while (beanClass != Object.class && beanClass != null);
+        if (method == null) {
+            return null;
+        }
+
+        final MethodDescriptor descriptor = methodConstraints.get(method);
+        if (descriptor != null) {
+            final boolean hasConstraint = descriptor.hasConstrainedParameters() || descriptor.hasConstrainedReturnValue();
+            if (!hasConstraint) {
+                return null;
+            }
+            return descriptor;
+        }
+
+        // TODO: surely remove it
+        for (final MetaMethod metaMethod : metaBean.getMethods()) {
+            if (metaMethod.getMethod().equals(method)) {
+                final MethodDescriptorImpl methodDescriptor = createMethodDescriptor(metaMethod);
+                ensureNotNullDescriptors(metaMethod.getMethod().getReturnType(), methodDescriptor);
+                methodConstraints.put(method, methodDescriptor);
+                containedMethods.add(methodDescriptor);
+                return methodDescriptor;
+            }
+        }
+
+        return null;
+    }
+
+    private MethodDescriptorImpl createMethodDescriptor(final MetaMethod metaMethod) {
+        MethodDescriptorImpl edesc = metaMethod.getFeature(Jsr303Features.Method.MethodDescriptor);
+        if (edesc == null) {
+            edesc = new MethodDescriptorImpl(metaBean, metaMethod);
+            metaMethod.putFeature(Jsr303Features.Method.MethodDescriptor, edesc);
+        }
+        return edesc;
+    }
+
+    public Set<MethodDescriptor> getConstrainedMethods(MethodType methodType, MethodType... methodTypes) {
+        final Set<MethodDescriptor> desc = new HashSet<MethodDescriptor>();
+        desc.addAll(filter(containedMethods, methodType));
+        if (methodTypes != null) {
+            for (final MethodType type : methodTypes) {
+                desc.addAll(filter(containedMethods, type));
+            }
+        }
+        return desc;
+    }
+
+    private static Collection<MethodDescriptor> filter(final Set<MethodDescriptor> containedMethods, final MethodType type) {
+        final Collection<MethodDescriptor> list = new ArrayList<MethodDescriptor>();
+        for (final MethodDescriptor d : containedMethods) {
+            final boolean getter = d.getName().startsWith("get") && d.getParameterDescriptors().isEmpty();
+
+            switch (type) {
+                case GETTER:
+                    if (getter) {
+                        list.add(d);
+                    }
+                break;
+
+                case NON_GETTER:
+                    if (!getter) {
+                        list.add(d);
+                    }
+            }
+        }
+        return list;
+    }
+
+    public ConstructorDescriptor getConstraintsForConstructor(Class<?>... parameterTypes) {
+        final Constructor<?> declaredConstructor;
+        try {
+            declaredConstructor = metaBean.getBeanClass().getDeclaredConstructor(parameterTypes);
+        } catch (final NoSuchMethodException e) {
+            return null;
+        }
+
+        final ConstructorDescriptor descriptor = contructorConstraints.get(declaredConstructor);
+        if (descriptor != null && (descriptor.hasConstrainedParameters() || descriptor.hasConstrainedReturnValue())) {
+            return descriptor;
+        }
+
+        return null;
+    }
+
+    public Set<ConstructorDescriptor> getConstrainedConstructors() {
+        return constrainedConstructors;
+    }
+
     /**
      * {@inheritDoc}
      */
     public String toString() {
         return "BeanDescriptorImpl{" + "returnType=" + elementClass + '}';
     }
+
+    public void buildExecutableDescriptors() {
+        try {
+            buildMethodConstraints();
+            setConstrained(containedMethods, methodConstraints.values());
+
+            buildConstructorConstraints();
+            setConstrained(constrainedConstructors, contructorConstraints.values());
+        } catch (final Exception ex) {
+            if (RuntimeException.class.isInstance(ex)) {
+                throw RuntimeException.class.cast(ex);
+            }
+
+            throw new IllegalArgumentException(ex.getMessage(), ex);
+        }
+    }
+
+    private <A extends ExecutableDescriptor> void setConstrained(final Set<A> dest, final Collection<A> src) {
+        for (final A d : src) {
+            if (d.hasConstrainedParameters() || d.hasConstrainedReturnValue()) {
+                dest.add(d);
+            }
+        }
+    }
+
+    private void buildConstructorConstraints() throws InvocationTargetException, IllegalAccessException {
+        for (final Constructor<?> cons : SecureActions.getDeclaredConstructors(getMetaBean().getBeanClass()).run()) {
+            final ConstructorDescriptorImpl consDesc = new ConstructorDescriptorImpl(getMetaBean(), new Validation[0]);
+            contructorConstraints.put(cons, consDesc);
+
+            final List<String> names = factoryContext.getParameterNameProvider().getParameterNames(cons);
+            final boolean isInnerClass = cons.getDeclaringClass().getEnclosingClass() != null && !Modifier.isStatic(cons.getDeclaringClass().getModifiers());
+
+            {
+                final Annotation[][] paramsAnnos = cons.getParameterAnnotations();
+
+                int idx = 0;
+                if (isInnerClass) { // paramsAnnos.length = parameterTypes.length - 1 in this case
+                    final ParameterDescriptorImpl paramDesc = new ParameterDescriptorImpl(getMetaBean(), new Validation[0], names.get(idx));
+                    consDesc.getParameterDescriptors().add(paramDesc);
+                    idx++;
+                }
+
+                for (final Annotation[] paramAnnos : paramsAnnos) {
+                    if (factoryContext.getFactory().getAnnotationIgnores().isIgnoreAnnotationOnParameter(cons, idx)) {
+                        consDesc.getParameterDescriptors().add(new ParameterDescriptorImpl(metaBean, new Validation[0], names.get(idx)));
+                    } else if (cons.getParameterTypes().length > idx) {
+                        ParameterAccess access = new ParameterAccess(cons.getParameterTypes()[idx], idx);
+                        consDesc.addValidations(processAnnotations(consDesc, paramAnnos, access, idx, names.get(idx)).getValidations());
+                    } // else anonymous class so that's fine
+                    idx++;
+                }
+
+                if (!factoryContext.getFactory().getAnnotationIgnores().isIgnoreAnnotations(cons)) {
+                    for (final Annotation anno : cons.getAnnotations()) {
+                        if (!Valid.class.isInstance(anno)) {
+                            processAnnotations(null, consDesc, cons.getDeclaringClass(), anno);
+                        } else {
+                            consDesc.setCascaded(true);
+                        }
+                    }
+                }
+            }
+
+            if (factoryContext.getFactory().getAnnotationIgnores().isIgnoreAnnotationOnCrossParameter(cons) && consDesc.getCrossParameterDescriptor() != null) {
+                consDesc.setCrossParameterDescriptor(null);
+            }
+            if (factoryContext.getFactory().getAnnotationIgnores().isIgnoreAnnotationOnReturn(cons) && consDesc.getReturnValueDescriptor() != null) {
+                consDesc.setReturnValueDescriptor(null);
+            }
+
+            final MetaConstructor metaConstructor = metaBean.getConstructor(cons);
+            if (metaConstructor != null) {
+                for (final Annotation anno : metaConstructor.getAnnotations()) {
+                    if (!Valid.class.isInstance(anno)) {
+                        processAnnotations(null, consDesc, cons.getDeclaringClass(), anno);
+                    } else {
+                        consDesc.setCascaded(true);
+                    }
+                }
+
+                // parameter validations
+                final Collection<MetaParameter> paramsAnnos = metaConstructor.getParameters();
+                for (final MetaParameter paramAnnos : paramsAnnos) {
+                    final int idx = paramAnnos.getIndex();
+                    final ParameterAccess access = new ParameterAccess(cons.getParameterTypes()[idx], idx);
+                    processAnnotations(consDesc, paramAnnos.getAnnotations(), access, idx, names.get(idx));
+                }
+            }
+
+            if (!consDesc.getGroupConversions().isEmpty() && !consDesc.isCascaded()) {
+                throw new ConstraintDeclarationException("@Valid is needed to define a group conversion");
+            }
+
+            ensureNotNullDescriptors(cons.getDeclaringClass(), consDesc);
+        }
+    }
+
+    private void ensureNotNullDescriptors(final Class<?> returnType, final InvocableElementDescriptor consDesc) {
+        // can't be null
+        if (consDesc.getCrossParameterDescriptor() == null) {
+            consDesc.setCrossParameterDescriptor(new CrossParameterDescriptorImpl(getMetaBean(), NO_CONSTRAINTS));
+        }
+        if (consDesc.getReturnValueDescriptor() == null) {
+            consDesc.setReturnValueDescriptor(new ReturnValueDescriptorImpl(getMetaBean(), returnType, NO_CONSTRAINTS, consDesc.isCascaded()));
+        }
+        // enforce it since ReturnValueDescriptor can be created before cascaded is set to true
+        final ReturnValueDescriptorImpl returnValueDescriptor = ReturnValueDescriptorImpl.class.cast(consDesc.getReturnValueDescriptor());
+        returnValueDescriptor.setCascaded(consDesc.isCascaded());
+        if (returnValueDescriptor.getGroupConversions().isEmpty()) {
+            // loop to not forget to map calling addGroupConversion()
+            for (final GroupConversionDescriptor c : consDesc.getGroupConversions()) {
+                returnValueDescriptor.addGroupConversion(c);
+            }
+        }
+
+    }
+
+    private void processAnnotations(final Method mtd, final InvocableElementDescriptor consDesc, final Class<?> clazz, final Annotation anno) throws InvocationTargetException, IllegalAccessException {
+        if (mtd == null || !factoryContext.getFactory().getAnnotationIgnores().isIgnoreAnnotationOnReturn(mtd)) {
+            final ReturnAccess returnAccess = new ReturnAccess(clazz);
+            final AppendValidationToList validations = new AppendValidationToList();
+            processAnnotation(anno, consDesc, returnAccess, validations);
+            final List<ConstraintValidation<?>> list = removeFromListValidationAppliesTo(validations.getValidations(), ConstraintTarget.PARAMETERS);
+            consDesc.addValidations(list);
+
+            ReturnValueDescriptorImpl returnValueDescriptor = ReturnValueDescriptorImpl.class.cast(consDesc.getReturnValueDescriptor());
+            if (consDesc.getReturnValueDescriptor() != null) {
+                returnValueDescriptor.getMutableConstraintDescriptors().addAll(list);
+            } else {
+                returnValueDescriptor = new ReturnValueDescriptorImpl(getMetaBean(), clazz, list, consDesc.isCascaded());
+                consDesc.setReturnValueDescriptor(returnValueDescriptor);
+            }
+        }
+
+        if (mtd == null || !factoryContext.getFactory().getAnnotationIgnores().isIgnoreAnnotationOnCrossParameter(mtd)) {
+            final ParametersAccess parametersAccess = new ParametersAccess();
+            final AppendValidationToList validations = new AppendValidationToList();
+            processAnnotation(anno, consDesc, parametersAccess, validations);
+            final List<ConstraintValidation<?>> list = removeFromListValidationAppliesTo(validations.getValidations(), ConstraintTarget.RETURN_VALUE);
+            consDesc.addValidations(list);
+            if (consDesc.getCrossParameterDescriptor() != null) {
+                CrossParameterDescriptorImpl.class.cast(consDesc.getCrossParameterDescriptor()).getMutableConstraintDescriptors().addAll(list);
+            } else {
+                consDesc.setCrossParameterDescriptor(new CrossParameterDescriptorImpl(getMetaBean(), list));
+            }
+        }
+    }
+
+    private static List<ConstraintValidation<?>> removeFromListValidationAppliesTo(final List<ConstraintValidation<?>> validations, final ConstraintTarget constraint) {
+        final Iterator<ConstraintValidation<?>> i = validations.iterator();
+        while (i.hasNext()) {
+            if (constraint.equals(i.next().getValidationAppliesTo())) {
+                i.remove();
+            }
+        }
+        return validations;
+    }
+
+    private void buildMethodConstraints() throws InvocationTargetException, IllegalAccessException {
+
+        Class<?> current = getMetaBean().getBeanClass();
+        do {
+            for (final Method method : current.getDeclaredMethods()) {
+                final boolean getter = method.getName().startsWith("get") && method.getParameterTypes().length == 0;
+
+                final MethodDescriptorImpl methodDesc = new MethodDescriptorImpl(getMetaBean(), new Validation[0], method);
+                methodConstraints.put(method, methodDesc);
+
+                final List<Class<?>> classHierarchy = new ArrayList<Class<?>>();
+                ClassHelper.fillFullClassHierarchyAsList(classHierarchy, current);
+                classHierarchy.remove(current);
+
+                final Collection<Method> parents = new ArrayList<Method>();
+                for (final Class<?> clazz : classHierarchy) {
+                    final Method overriden = SecureActions.getDeclaredMethod(clazz, method.getName(), method.getParameterTypes()).run();
+                    if (overriden != null) {
+                        processMethod(overriden, methodDesc);
+                        parents.add(overriden);
+                    }
+                }
+
+                processMethod(method, methodDesc);
+
+                ensureNotNullDescriptors(method.getReturnType(), methodDesc);
+
+                // validations, TODO: if (!factoryContext.getFactory().getAnnotationIgnores().isIgnoreAnnotations(method)) {
+                if (parents != null) {
+                    if (parents.size() > 1) {
+                        for (final Method parent : parents) {
+                            final InvocableElementDescriptor elementDescriptor = InvocableElementDescriptor.class.cast(factoryContext.getValidator().getConstraintsForClass(parent.getDeclaringClass()).getConstraintsForMethod(parent.getName(), parent.getParameterTypes()));
+                            if (elementDescriptor != null) {
+                                ensureNoParameterConstraint(elementDescriptor, "Parameter constraints can't be defined for parallel interfaces/parents");
+                            } else {
+                                ensureMethodDoesntDefineParameterConstraint(methodDesc);
+                            }
+                            ensureNoReturnValueAddedInChild(methodDesc.getReturnValueDescriptor(), parent, "Return value constraints should be the same for parent and children");
+                        }
+                    } else if (!parents.isEmpty()) {
+                        final Method parent = parents.iterator().next();
+                        ensureNoReturnValueAddedInChild(methodDesc.getReturnValueDescriptor(), parent, "Return value constraints should be at least the same for parent and children");
+
+                        final MethodDescriptor parentDesc = factoryContext.getValidator().getConstraintsForClass(parent.getDeclaringClass()).getConstraintsForMethod(parent.getName(), parent.getParameterTypes());
+                        if (parentDesc != null) {
+                            final Iterator<ParameterDescriptor> parentPd = parentDesc.getParameterDescriptors().iterator();
+                            for (final ParameterDescriptor pd : methodDesc.getParameterDescriptors()) {
+                                final ParameterDescriptor next = parentPd.next();
+                                if (pd.getConstraintDescriptors().size() != next.getConstraintDescriptors().size()) {
+                                    throw new ConstraintDeclarationException("child shouldn't get more constraint than parent");
+                                }
+                                if (pd.isCascaded() != next.isCascaded()) { // @Valid
+                                    throw new ConstraintDeclarationException("child shouldn't get more constraint than parent");
+                                }
+                            }
+                        } else {
+                            ensureMethodDoesntDefineParameterConstraint(methodDesc);
+                        }
+                    }
+
+                    final Class<?>[] interfaces = method.getDeclaringClass().getInterfaces();
+                    final Collection<Method> itfWithThisMethod = new ArrayList<Method>();
+                    for (final Class<?> i : interfaces) {
+                        final Method m = SecureActions.getDeclaredMethod(i, method.getName(), method.getParameterTypes()).run();
+                        if (m != null) {
+                            itfWithThisMethod.add(m);
+                        }
+                    }
+                    if (itfWithThisMethod.size() > 1) {
+                        for (final Method m : itfWithThisMethod) {
+                            ensureNoConvertGroup(m, "ConvertGroup can't be used in parallel interfaces");
+                        }
+                    } else if (itfWithThisMethod.size() == 1) {
+                        ensureNoConvertGroup(itfWithThisMethod.iterator().next(), "ConvertGroup can't be used in interface AND parent class");
+                    }
+
+                    int returnValid = 0;
+                    if (method.getAnnotation(Valid.class) != null) {
+                        returnValid++;
+                    }
+                    for (final Class<?> clazz : classHierarchy) {
+                        final Method overriden = SecureActions.getDeclaredMethod(clazz, method.getName(), method.getParameterTypes()).run();
+                        if (overriden != null) {
+                            if (overriden.getAnnotation(Valid.class) != null) {
+                                returnValid++;
+                            }
+                        }
+                    }
+                    if (returnValid > 1 && !(interfaces.length == returnValid && method.getAnnotation(Valid.class) == null)) {
+                        throw new ConstraintDeclarationException("@Valid on returned value can't be set more than once");
+                    }
+                }
+
+                if (getter) {
+                    final MetaProperty prop = metaBean.getProperty(Introspector.decapitalize(method.getName().substring(3)));
+                    if (prop != null && prop.getFeature(Features.Property.REF_CASCADE) != null) {
+                        methodDesc.setCascaded(true);
+                    }
+                }
+
+                if (!methodDesc.getGroupConversions().isEmpty() && !methodDesc.isCascaded()) {
+                    throw new ConstraintDeclarationException("@Valid is needed to define a group conversion");
+                }
+            }
+            current = current.getSuperclass();
+        } while (current != null && current != Object.class);
+    }
+
+    private void ensureMethodDoesntDefineParameterConstraint(MethodDescriptorImpl methodDesc) {
+        for (final ParameterDescriptor pd : methodDesc.getParameterDescriptors()) {
+            if (!pd.getConstraintDescriptors().isEmpty()) {
+                throw new ConstraintDeclarationException("child shouldn't get more constraint than parent");
+            }
+            if (pd.isCascaded()) { // @Valid
+                throw new ConstraintDeclarationException("child shouldn't get more constraint than parent");
+            }
+        }
+    }
+
+    private void ensureNoReturnValueAddedInChild(final ReturnValueDescriptor returnValueDescriptor, final Method parent, final String msg) {
+        final MethodDescriptor parentDesc = factoryContext.getValidator().getConstraintsForClass(parent.getDeclaringClass()).getConstraintsForMethod(parent.getName(), parent.getParameterTypes());
+        if (parentDesc == null) {
+            return;
+        }
+
+        final ReturnValueDescriptor parentReturnDesc = parentDesc.getReturnValueDescriptor();
+        if (parentReturnDesc.isCascaded() && !returnValueDescriptor.isCascaded() || parentReturnDesc.getConstraintDescriptors().size() > returnValueDescriptor.getConstraintDescriptors().size()) {
+            throw new ConstraintDeclarationException(msg);
+        }
+    }
+
+    private static void ensureNoParameterConstraint(final InvocableElementDescriptor constraintsForMethod, final String msg) {
+        for (final ParameterDescriptor parameterDescriptor : constraintsForMethod.getParameterDescriptors()) {
+            if (!parameterDescriptor.getConstraintDescriptors().isEmpty() || parameterDescriptor.isCascaded()) {
+                throw new ConstraintDeclarationException(msg);
+            }
+        }
+    }
+
+    private static void ensureNoConvertGroup(final Method method, final String msg) {
+        for (final Annotation[] annotations : method.getParameterAnnotations()) {
+            for (final Annotation a : annotations) {
+                if (ConvertGroup.class.isInstance(a)) {
+                    throw new ConstraintDeclarationException(msg);
+                }
+            }
+        }
+        if (method.getAnnotation(ConvertGroup.class) != null) {
+            throw new ConstraintDeclarationException(msg);
+        }
+    }
+
+    private void processMethod(Method method, MethodDescriptorImpl methodDesc) throws InvocationTargetException, IllegalAccessException {
+        { // reflection
+            if (!factoryContext.getFactory().getAnnotationIgnores().isIgnoreAnnotations(method)) {
+                // return value validations and/or cross-parameter validation
+                for (Annotation anno : method.getAnnotations()) {
+                    if (anno instanceof Valid || anno instanceof Validate) {
+                        methodDesc.setCascaded(true);
+                    } else {
+                        processAnnotations(method, methodDesc, method.getReturnType(), anno);
+                    }
+                }
+            }
+
+            // parameter validations
+            final Annotation[][] paramsAnnos = method.getParameterAnnotations();
+            int idx = 0;
+            final List<String> names = factoryContext.getParameterNameProvider().getParameterNames(method);
+            for (final Annotation[] paramAnnos : paramsAnnos) {
+                if (!factoryContext.getFactory().getAnnotationIgnores().isIgnoreAnnotationOnParameter(method, idx)) {
+                    final ParameterAccess access = new ParameterAccess(method.getParameterTypes()[idx], idx);
+                    processAnnotations(methodDesc, paramAnnos, access, idx, names.get(idx));
+                } else {
+                    final ParameterDescriptorImpl parameterDescriptor = new ParameterDescriptorImpl(metaBean, new Validation[0], names.get(idx));
+                    parameterDescriptor.setIndex(idx);
+                    methodDesc.getParameterDescriptors().add(parameterDescriptor);
+                }
+                idx++;
+            }
+        }
+
+        if (factoryContext.getFactory().getAnnotationIgnores().isIgnoreAnnotationOnCrossParameter(method) && methodDesc.getCrossParameterDescriptor() != null) {
+            methodDesc.setCrossParameterDescriptor(null);
+        }
+        if (factoryContext.getFactory().getAnnotationIgnores().isIgnoreAnnotationOnReturn(method) && methodDesc.getReturnValueDescriptor() != null) {
+            methodDesc.setReturnValueDescriptor(null);
+        }
+
+        final MetaMethod metaMethod = metaBean.getMethod(method);
+        if (metaMethod != null) {
+            for (final Annotation anno : metaMethod.getAnnotations()) {
+                if (anno instanceof Valid) {
+                    methodDesc.setCascaded(true);
+                } else {
+                    // set first param as null to force it to be read
+                    processAnnotations(null, methodDesc, method.getReturnType(), anno);
+                }
+            }
+
+            // parameter validations
+            final Collection<MetaParameter> paramsAnnos = metaMethod.getParameters();
+            final List<String> names = factoryContext.getParameterNameProvider().getParameterNames(method);
+            for (final MetaParameter paramAnnos : paramsAnnos) {
+                final int idx = paramAnnos.getIndex();
+                final ParameterAccess access = new ParameterAccess(method.getParameterTypes()[idx], idx);
+                processAnnotations(methodDesc, paramAnnos.getAnnotations(), access, idx, names.get(idx));
+            }
+
+        }
+
+    }
+
+    private AppendValidationToList processAnnotations(InvocableElementDescriptor methodDesc, Annotation[] paramAnnos, AccessStrategy access, int idx, String name)
+            throws InvocationTargetException, IllegalAccessException {
+        final AppendValidationToList validations = new AppendValidationToList();
+        boolean cascaded = false;
+
+        Group[] from = null;
+        Group[] to = null;
+
+        for (final Annotation anno : paramAnnos) {
+            if (anno instanceof Valid || anno instanceof Validate) {
+                cascaded = true;
+            } else if (ConvertGroup.class.isInstance(anno)) {
+                final ConvertGroup cg = ConvertGroup.class.cast(anno);
+                from = new Group[]{new Group(cg.from())};
+                to = new Group[]{new Group(cg.to())};
+            } else if (ConvertGroup.List.class.isInstance(anno)) {
+                final ConvertGroup.List cgl = ConvertGroup.List.class.cast(anno);
+                final ConvertGroup[] groups = cgl.value();
+                from = new Group[groups.length];
+                to = new Group[groups.length];
+                for (int i = 0; i < to.length; i++) {
+                    from[i] = new Group(groups[i].from());
+                    to[i] = new Group(groups[i].to());
+                }
+            } else {
+                processAnnotation(anno, methodDesc, access, validations);
+            }
+        }
+
+        ParameterDescriptorImpl paramDesc = null;
+        for (final ParameterDescriptor pd : methodDesc.getParameterDescriptors()) {
+            if (pd.getIndex() == idx) {
+                paramDesc = ParameterDescriptorImpl.class.cast(pd);
+            }
+        }
+
+        if (paramDesc == null) {
+            paramDesc = new ParameterDescriptorImpl(Class.class.cast(access.getJavaType()), // set from getParameterTypes() so that's a Class<?>
+                    validations.getValidations().toArray(new Validation[validations.getValidations().size()]), name);
+            paramDesc.setIndex(idx);
+            methodDesc.getParameterDescriptors().add(paramDesc);
+            paramDesc.setCascaded(cascaded);
+        } else {
+            paramDesc.getMutableConstraintDescriptors().addAll(validations.getValidations());
+            if (cascaded) {
+                paramDesc.setCascaded(true);
+            } // else keep previous config
+        }
+        if (paramDesc.isCascaded() && from != null) {
+            for (int i = 0; i < from.length; i++) {
+                paramDesc.addGroupConversion(new GroupConversionDescriptorImpl(from[i], to[i]));
+            }
+        } else if (from != null) {
+            throw new ConstraintDeclarationException("Group conversion is only relevant for @Valid cases");
+        }
+
+        return validations;
+    }
+
+    private <A extends Annotation> void processAnnotation(A annotation, InvocableElementDescriptor desc,
+                                                          AccessStrategy access, AppendValidation validations) throws InvocationTargetException, IllegalAccessException {
+
+        if (annotation instanceof Valid || annotation instanceof Validate) {
+            desc.setCascaded(true);
+        } else if (ConvertGroup.class.isInstance(annotation) && ReturnAccess.class.isInstance(access)) { // access is just tested to ensure to not read it twice with cross parameter
+            final ConvertGroup cg = ConvertGroup.class.cast(annotation);
+            desc.addGroupConversion(new GroupConversionDescriptorImpl(new Group(cg.from()), new Group(cg.to())));
+        } else if (ConvertGroup.List.class.isInstance(annotation) && ReturnAccess.class.isInstance(access)) {
+            final ConvertGroup.List cgl = ConvertGroup.List.class.cast(annotation);
+            for (final ConvertGroup cg : cgl.value()) {
+                desc.addGroupConversion(new GroupConversionDescriptorImpl(new Group(cg.from()), new Group(cg.to())));
+            }
+        } else {
+            Constraint vcAnno = annotation.annotationType().getAnnotation(Constraint.class);
+            if (vcAnno != null) {
+                annotationProcessor.processAnnotation(annotation, null, ClassUtils.primitiveToWrapper((Class<?>) access.getJavaType()), access, validations, true);
+            } else {
+                /**
+                 * Multi-valued constraints
+                 */
+                if (ConstraintAnnotationAttributes.VALUE.isDeclaredOn(annotation.annotationType())) {
+                    Annotation[] children = ConstraintAnnotationAttributes.VALUE.getValue(annotation);
+                    if (children != null) {
+                        for (Annotation child : children) {
+                            processAnnotation(child, desc, access, validations); // recursion
+                        }
+                    }
+                }
+            }
+        }
+    }
 }

Added: bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/BootstrapConfigurationImpl.java
URL: http://svn.apache.org/viewvc/bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/BootstrapConfigurationImpl.java?rev=1498347&view=auto
==============================================================================
--- bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/BootstrapConfigurationImpl.java (added)
+++ bval/branches/bval-11/bval-jsr303/src/main/java/org/apache/bval/jsr303/BootstrapConfigurationImpl.java Mon Jul  1 10:06:18 2013
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.bval.jsr303;
+
+import javax.validation.BootstrapConfiguration;
+import javax.validation.executable.ExecutableType;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+public class BootstrapConfigurationImpl implements BootstrapConfiguration {
+    private Map<String, String> properties;
+    private Set<ExecutableType> defaultValidatedExecutableTypes;
+    private boolean executableValidationEnabled;
+    private Set<String> constraintMappingResourcePaths;
+    private String parameterNameProviderClassName;
+    private String traversableResolverClassName;
+    private String messageInterpolatorClassName;
+    private String constraintValidatorFactoryClassName;
+    private String defaultProviderClassName;
+
+    public BootstrapConfigurationImpl(final String defaultProviderClassName,
+                                      final String constraintValidatorFactoryClassName,
+                                      final String messageInterpolatorClassName,
+                                      final String traversableResolverClassName,
+                                      final String parameterNameProviderClassName,
+                                      final Set<String> constraintMappingResourcePaths,
+                                      final boolean executableValidationEnabled,
+                                      final Set<ExecutableType> defaultValidatedExecutableTypes,
+                                      final Map<String, String> properties) {
+        this.properties = Collections.unmodifiableMap(properties);
+        this.defaultValidatedExecutableTypes = Collections.unmodifiableSet(defaultValidatedExecutableTypes);
+        this.executableValidationEnabled = executableValidationEnabled;
+        this.constraintMappingResourcePaths = Collections.unmodifiableSet(constraintMappingResourcePaths);
+        this.parameterNameProviderClassName = parameterNameProviderClassName;
+        this.traversableResolverClassName = traversableResolverClassName;
+        this.messageInterpolatorClassName = messageInterpolatorClassName;
+        this.constraintValidatorFactoryClassName = constraintValidatorFactoryClassName;
+        this.defaultProviderClassName = defaultProviderClassName;
+    }
+
+    public String getDefaultProviderClassName() {
+        return defaultProviderClassName;
+    }
+
+    public String getConstraintValidatorFactoryClassName() {
+        return constraintValidatorFactoryClassName;
+    }
+
+    public String getMessageInterpolatorClassName() {
+        return messageInterpolatorClassName;
+    }
+
+    public String getTraversableResolverClassName() {
+        return traversableResolverClassName;
+    }
+
+    public String getParameterNameProviderClassName() {
+        return parameterNameProviderClassName;
+    }
+
+    public Set<String> getConstraintMappingResourcePaths() {
+        return constraintMappingResourcePaths;
+    }
+
+    public boolean isExecutableValidationEnabled() {
+        return executableValidationEnabled;
+    }
+
+    public Set<ExecutableType> getDefaultValidatedExecutableTypes() {
+        return defaultValidatedExecutableTypes;
+    }
+
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+}