bval-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From rmannibu...@apache.org
Subject svn commit: r1517540 [5/15] - in /bval/branches/bval-11/bval-jsr: ./ src/ src/main/ src/main/appended-resources/ src/main/appended-resources/META-INF/ src/main/java/ src/main/java/org/ src/main/java/org/apache/ src/main/java/org/apache/bval/ src/main/j...
Date Mon, 26 Aug 2013 13:59:20 GMT
Added: bval/branches/bval-11/bval-jsr/src/main/java/org/apache/bval/jsr/ClassValidator.java
URL: http://svn.apache.org/viewvc/bval/branches/bval-11/bval-jsr/src/main/java/org/apache/bval/jsr/ClassValidator.java?rev=1517540&view=auto
==============================================================================
--- bval/branches/bval-11/bval-jsr/src/main/java/org/apache/bval/jsr/ClassValidator.java (added)
+++ bval/branches/bval-11/bval-jsr/src/main/java/org/apache/bval/jsr/ClassValidator.java Mon Aug 26 13:59:15 2013
@@ -0,0 +1,1345 @@
+/*
+ * 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.jsr;
+
+import org.apache.bval.DynamicMetaBean;
+import org.apache.bval.MetaBeanFinder;
+import org.apache.bval.jsr.groups.Group;
+import org.apache.bval.jsr.groups.Groups;
+import org.apache.bval.jsr.groups.GroupsComputer;
+import org.apache.bval.jsr.util.ClassHelper;
+import org.apache.bval.jsr.util.NodeImpl;
+import org.apache.bval.jsr.util.PathImpl;
+import org.apache.bval.jsr.util.PathNavigation;
+import org.apache.bval.jsr.util.Proxies;
+import org.apache.bval.jsr.util.ValidationContextTraversal;
+import org.apache.bval.model.Features;
+import org.apache.bval.model.FeaturesCapable;
+import org.apache.bval.model.MetaBean;
+import org.apache.bval.model.MetaProperty;
+import org.apache.bval.model.Validation;
+import org.apache.bval.util.AccessStrategy;
+import org.apache.bval.util.ValidationHelper;
+import org.apache.commons.lang3.ArrayUtils;
+import org.apache.commons.lang3.ClassUtils;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.reflect.TypeUtils;
+
+import javax.validation.ConstraintDeclarationException;
+import javax.validation.ConstraintDefinitionException;
+import javax.validation.ConstraintTarget;
+import javax.validation.ConstraintViolation;
+import javax.validation.ElementKind;
+import javax.validation.ValidationException;
+import javax.validation.executable.ExecutableValidator;
+import javax.validation.groups.Default;
+import javax.validation.metadata.BeanDescriptor;
+import javax.validation.metadata.ConstraintDescriptor;
+import javax.validation.metadata.ElementDescriptor;
+import javax.validation.metadata.ParameterDescriptor;
+import javax.validation.metadata.PropertyDescriptor;
+import java.lang.reflect.Constructor;
+import java.lang.reflect.Member;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+// TODO: centralize treatMapsLikeBeans
+
+/**
+ * Objects of this class are able to validate bean instances (and the associated object graphs).
+ * <p/>
+ * Implementation is thread-safe.
+ * <p/>
+ * API class
+ *
+ * @version $Rev: 1514672 $ $Date: 2013-08-16 14:15:12 +0200 (ven., 16 août 2013) $
+ * 
+ * @author Roman Stumm
+ * @author Carlos Vara
+ */
+public class ClassValidator implements CascadingPropertyValidator, ExecutableValidator {
+    private static final Object VALIDATE_PROPERTY = new Object() {
+        public String toString() {
+            return "VALIDATE_PROPERTY";
+        }
+    };
+
+    /**
+     * {@link ApacheFactoryContext} used
+     */
+    protected final ApacheFactoryContext factoryContext;
+
+    /**
+     * {@link GroupsComputer} used
+     */
+    protected final GroupsComputer groupsComputer = new GroupsComputer();
+
+    private final MetaBeanFinder metaBeanFinder;
+
+    /**
+     * Create a new ClassValidator instance.
+     *
+     * @param factoryContext
+     */
+    public ClassValidator(ApacheFactoryContext factoryContext) {
+        this.factoryContext = factoryContext;
+        metaBeanFinder = factoryContext.getMetaBeanFinder();
+    }
+
+    // Validator implementation
+    // --------------------------------------------------
+
+    /**
+     * {@inheritDoc} Validates all constraints on <code>object</code>.
+     *
+     * @param object object to validate
+     * @param groups group or list of groups targeted for validation (default to
+     *               {@link javax.validation.groups.Default})
+     * @return constraint violations or an empty Set if none
+     * @throws IllegalArgumentException if object is null or if null is passed to the varargs groups
+     * @throws ValidationException      if a non recoverable error happens during the validation
+     *                                  process
+     */
+    // @Override - not allowed in 1.5 for Interface methods
+    @SuppressWarnings("unchecked")
+    public <T> Set<ConstraintViolation<T>> validate(T object, Class<?>... groups) {
+        if (object == null) {
+            throw new IllegalArgumentException("cannot validate null");
+        }
+        checkGroups(groups);
+
+        try {
+
+            final Class<T> objectClass = (Class<T>) object.getClass();
+            final MetaBean objectMetaBean = metaBeanFinder.findForClass(objectClass);
+            final GroupValidationContext<T> context = createContext(objectMetaBean, object, objectClass, groups);
+            return validateBeanWithGroups(context, context.getGroups());
+        } catch (final RuntimeException ex) {
+            throw unrecoverableValidationError(ex, object);
+        }
+    }
+
+    private <T> Set<ConstraintViolation<T>> validateBeanWithGroups(final GroupValidationContext<T> context, final Groups sequence) {
+        final ConstraintValidationListener<T> result = context.getListener();
+
+        // 1. process groups
+        for (final Group current : sequence.getGroups()) {
+            context.setCurrentGroup(current);
+            validateBeanNet(context);
+        }
+
+        // 2. process sequences
+        for (final List<Group> eachSeq : sequence.getSequences()) {
+            for (final Group current : eachSeq) {
+                context.setCurrentGroup(current);
+                validateBeanNet(context);
+                // if one of the group process in the sequence leads to one
+                // or more validation failure,
+                // the groups following in the sequence must not be
+                // processed
+                if (!result.isEmpty()) {
+                    break;
+                }
+            }
+            if (!result.isEmpty()) {
+                break;
+            }
+        }
+        return result.getConstraintViolations();
+    }
+
+    /**
+     * {@inheritDoc} Validates all constraints placed on the property of <code>object</code> named
+     * <code>propertyName</code>.
+     *
+     * @param object       object to validate
+     * @param propertyName property to validate (ie field and getter constraints). Nested
+     *                     properties may be referenced (e.g. prop[2].subpropA.subpropB)
+     * @param groups       group or list of groups targeted for validation (default to
+     *                     {@link javax.validation.groups.Default})
+     * @return constraint violations or an empty Set if none
+     * @throws IllegalArgumentException if <code>object</code> is null, if <code>propertyName</code>
+     *                                  null, empty or not a valid object property or if null is
+     *                                  passed to the varargs groups
+     * @throws ValidationException      if a non recoverable error happens during the validation
+     *                                  process
+     */
+    // @Override - not allowed in 1.5 for Interface methods
+    public <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, Class<?>... groups) {
+        return validateProperty(object, propertyName, false, groups);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public <T> Set<ConstraintViolation<T>> validateProperty(T object, String propertyName, boolean cascade,
+        Class<?>... groups) {
+
+        if (object == null)
+            throw new IllegalArgumentException("cannot validate null");
+
+        @SuppressWarnings("unchecked")
+        Set<ConstraintViolation<T>> result =
+            validateValueImpl((Class<T>) object.getClass(), object, propertyName, VALIDATE_PROPERTY, cascade, groups);
+        return result;
+    }
+
+    /**
+     * {@inheritDoc} Validates all constraints placed on the property named <code>propertyName</code> of the class
+     * <code>beanType</code> would the property value be <code>value</code>
+     * <p/>
+     * <code>ConstraintViolation</code> objects return null for {@link ConstraintViolation#getRootBean()} and
+     * {@link ConstraintViolation#getLeafBean()}
+     *
+     * @param beanType     the bean type
+     * @param propertyName property to validate
+     * @param value        property value to validate
+     * @param groups       group or list of groups targeted for validation (default to
+     *                     {@link javax.validation.groups.Default})
+     * @return constraint violations or an empty Set if none
+     * @throws IllegalArgumentException if <code>beanType</code> is null, if
+     *                                  <code>propertyName</code> null, empty or not a valid object
+     *                                  property or if null is passed to the varargs groups
+     * @throws ValidationException      if a non recoverable error happens during the validation
+     *                                  process
+     */
+    // @Override - not allowed in 1.5 for Interface methods
+    public <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value,
+        Class<?>... groups) {
+        return validateValue(beanType, propertyName, value, false, groups);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public <T> Set<ConstraintViolation<T>> validateValue(Class<T> beanType, String propertyName, Object value,
+        boolean cascade, Class<?>... groups) {
+        return validateValueImpl(checkBeanType(beanType), null, propertyName, value, cascade, groups);
+    }
+
+    /**
+     * {@inheritDoc} Return the descriptor object describing bean constraints. The returned object (and associated
+     * objects including <code>ConstraintDescriptor<code>s) are immutable.
+     *
+     * @param clazz class or interface type evaluated
+     * @return the bean descriptor for the specified class.
+     * @throws IllegalArgumentException if clazz is null
+     * @throws ValidationException      if a non recoverable error happens during the metadata
+     *                                  discovery or if some constraints are invalid.
+     */
+    // @Override - not allowed in 1.5 for Interface methods
+    public BeanDescriptor getConstraintsForClass(final Class<?> clazz) {
+        if (clazz == null) {
+            throw new IllegalArgumentException("Class cannot be null");
+        }
+        try {
+            final MetaBean metaBean = metaBeanFinder.findForClass(clazz); // don't throw an exception because of a missing validator here
+            BeanDescriptorImpl edesc = metaBean.getFeature(JsrFeatures.Bean.BEAN_DESCRIPTOR);
+            if (edesc == null) {
+                edesc = metaBean.initFeature(JsrFeatures.Bean.BEAN_DESCRIPTOR, createBeanDescriptor(metaBean));
+            }
+            return edesc;
+        } catch (final ConstraintDefinitionException definitionEx) {
+            throw definitionEx;
+        } catch (final ConstraintDeclarationException declarationEx) {
+            throw declarationEx;
+        } catch (final RuntimeException ex) {
+            throw new ValidationException("error retrieving constraints for " + clazz, ex);
+        }
+    }
+
+    /**
+     * {@inheritDoc} Return an instance of the specified type allowing access to provider-specific APIs. If the Bean
+     * Validation provider implementation does not support the specified class, <code>ValidationException</code> is
+     * thrown.
+     *
+     * @param type the class of the object to be returned.
+     * @return an instance of the specified class
+     * @throws ValidationException if the provider does not support the call.
+     */
+    // @Override - not allowed in 1.5 for Interface methods
+    public <T> T unwrap(Class<T> type) {
+        // FIXME 2011-03-27 jw:
+        // This code is unsecure.
+        // It should allow only a fixed set of classes.
+        // Can't fix this because don't know which classes this method should support.
+
+        if (type.isAssignableFrom(getClass())) {
+            return (T) this;
+        } else if (!(type.isInterface() || Modifier.isAbstract(type.getModifiers()))) {
+            return newInstance(type);
+        } else {
+            try {
+                Class<?> cls = ClassUtils.getClass(type.getName() + "Impl");
+                if (type.isAssignableFrom(cls)) {
+                    @SuppressWarnings("unchecked")
+                    final Class<? extends T> implClass = (Class<? extends T>) cls;
+                    return newInstance(implClass);
+                }
+            } catch (ClassNotFoundException e) {
+            }
+            throw new ValidationException("Type " + type + " not supported");
+        }
+    }
+
+    public ExecutableValidator forExecutables() {
+        return this;
+    }
+
+    private <T> T newInstance(final Class<T> cls) {
+        if (System.getSecurityManager() == null) {
+            return doNewInstance(cls);
+        }
+        return AccessController.doPrivileged(new PrivilegedAction<T>() {
+            public T run() {
+                return doNewInstance(cls);
+            }
+        });
+    }
+
+    private <T> T doNewInstance(final Class<T> cls) {
+        try {
+            Constructor<T> cons = cls.getConstructor(ApacheFactoryContext.class);
+            if (!cons.isAccessible()) {
+                cons.setAccessible(true);
+            }
+            return cons.newInstance(factoryContext);
+        } catch (final Exception ex) {
+            throw new ValidationException("Cannot instantiate : " + cls, ex);
+        }
+    }
+
+    // Helpers
+    // -------------------------------------------------------------------
+
+    /**
+     * Validates a bean and all its cascaded related beans for the currently defined group.
+     * <p/>
+     * Special code is present to manage the {@link Default} group.
+     *
+     * @param context The current context of this validation call. Must have its
+     *                          {@link GroupValidationContext#getCurrentGroup()} field set.
+     */
+    protected void validateBeanNet(GroupValidationContext<?> context) {
+
+        // If reached a cascaded bean which is null
+        if (context.getBean() == null) {
+            return;
+        }
+
+        // If reached a cascaded bean which has already been validated for the
+        // current group
+        if (!context.collectValidated()) {
+            return;
+        }
+
+        // ### First, validate the bean
+
+        // Default is a special case
+        if (context.getCurrentGroup().isDefault()) {
+
+            List<Group> defaultGroups = expandDefaultGroup(context);
+            final ConstraintValidationListener<?> result = context.getListener();
+
+            // If the rootBean defines a GroupSequence
+            if (defaultGroups != null && defaultGroups.size() > 1) {
+
+                int numViolations = result.violationsSize();
+
+                // Validate the bean for each group in the sequence
+                final Group currentGroup = context.getCurrentGroup();
+                for (final Group each : defaultGroups) {
+                    context.setCurrentGroup(each);
+
+                    // ValidationHelper.validateBean(context);, doesn't match anymore because of @ConvertGroup
+                    validateBean(context);
+
+                    // Spec 3.4.3 - Stop validation if errors already found
+                    if (result.violationsSize() > numViolations) {
+                        break;
+                    }
+                }
+                context.setCurrentGroup(currentGroup);
+            } else {
+
+                // For each class in the hierarchy of classes of rootBean,
+                // validate the constraints defined in that class according
+                // to the GroupSequence defined in the same class
+
+                // Obtain the full class hierarchy
+                final List<Class<?>> classHierarchy = new ArrayList<Class<?>>();
+                ClassHelper.fillFullClassHierarchyAsList(classHierarchy, context.getMetaBean().getBeanClass());
+                final Class<?> initialOwner = context.getCurrentOwner();
+
+                // For each owner in the hierarchy
+                for (final Class<?> owner : classHierarchy) {
+                    context.setCurrentOwner(owner);
+
+                    int numViolations = result.violationsSize();
+
+                    // Obtain the group sequence of the owner, and use it for
+                    // the constraints that belong to it
+                    final List<Group> ownerDefaultGroups = context.getMetaBean().getFeature("{GroupSequence:" + owner.getCanonicalName() + "}");
+                    for (Group each : ownerDefaultGroups) {
+                        context.setCurrentGroup(each);
+                        validateBean(context);
+                        // Spec 3.4.3 - Stop validation if errors already found
+                        if (result.violationsSize() > numViolations) {
+                            break;
+                        }
+                    }
+
+                }
+                context.setCurrentOwner(initialOwner);
+                context.setCurrentGroup(Group.DEFAULT);
+
+            }
+
+        }
+        // if not the default group, proceed as normal
+        else {
+            validateBean(context);
+        }
+
+        // ### Then, the cascaded beans (@Valid)
+        for (final MetaProperty prop : context.getMetaBean().getProperties()) {
+            final Group group = context.getCurrentGroup();
+            final Group mappedGroup;
+
+            final Object feature = prop.getFeature(JsrFeatures.Property.PropertyDescriptor);
+            if (feature != null) {
+                mappedGroup = PropertyDescriptorImpl.class.cast(feature).mapGroup(group);
+            } else {
+                mappedGroup = group;
+            }
+
+
+            if (group != mappedGroup) {
+                final Groups propertyGroup = groupsComputer.computeGroups(new Class<?>[]{ mappedGroup.getGroup() });
+                validateCascadedBean(context, prop, propertyGroup);
+            } else {
+                validateCascadedBean(context, prop, null);
+            }
+
+            context.setCurrentGroup(group);
+        }
+
+    }
+
+    // TODO: maybe add a GroupMapper to bval-core to ease this kind of thing and void to fork this method from ValidationHelper
+    private void validateBean(final GroupValidationContext<?> context) {
+        // execute all property level validations
+        for (final PropertyDescriptor prop : getConstraintsForClass(context.getMetaBean().getBeanClass()).getConstrainedProperties()) {
+            final PropertyDescriptorImpl impl = PropertyDescriptorImpl.class.cast(prop);
+            if (!impl.isValidated(impl)) {
+                checkValidationAppliesTo(impl.getConstraintDescriptors(), ConstraintTarget.PARAMETERS);
+                checkValidationAppliesTo(impl.getConstraintDescriptors(), ConstraintTarget.RETURN_VALUE);
+                impl.setValidated(impl); // we don't really care about concurrency here
+            }
+
+            final MetaProperty metaProperty = context.getMetaBean().getProperty(prop.getPropertyName());
+            context.setMetaProperty(metaProperty);
+            final Group current = context.getCurrentGroup();
+            context.setCurrentGroup(impl.mapGroup(current));
+            ValidationHelper.validateProperty(context);
+            context.setCurrentGroup(current);
+        }
+
+        // execute all bean level validations
+        context.setMetaProperty(null);
+        for (final Validation validation : context.getMetaBean().getValidations()) {
+            if (ConstraintValidation.class.isInstance(validation)) {
+                final ConstraintValidation constraintValidation = ConstraintValidation.class.cast(validation);
+                if (!constraintValidation.isValidated()) {
+                    checkValidationAppliesTo(constraintValidation.getValidationAppliesTo(), ConstraintTarget.PARAMETERS);
+                    checkValidationAppliesTo(constraintValidation.getValidationAppliesTo(), ConstraintTarget.RETURN_VALUE);
+                    constraintValidation.setValidated(true);
+                }
+            }
+            validation.validate(context);
+        }
+    }
+
+    /**
+     * Checks if the the meta property <code>prop</code> defines a cascaded bean, and in case it does, validates it.
+     *
+     * @param context The current validation context.
+     * @param prop    The property to cascade from (in case it is possible).
+     */
+    private void validateCascadedBean(final GroupValidationContext<?> context, final MetaProperty prop, final Groups groups) {
+        AccessStrategy[] access = prop.getFeature(Features.Property.REF_CASCADE);
+        if (access != null) { // different accesses to relation
+            // save old values from context
+            final Object bean = context.getBean();
+            final MetaBean mbean = context.getMetaBean();
+            // TODO implement Validation.groups support on related bean
+//            Class[] groups = prop.getFeature(JsrFeatures.Property.REF_GROUPS);
+            for (final AccessStrategy each : access) {
+                if (isCascadable(context, prop, each)) {
+                    // modify context state for relationship-target bean
+                    context.moveDown(prop, each);
+                    // validate
+                    if (groups == null) {
+                        ValidationHelper.validateContext(context, new JsrValidationCallback(context), treatMapsLikeBeans);
+                    } else {
+                        ValidationHelper.validateContext(context, new ValidationHelper.ValidateCallback() {
+                            public void validate() {
+                                validateBeanWithGroups(context, groups);
+                            }
+                        }, treatMapsLikeBeans);
+                    }
+                    // restore old values in context
+                    context.moveUp(bean, mbean);
+                }
+            }
+        }
+    }
+
+    /**
+     * Before accessing a related bean (marked with {@link javax.validation.Valid}), the validator has to check if it is
+     * reachable and cascadable.
+     *
+     * @param context The current validation context.
+     * @param prop    The property of the related bean.
+     * @param access  The access strategy used to get the related bean value.
+     * @return <code>true</code> if the validator can access the related bean, <code>false</code> otherwise.
+     */
+    private boolean isCascadable(GroupValidationContext<?> context, MetaProperty prop, AccessStrategy access) {
+
+        PathImpl beanPath = context.getPropertyPath();
+        final NodeImpl node = new NodeImpl.PropertyNodeImpl(prop.getName());
+        if (beanPath == null) {
+            beanPath = PathImpl.create();
+        }
+        try {
+            if (!context.getTraversableResolver().isReachable(context.getBean(), node,
+                context.getRootMetaBean().getBeanClass(), beanPath, access.getElementType())) {
+                return false;
+            }
+        } catch (RuntimeException e) {
+            throw new ValidationException("Error in TraversableResolver.isReachable() for " + context.getBean(), e);
+        }
+
+        try {
+            if (!context.getTraversableResolver().isCascadable(context.getBean(), node,
+                context.getRootMetaBean().getBeanClass(), beanPath, access.getElementType()))
+                return false;
+        } catch (RuntimeException e) {
+            throw new ValidationException("Error TraversableResolver.isCascadable() for " + context.getBean(), e);
+        }
+
+        return true;
+    }
+
+    /**
+     * in case of a default group return the list of groups for a redefined default GroupSequence
+     *
+     * @return null when no in default group or default group sequence not redefined
+     */
+    private List<Group> expandDefaultGroup(GroupValidationContext<?> context) {
+        if (context.getCurrentGroup().isDefault()) {
+            // mention if metaBean redefines the default group
+            List<Group> groupSeq = context.getMetaBean().getFeature(JsrFeatures.Bean.GROUP_SEQUENCE);
+            if (groupSeq != null) {
+                context.getGroups().assertDefaultGroupSequenceIsExpandable(groupSeq);
+            }
+            return groupSeq;
+        } else {
+            return null;
+        }
+    }
+
+    /**
+     * Generate an unrecoverable validation error
+     *
+     * @param ex
+     * @param object
+     * @return a {@link RuntimeException} of the appropriate type
+     */
+    @SuppressWarnings("finally")
+    protected static RuntimeException unrecoverableValidationError(RuntimeException ex, Object object) {
+        if (ex instanceof UnknownPropertyException || ex instanceof IncompatiblePropertyValueException) {
+            // Convert to IllegalArgumentException
+            return new IllegalArgumentException(ex.getMessage(), ex);
+        } else if (ex instanceof ValidationException) {
+            return ex; // do not wrap specific ValidationExceptions (or
+            // instances from subclasses)
+        } else {
+            String objectId = "";
+            try {
+                if (object != null) {
+                    objectId = object.toString();
+                } else {
+                    objectId = "<null>";
+                }
+            } catch (Exception e) {
+                objectId = "<unknown>";
+            } finally {
+                return new ValidationException("error during validation of "
+                        + objectId, ex);
+            }
+        }
+    }
+
+    private void validatePropertyInGroup(final GroupValidationContext<?> context) {
+        final Runnable helper;
+        if (context.getMetaProperty() == null) {
+            helper = new Runnable() {
+
+                public void run() {
+                    ValidationHelper.validateBean(context);
+                }
+            };
+        } else {
+            helper = new Runnable() {
+
+                public void run() {
+                    ValidationHelper.validateProperty(context);
+                }
+            };
+        }
+        Group currentGroup = context.getCurrentGroup();
+        List<Group> defaultGroups = expandDefaultGroup(context);
+        if (defaultGroups != null) {
+            for (Group each : defaultGroups) {
+                context.setCurrentGroup(each);
+                helper.run();
+                // continue validation, even if errors already found
+            }
+            context.setCurrentGroup(currentGroup); // restore
+        } else {
+            helper.run();
+        }
+    }
+
+    /**
+     * Create a {@link GroupValidationContext}.
+     *
+     * @param <T>
+     * @param metaBean
+     * @param object
+     * @param objectClass
+     * @param groups
+     * @return {@link GroupValidationContext} instance
+     */
+    protected <T> GroupValidationContext<T> createContext(MetaBean metaBean, T object, Class<T> objectClass, Class<?>... groups) {
+        final ConstraintValidationListener<T> listener = new ConstraintValidationListener<T>(object, objectClass);
+        final GroupValidationContextImpl<T> context =
+            new GroupValidationContextImpl<T>(listener, this.factoryContext.getMessageInterpolator(),
+                this.factoryContext.getTraversableResolver(), factoryContext.getParameterNameProvider(), metaBean);
+        context.setBean(object, metaBean);
+        context.setGroups(groupsComputer.computeGroups(groups));
+        return context;
+    }
+
+    protected <T> GroupValidationContext<T> createInvocableContext(MetaBean metaBean, T object, Class<T> objectClass, Class<?>... groups) {
+        final ConstraintValidationListener<T> listener = new ConstraintValidationListener<T>(object, objectClass);
+        final GroupValidationContextImpl<T> context =
+                new GroupValidationContextImpl<T>(listener, this.factoryContext.getMessageInterpolator(),
+                        this.factoryContext.getTraversableResolver(), factoryContext.getParameterNameProvider(), metaBean);
+        context.setBean(object, metaBean);
+        final Groups computedGroup = groupsComputer.computeGroups(groups);
+        if (computedGroup.getGroups().size() == 1 && Group.DEFAULT.equals(computedGroup.getGroups().iterator().next()) && metaBean.getFeature(JsrFeatures.Bean.GROUP_SEQUENCE) != null) {
+            final Groups sequence = new Groups();
+            sequence.getGroups().addAll(List.class.cast(metaBean.getFeature(JsrFeatures.Bean.GROUP_SEQUENCE)));
+            context.setGroups(sequence);
+        } else {
+            context.setGroups(computedGroup);
+        }
+        return context;
+    }
+
+    /**
+     * Create a {@link BeanDescriptorImpl}
+     *
+     * @param metaBean
+     * @return {@link BeanDescriptorImpl} instance
+     */
+    /*
+    protected BeanDescriptorImpl createBeanDescriptor(MetaBean metaBean) {
+        return new BeanDescriptorImpl(factoryContext, metaBean);
+    }
+    */
+    protected BeanDescriptorImpl createBeanDescriptor(MetaBean metaBean) {
+        return new BeanDescriptorImpl(factoryContext, metaBean);
+    }
+
+    private boolean treatMapsLikeBeans = false;
+
+    /**
+     * Behavior configuration -
+     * <p/>
+     * <pre>
+     * @return treatMapsLikeBeans - true (validate maps like beans, so that
+     *                              you can use Maps to validate dynamic classes or
+     *                              beans for which you have the MetaBean but no instances)
+     *                            - false (default), validate maps like collections
+     *                              (validating the values only)
+     * </pre>
+     * <p/>
+     * (is still configuration to better in BeanValidationContext?)
+     */
+    public boolean isTreatMapsLikeBeans() {
+        return treatMapsLikeBeans;
+    }
+
+    /**
+     * Set whether maps are to be treated like beans.
+     * 
+     * <pre>
+     * @param treatMapsLikeBeans - true (validate maps like beans, so that
+     *                             you can use Maps to validate dynamic classes or
+     *                             beans for which you have the MetaBean but no instances)
+     *                           - false (default), validate maps like collections
+     *                             (validating the values only)
+     * </pre>
+     */
+    public void setTreatMapsLikeBeans(boolean treatMapsLikeBeans) {
+        this.treatMapsLikeBeans = treatMapsLikeBeans;
+    }
+
+    /**
+     * Checks that beanType is valid according to spec Section 4.1.1 i. Throws an {@link IllegalArgumentException} if it
+     * is not.
+     *
+     * @param beanType Bean type to check.
+     */
+    private <T> Class<T> checkBeanType(Class<T> beanType) {
+        if (beanType == null) {
+            throw new IllegalArgumentException("Bean type cannot be null.");
+        }
+        return beanType;
+    }
+
+    /**
+     * Checks that the property name is valid according to spec Section 4.1.1 i. Throws an
+     * {@link IllegalArgumentException} if it is not.
+     *
+     * @param propertyName Property name to check.
+     */
+    private void checkPropertyName(String propertyName) {
+        if (propertyName == null || propertyName.trim().length() == 0) {
+            throw new IllegalArgumentException("Property path cannot be null or empty.");
+        }
+    }
+
+    /**
+     * Checks that the groups array is valid according to spec Section 4.1.1 i. Throws an
+     * {@link IllegalArgumentException} if it is not.
+     *
+     * @param groups The groups to check.
+     */
+    private void checkGroups(Class<?>[] groups) {
+        if (groups == null) {
+            throw new IllegalArgumentException("Groups cannot be null.");
+        }
+        for (final Class<?> c : groups) {
+            if (c == null) {
+                throw new IllegalArgumentException("Group cannot be null.");
+            }
+        }
+    }
+
+    public <T> Set<ConstraintViolation<T>> validateConstructorParameters(Constructor<? extends T> constructor, Object[] parameterValues, Class<?>... gps) {
+        notNull("Constructor", constructor);
+        notNull("Groups", gps);
+        notNull("Parameters", parameterValues);
+
+        final Class<?> declaringClass = constructor.getDeclaringClass();
+        final ConstructorDescriptorImpl constructorDescriptor = ConstructorDescriptorImpl.class.cast(getConstraintsForClass(declaringClass).getConstraintsForConstructor(constructor.getParameterTypes()));
+        if (constructorDescriptor == null) { // no constraint
+            return Collections.emptySet();
+        }
+
+        // sanity checks
+        if (!constructorDescriptor.isValidated(constructor)) {
+            if (parameterValues.length > 0) {
+                checkValidationAppliesTo(Collections.singleton(constructorDescriptor.getCrossParameterDescriptor()), ConstraintTarget.IMPLICIT);
+                checkValidationAppliesTo(constructorDescriptor.getParameterDescriptors(), ConstraintTarget.IMPLICIT);
+            } else {
+                checkValidationAppliesTo(Collections.singleton(constructorDescriptor.getCrossParameterDescriptor()), ConstraintTarget.PARAMETERS);
+                checkValidationAppliesTo(constructorDescriptor.getParameterDescriptors(), ConstraintTarget.PARAMETERS);
+            }
+            constructorDescriptor.setValidated(constructor);
+        }
+
+        // validations
+        return validateInvocationParameters(constructor, parameterValues, constructorDescriptor, gps, new NodeImpl.ConstructorNodeImpl(declaringClass.getSimpleName(), Arrays.asList(constructor.getParameterTypes())), null);
+    }
+
+    private <T> Set<ConstraintViolation<T>> validateInvocationParameters(final Member invocable, final Object[] parameterValues, final InvocableElementDescriptor constructorDescriptor,
+                                                                         final Class<?>[] gps, final NodeImpl rootNode, final Object rootBean) {
+        final Set<ConstraintViolation<T>> violations = new HashSet<ConstraintViolation<T>>();
+
+
+        final GroupValidationContext<ConstraintValidationListener<?>> parametersContext = createInvocableContext(constructorDescriptor.getMetaBean(), rootBean, Class.class.cast(invocable.getDeclaringClass()), gps);
+        final GroupValidationContext<Object> crossParameterContext = createContext(constructorDescriptor.getMetaBean(), rootBean, Class.class.cast(invocable.getDeclaringClass()), gps);
+        if (rootBean != null) { // could be more sexy but that's ok for now
+            final Method m = Method.class.cast(invocable);
+            parametersContext.setMethod(m);
+            crossParameterContext.setMethod(m);
+        } else {
+            final Constructor<?> m = Constructor.class.cast(invocable);
+            parametersContext.setConstructor(m);
+            crossParameterContext.setConstructor(m);
+        }
+
+        final Groups groups = parametersContext.getGroups();
+
+        final List<ParameterDescriptor> parameterDescriptors = constructorDescriptor.getParameterDescriptors();
+        final ElementDescriptorImpl crossParamDescriptor = ElementDescriptorImpl.class.cast(constructorDescriptor.getCrossParameterDescriptor());
+        final Set<ConstraintDescriptor<?>> crossParamConstraints = crossParamDescriptor.getConstraintDescriptors();
+
+        crossParameterContext.setBean(parameterValues);
+        crossParameterContext.moveDown(rootNode);
+        crossParameterContext.moveDown("<cross-parameter>");
+        crossParameterContext.setKind(ElementKind.CROSS_PARAMETER);
+
+        parametersContext.moveDown(rootNode);
+        parametersContext.setParameters(parameterValues);
+
+        for (final Group current : groups.getGroups()) {
+            for (int i = 0; i < parameterValues.length; i++) {
+                final ParameterDescriptorImpl paramDesc = ParameterDescriptorImpl.class.cast(parameterDescriptors.get(i));
+                parametersContext.setBean(parameterValues[i]);
+                parametersContext.moveDown(new NodeImpl.ParameterNodeImpl(paramDesc.getName(), i));
+                for (final ConstraintDescriptor<?> constraintDescriptor : paramDesc.getConstraintDescriptors()) {
+                    final ConstraintValidation<?> validation = ConstraintValidation.class.cast(constraintDescriptor);
+                    parametersContext.setCurrentGroup(paramDesc.mapGroup(current));
+                    validation.validate(parametersContext);
+                }
+                parametersContext.moveUp(null, null);
+            }
+
+            for (final ConstraintDescriptor<?> d : crossParamConstraints) {
+                final ConstraintValidation<?> validation = ConstraintValidation.class.cast(d);
+                crossParameterContext.setCurrentGroup(crossParamDescriptor.mapGroup(current));
+                validation.validate(crossParameterContext);
+            }
+
+            if (gps.length == 0 && parametersContext.getListener().getConstraintViolations().size() + crossParameterContext.getListener().getConstraintViolations().size() > 0) {
+                break;
+            }
+        }
+
+        for (final Group current : groups.getGroups()) {
+            for (int i = 0; i < parameterValues.length; i++) {
+                final ParameterDescriptorImpl paramDesc = ParameterDescriptorImpl.class.cast(parameterDescriptors.get(i));
+                if (paramDesc.isCascaded() && parameterValues[i] != null) {
+                    parametersContext.setBean(parameterValues[i]);
+                    parametersContext.moveDown(new NodeImpl.ParameterNodeImpl(paramDesc.getName(), i));
+                    initMetaBean(parametersContext, factoryContext.getMetaBeanFinder(), parameterValues[i].getClass());
+                    parametersContext.setCurrentGroup(paramDesc.mapGroup(current));
+                    ValidationHelper.validateContext(parametersContext, new JsrValidationCallback(parametersContext), isTreatMapsLikeBeans());
+                    parametersContext.moveUp(null, null);
+                }
+            }
+        }
+
+        for (final List<Group> eachSeq : groups.getSequences()) {
+            for (final Group current : eachSeq) {
+                for (int i = 0; i < parameterValues.length; i++) {
+                    final ParameterDescriptorImpl paramDesc = ParameterDescriptorImpl.class.cast(parameterDescriptors.get(i));
+                    parametersContext.setBean(parameterValues[i]);
+                    parametersContext.moveDown(new NodeImpl.ParameterNodeImpl(paramDesc.getName(), i));
+                    for (final ConstraintDescriptor<?> constraintDescriptor : paramDesc.getConstraintDescriptors()) {
+                        final ConstraintValidation<?> validation = ConstraintValidation.class.cast(constraintDescriptor);
+                        parametersContext.setCurrentGroup(paramDesc.mapGroup(current));
+                        validation.validate(parametersContext);
+                    }
+                    parametersContext.moveUp(null, null);
+                }
+
+                for (final ConstraintDescriptor<?> d : crossParamConstraints) {
+                    final ConstraintValidation<?> validation = ConstraintValidation.class.cast(d);
+                    crossParameterContext.setCurrentGroup(crossParamDescriptor.mapGroup(current));
+                    validation.validate(crossParameterContext);
+                }
+
+                if (parametersContext.getListener().getConstraintViolations().size() + crossParameterContext.getListener().getConstraintViolations().size() > 0) {
+                    break;
+                }
+            }
+
+            for (final Group current : eachSeq) {
+                for (int i = 0; i < parameterValues.length; i++) {
+                    final ParameterDescriptorImpl paramDesc = ParameterDescriptorImpl.class.cast(parameterDescriptors.get(i));
+                    if (paramDesc.isCascaded() && parameterValues[i] != null) {
+                        parametersContext.setBean(parameterValues[i]);
+                        parametersContext.moveDown(new NodeImpl.ParameterNodeImpl(paramDesc.getName(), i));
+                        initMetaBean(parametersContext, factoryContext.getMetaBeanFinder(), parameterValues[i].getClass());
+                        parametersContext.setCurrentGroup(paramDesc.mapGroup(current));
+                        ValidationHelper.validateContext(parametersContext, new JsrValidationCallback(parametersContext), isTreatMapsLikeBeans());
+                        parametersContext.moveUp(null, null);
+                    }
+                }
+            }
+        }
+        if (constructorDescriptor.isCascaded()) {
+            if (parametersContext.getValidatedValue() != null) {
+                initMetaBean(parametersContext, factoryContext.getMetaBeanFinder(), parametersContext.getValidatedValue().getClass());
+
+                for (final Group current : groups.getGroups()) {
+                    parametersContext.setCurrentGroup(constructorDescriptor.mapGroup(current));
+                    ValidationHelper.validateContext(parametersContext, new JsrValidationCallback(parametersContext), isTreatMapsLikeBeans());
+                }
+                for (final List<Group> eachSeq : groups.getSequences()) {
+                    for (final Group current : eachSeq) {
+                        parametersContext.setCurrentGroup(constructorDescriptor.mapGroup(current));
+                        ValidationHelper.validateContext(parametersContext, new JsrValidationCallback(parametersContext), isTreatMapsLikeBeans());
+                        if (!parametersContext.getListener().isEmpty()) {
+                            break;
+                        }
+                    }
+                }
+            }
+            if (crossParameterContext.getValidatedValue() != null) {
+                initMetaBean(crossParameterContext, factoryContext.getMetaBeanFinder(), crossParameterContext.getValidatedValue().getClass());
+
+                for (final Group current : groups.getGroups()) {
+                    crossParameterContext.setCurrentGroup(constructorDescriptor.mapGroup(current));
+                    ValidationHelper.validateContext(crossParameterContext, new JsrValidationCallback(crossParameterContext), isTreatMapsLikeBeans());
+                }
+                for (final List<Group> eachSeq : groups.getSequences()) {
+                    for (final Group current : eachSeq) {
+                        crossParameterContext.setCurrentGroup(constructorDescriptor.mapGroup(current));
+                        ValidationHelper.validateContext(crossParameterContext, new JsrValidationCallback(crossParameterContext), isTreatMapsLikeBeans());
+                        if (!crossParameterContext.getListener().isEmpty()) {
+                            break;
+                        }
+                    }
+                }
+            }
+        }
+
+        violations.addAll(Set.class.cast(parametersContext.getListener().getConstraintViolations()));
+        violations.addAll(Set.class.cast(crossParameterContext.getListener().getConstraintViolations()));
+
+        return violations;
+    }
+
+    private static void checkValidationAppliesTo(final Collection<? extends ElementDescriptor> descriptors, final ConstraintTarget forbidden) {
+        for (final ElementDescriptor descriptor : descriptors) {
+            for (final ConstraintDescriptor<?> consDesc : descriptor.getConstraintDescriptors()) {
+                checkValidationAppliesTo(consDesc.getValidationAppliesTo(), forbidden);
+            }
+        }
+    }
+
+    private static void checkValidationAppliesTo(final Set<ConstraintDescriptor<?>> constraintDescriptors, final ConstraintTarget forbidden) {
+        for (final ConstraintDescriptor<?> descriptor : constraintDescriptors) {
+            checkValidationAppliesTo(descriptor.getValidationAppliesTo(), forbidden);
+        }
+    }
+
+    private static void checkValidationAppliesTo(final ConstraintTarget configured, final ConstraintTarget forbidden) {
+        if (forbidden.equals(configured)) {
+            throw new ConstraintDeclarationException(forbidden.name() + " forbidden here");
+        }
+    }
+
+    public <T> Set<ConstraintViolation<T>> validateConstructorReturnValue(final Constructor<? extends T> constructor, final T createdObject, final Class<?>... gps) {
+        {
+            notNull("Constructor", constructor);
+            notNull("Returned value", createdObject);
+        }
+
+        final Class<? extends T> declaringClass = constructor.getDeclaringClass();
+        final ConstructorDescriptorImpl methodDescriptor = ConstructorDescriptorImpl.class.cast(getConstraintsForClass(declaringClass).getConstraintsForConstructor(constructor.getParameterTypes()));
+        if (methodDescriptor == null) {
+            throw new ValidationException("Constructor " + constructor + " doesn't belong to class " + declaringClass);
+        }
+
+        return validaReturnedValue(new NodeImpl.ConstructorNodeImpl(declaringClass.getSimpleName(), Arrays.asList(constructor.getParameterTypes())), createdObject, declaringClass, methodDescriptor, gps, null);
+    }
+
+    private <T> Set<ConstraintViolation<T>> validaReturnedValue(final NodeImpl rootNode, final T createdObject, final Class<?> clazz,
+                                                                final InvocableElementDescriptor methodDescriptor, final Class<?>[] gps,
+                                                                final Object rootBean) {
+        final ElementDescriptorImpl returnedValueDescriptor = ElementDescriptorImpl.class.cast(methodDescriptor.getReturnValueDescriptor());
+        final Set<ConstraintDescriptor<?>> returnedValueConstraints = returnedValueDescriptor.getConstraintDescriptors();
+
+        final GroupValidationContext<T> context = createInvocableContext(methodDescriptor.getMetaBean(), createdObject, Class.class.cast(Proxies.classFor(clazz)), gps);
+        context.moveDown(rootNode);
+        context.moveDown(new NodeImpl.ReturnValueNodeImpl());
+        context.setReturnValue(rootBean);
+
+        final Groups groups = context.getGroups();
+
+        for (final Group current : groups.getGroups()) {
+            for (final ConstraintDescriptor<?> d : returnedValueConstraints) {
+                final ConstraintValidation<?> validation = ConstraintValidation.class.cast(d);
+                context.setCurrentGroup(returnedValueDescriptor.mapGroup(current));
+                validation.validate(context);
+            }
+
+            if (gps.length == 0 && !context.getListener().getConstraintViolations().isEmpty()) {
+                break;
+            }
+        }
+
+        int currentViolationNumber = context.getListener().getConstraintViolations().size();
+        for (final Group current : groups.getGroups()) {
+            if (returnedValueDescriptor.isCascaded() && context.getValidatedValue() != null) {
+                context.setBean(createdObject);
+                initMetaBean(context, factoryContext.getMetaBeanFinder(), context.getValidatedValue().getClass());
+
+                context.setCurrentGroup(methodDescriptor.mapGroup(current));
+                ValidationHelper.validateContext(context, new JsrValidationCallback(context), isTreatMapsLikeBeans());
+
+                if (currentViolationNumber < context.getListener().getConstraintViolations().size()) {
+                    break;
+                }
+            }
+        }
+
+        for (final List<Group> eachSeq : groups.getSequences()) {
+            for (final Group current : eachSeq) {
+                for (final ConstraintDescriptor<?> d : returnedValueConstraints) {
+                    final ConstraintValidation<?> validation = ConstraintValidation.class.cast(d);
+                    // context.setCurrentGroup(returnedValueDescriptor.mapGroup(current)); // mapping is only relevant for cascaded validations
+                    context.setCurrentGroup(current);
+                    validation.validate(context);
+                }
+
+                if (!context.getListener().getConstraintViolations().isEmpty()) {
+                    break;
+                }
+            }
+
+            currentViolationNumber = context.getListener().getConstraintViolations().size();
+            for (final Group current : eachSeq) {
+                if (returnedValueDescriptor.isCascaded() && context.getValidatedValue() != null) {
+                    context.setBean(createdObject);
+                    initMetaBean(context, factoryContext.getMetaBeanFinder(), context.getValidatedValue().getClass());
+
+                    context.setCurrentGroup(methodDescriptor.mapGroup(current));
+                    ValidationHelper.validateContext(context, new JsrValidationCallback(context), isTreatMapsLikeBeans());
+
+                    if (currentViolationNumber < context.getListener().getConstraintViolations().size()) {
+                        break;
+                    }
+                }
+            }
+        }
+
+        return context.getListener().getConstraintViolations();
+    }
+
+    public <T> Set<ConstraintViolation<T>> validateParameters(T object, Method method, Object[] parameterValues, Class<?>... groups) {
+        {
+            notNull("Object", object);
+            notNull("Parameters", parameterValues);
+            notNull("Method", method);
+            notNull("Groups", groups);
+            for (final Class<?> g : groups) {
+                notNull("Each group", g);
+            }
+        }
+
+        final MethodDescriptorImpl methodDescriptor = findMethodDescriptor(object, method);
+        if (methodDescriptor == null) { // no constraint
+            return Collections.emptySet();
+        }
+
+        if (!methodDescriptor.isValidated(method)) {
+            if (method.getParameterTypes().length > 0 && method.getReturnType() != Void.TYPE) {
+                checkValidationAppliesTo(Collections.singleton(methodDescriptor.getCrossParameterDescriptor()), ConstraintTarget.IMPLICIT);
+                checkValidationAppliesTo(methodDescriptor.getParameterDescriptors(), ConstraintTarget.IMPLICIT);
+            } else if (method.getParameterTypes().length == 0) {
+                checkValidationAppliesTo(Collections.singleton(methodDescriptor.getCrossParameterDescriptor()), ConstraintTarget.PARAMETERS);
+                checkValidationAppliesTo(methodDescriptor.getParameterDescriptors(), ConstraintTarget.PARAMETERS);
+            }
+            methodDescriptor.setValidated(method);
+        }
+
+        return validateInvocationParameters(method, parameterValues, methodDescriptor, groups, new NodeImpl.MethodNodeImpl(method.getName(), Arrays.asList(method.getParameterTypes())), object);
+    }
+
+    private static void notNull(final String entity, final Object shouldntBeNull) {
+        if (shouldntBeNull == null) {
+            throw new IllegalArgumentException(entity + " shouldn't be null");
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public <T> Set<ConstraintViolation<T>> validateReturnValue(T object, Method method, Object returnValue, Class<?>... groups) {
+        notNull("object", object);
+        notNull("method", method);
+        notNull("groups", groups);
+
+        MethodDescriptorImpl methodDescriptor = findMethodDescriptor(object, method);
+        if (methodDescriptor == null) {
+            throw new ValidationException("Method " + method + " doesn't belong to class " + object.getClass());
+        }
+
+        if (method.getReturnType() == Void.TYPE) {
+            checkValidationAppliesTo(methodDescriptor.getReturnValueDescriptor().getConstraintDescriptors(), ConstraintTarget.RETURN_VALUE);
+        }
+
+        return Set.class.cast(validaReturnedValue(new NodeImpl.MethodNodeImpl(method.getName(), Arrays.asList(method.getParameterTypes())), returnValue, object.getClass(), methodDescriptor, groups, object));
+    }
+
+    private <T> MethodDescriptorImpl findMethodDescriptor(final T object, final Method method) {
+        // return MethodDescriptorImpl.class.cast(getConstraintsForClass(Proxies.classFor(object.getClass())).getConstraintsForMethod(method.getName(), method.getParameterTypes()));
+        return MethodDescriptorImpl.class.cast(getConstraintsForClass(Proxies.classFor(method.getDeclaringClass())).getConstraintsForMethod(method.getName(), method.getParameterTypes()));
+    }
+
+    private <T> void initMetaBean(final GroupValidationContext<T> context, final MetaBeanFinder metaBeanFinder, final Class<?> directValueClass) {
+        final boolean collection = Collection.class.isAssignableFrom(directValueClass);
+        final boolean map = Map.class.isAssignableFrom(directValueClass);
+        if (!directValueClass.isArray()
+                && (!collection || Collection.class.cast(context.getValidatedValue()).isEmpty())
+                && (!map || Map.class.cast(context.getValidatedValue()).isEmpty())) {
+            context.setMetaBean(metaBeanFinder.findForClass(directValueClass));
+        } else if (collection) {
+            context.setMetaBean(metaBeanFinder.findForClass(Collection.class.cast(context.getValidatedValue()).iterator().next().getClass()));
+        } else if (map) {
+            context.setMetaBean(metaBeanFinder.findForClass(Map.class.cast(context.getValidatedValue()).values().iterator().next().getClass()));
+        } else {
+            context.setMetaBean(metaBeanFinder.findForClass(directValueClass.getComponentType()));
+        }
+    }
+
+    private <T> Group validateElementInContext(final GroupValidationContext<T> context,
+                                              final ElementDescriptor eltDescriptor) {
+
+        final ElementDescriptorImpl impl = ElementDescriptorImpl.class.cast(eltDescriptor);
+
+        final Groups groups = context.getGroups();
+
+        Group breakOnGroup = null;
+
+        for (final ConstraintDescriptor<?> consDesc : eltDescriptor.getConstraintDescriptors()) {
+            final ConstraintValidation<?> validation = (ConstraintValidation<?>) consDesc;
+
+            // 1. process groups
+            for (final Group current : groups.getGroups()) {
+                context.setCurrentGroup(current);
+                validation.validate(context);
+            }
+            // 2. process sequences
+            for (final List<Group> eachSeq : groups.getSequences()) {
+                for (final Group current : eachSeq) {
+                    context.setCurrentGroup(current);
+                    validation.validate(context);
+                    /**
+                     * if one of the group process in the sequence leads to one
+                     * or more validation failure, the groups following in the
+                     * sequence must not be processed
+                     */
+                    if (!context.getListener().isEmpty()) {
+                        breakOnGroup = current;
+                        break;
+                    }
+                }
+            }
+        }
+        if (impl.isCascaded() && context.getValidatedValue() != null) {
+            initMetaBean(context, factoryContext.getMetaBeanFinder(), context.getValidatedValue().getClass());
+
+            // 1. process groups
+            for (final Group current : groups.getGroups()) {
+                context.setCurrentGroup(impl.mapGroup(current));
+                ValidationHelper.validateContext(context, new JsrValidationCallback(context), isTreatMapsLikeBeans());
+            }
+            // 2. process sequences
+            for (final List<Group> eachSeq : groups.getSequences()) {
+                for (final Group current : eachSeq) {
+                    context.setCurrentGroup(impl.mapGroup(current));
+                    ValidationHelper.validateContext(context, new JsrValidationCallback(context), isTreatMapsLikeBeans());
+                    /**
+                     * if one of the group process in the sequence leads to one
+                     * or more validation failure, the groups following in the
+                     * sequence must not be processed
+                     */
+                    if (!context.getListener().isEmpty()) {
+                        breakOnGroup = current;
+                        break;
+                    }
+                }
+            }
+        }
+        return breakOnGroup;
+    }
+
+    /**
+     * Dispatches a call from {@link #validate()} to {@link ClassValidator#validateBeanNet(GroupValidationContext)} with
+     * the current context set.
+     */
+    protected class JsrValidationCallback implements ValidationHelper.ValidateCallback {
+
+        private final GroupValidationContext<?> context;
+
+        public JsrValidationCallback(GroupValidationContext<?> context) {
+            this.context = context;
+        }
+
+        public void validate() {
+            validateBeanNet(context);
+        }
+
+    }
+
+    /**
+     * Create a {@link ValidationContextTraversal} instance for this {@link ClassValidator}.
+     * 
+     * @param validationContext
+     * @return {@link ValidationContextTraversal}
+     */
+    protected ValidationContextTraversal createValidationContextTraversal(GroupValidationContext<?> validationContext) {
+        return new ValidationContextTraversal(validationContext);
+    }
+
+    /**
+     * Implement {@link #validateProperty(Object, String, boolean, Class[])} } and
+     * {@link #validateValue(Class, String, Object, boolean, Class...)}.
+     * 
+     * @param <T>
+     * @param beanType
+     * @param object
+     * @param propertyName
+     * @param value
+     * @param cascade
+     * @param groups
+     * @return {@link ConstraintViolation} {@link Set}
+     */
+    private <T> Set<ConstraintViolation<T>> validateValueImpl(Class<T> beanType, T object, String propertyName,
+        Object value, final boolean cascade, Class<?>... groups) {
+
+        assert (object == null) ^ (value == VALIDATE_PROPERTY);
+        checkPropertyName(propertyName);
+        checkGroups(groups);
+
+        try {
+            final MetaBean initialMetaBean = new DynamicMetaBean(metaBeanFinder);
+            initialMetaBean.setBeanClass(beanType);
+            GroupValidationContext<T> context = createContext(initialMetaBean, object, beanType, groups);
+            ValidationContextTraversal contextTraversal = createValidationContextTraversal(context);
+            PathNavigation.navigate(propertyName, contextTraversal);
+
+            MetaProperty prop = context.getMetaProperty();
+            boolean fixed = false;
+            if (value != VALIDATE_PROPERTY) {
+                assert !context.getPropertyPath().isRootPath();
+                if (prop == null && value != null) {
+                    context.setMetaBean(metaBeanFinder.findForClass(value.getClass()));
+                }
+                if (!cascade) {
+                    //TCK doesn't care what type a property is if there are no constraints to validate:
+                    FeaturesCapable meta = prop == null ? context.getMetaBean() : prop;
+                    if (ArrayUtils.isEmpty(meta.getValidations())) {
+                        return Collections.<ConstraintViolation<T>> emptySet();
+                    }
+                }
+                if (!TypeUtils.isAssignable(value == null ? null : value.getClass(), contextTraversal.getType())) {
+                    throw new IncompatiblePropertyValueException(String.format(
+                        "%3$s is not a valid value for property %2$s of type %1$s", beanType, propertyName, value));
+                }
+                if (prop == null) {
+                    context.setBean(value);
+                } else {
+                    context.setFixedValue(value);
+                    fixed = true;
+                }
+            }
+            boolean doCascade = cascade && (prop == null || prop.getMetaBean() != null);
+
+            Object bean = context.getBean();
+
+            ConstraintValidationListener<T> result = context.getListener();
+            Groups sequence = context.getGroups();
+
+            // 1. process groups
+
+            for (Group current : sequence.getGroups()) {
+                context.setCurrentGroup(current);
+
+                if (!doCascade || prop != null) {
+                    validatePropertyInGroup(context);
+                }
+                if (doCascade) {
+                    contextTraversal.moveDownIfNecessary();
+                    if (context.getMetaBean() instanceof DynamicMetaBean) {
+                        context.setMetaBean(context.getMetaBean().resolveMetaBean(
+                            ObjectUtils.defaultIfNull(context.getBean(), contextTraversal.getRawType())));
+                    }
+                    validateBeanNet(context);
+                    if (prop != null) {
+                        context.moveUp(bean, prop.getParentMetaBean());
+                        context.setMetaProperty(prop);
+                        if (fixed) {
+                            context.setFixedValue(value);
+                        }
+                    }
+                }
+            }
+
+            // 2. process sequences
+
+            int groupViolations = result.getConstraintViolations().size();
+
+            outer: for (List<Group> eachSeq : sequence.getSequences()) {
+                for (Group current : eachSeq) {
+                    context.setCurrentGroup(current);
+
+                    if (!doCascade || prop != null) {
+                        validatePropertyInGroup(context);
+                    }
+                    if (doCascade) {
+                        contextTraversal.moveDownIfNecessary();
+                        if (context.getMetaBean() instanceof DynamicMetaBean) {
+                            context.setMetaBean(context.getMetaBean().resolveMetaBean(
+                                ObjectUtils.defaultIfNull(context.getBean(), contextTraversal.getRawType())));
+                        }
+                        validateBeanNet(context);
+                        if (prop != null) {
+                            context.moveUp(bean, prop.getParentMetaBean());
+                            context.setMetaProperty(prop);
+                            if (fixed) {
+                                context.setFixedValue(value);
+                            }
+                        }
+                    }
+                    /**
+                     * if one of the group process in the sequence leads to one or more validation failure, the groups
+                     * following in the sequence must not be processed
+                     */
+                    if (result.getConstraintViolations().size() > groupViolations)
+                        break outer;
+                }
+            }
+            return result.getConstraintViolations();
+        } catch (RuntimeException ex) {
+            throw unrecoverableValidationError(ex, ObjectUtils.defaultIfNull(object, value));
+        }
+    }
+}

Added: bval/branches/bval-11/bval-jsr/src/main/java/org/apache/bval/jsr/ConfigurationImpl.java
URL: http://svn.apache.org/viewvc/bval/branches/bval-11/bval-jsr/src/main/java/org/apache/bval/jsr/ConfigurationImpl.java?rev=1517540&view=auto
==============================================================================
--- bval/branches/bval-11/bval-jsr/src/main/java/org/apache/bval/jsr/ConfigurationImpl.java (added)
+++ bval/branches/bval-11/bval-jsr/src/main/java/org/apache/bval/jsr/ConfigurationImpl.java Mon Aug 26 13:59:15 2013
@@ -0,0 +1,397 @@
+/*
+ * 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.jsr;
+
+
+import org.apache.bval.jsr.parameter.DefaultParameterNameProvider;
+import org.apache.bval.jsr.resolver.DefaultTraversableResolver;
+import org.apache.bval.jsr.util.IOs;
+import org.apache.bval.jsr.xml.ValidationParser;
+
+import javax.validation.BootstrapConfiguration;
+import javax.validation.ConstraintValidatorFactory;
+import javax.validation.MessageInterpolator;
+import javax.validation.ParameterNameProvider;
+import javax.validation.TraversableResolver;
+import javax.validation.ValidationException;
+import javax.validation.ValidationProviderResolver;
+import javax.validation.ValidatorFactory;
+import javax.validation.executable.ExecutableType;
+import javax.validation.spi.BootstrapState;
+import javax.validation.spi.ConfigurationState;
+import javax.validation.spi.ValidationProvider;
+import java.io.Closeable;
+import java.io.InputStream;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.logging.Logger;
+
+/**
+ * Description: used to configure apache-validation for jsr.
+ * Implementation of Configuration that also implements ConfigurationState,
+ * hence this can be passed to buildValidatorFactory(ConfigurationState).
+ * <br/>
+ */
+public class ConfigurationImpl implements ApacheValidatorConfiguration, ConfigurationState {
+    private static final Logger log = Logger.getLogger(ConfigurationImpl.class.getName());
+
+    /**
+     * Configured {@link ValidationProvider}
+     */
+    //couldn't this be parameterized <ApacheValidatorConfiguration> or <? super ApacheValidatorConfiguration>?
+    protected final ValidationProvider<?> provider;
+
+    /**
+     * Configured {@link ValidationProviderResolver}
+     */
+    protected final ValidationProviderResolver providerResolver;
+
+    /**
+     * Configured {@link ValidationProvider} class
+     */
+    protected Class<? extends ValidationProvider<?>> providerClass;
+
+    /**
+     * Configured {@link MessageInterpolator}
+     */
+    protected MessageInterpolator defaultMessageInterpolator = new DefaultMessageInterpolator();
+    protected MessageInterpolator messageInterpolator = defaultMessageInterpolator;
+
+    /**
+     * Configured {@link ConstraintValidatorFactory}
+     */
+    protected ConstraintValidatorFactory defaultConstraintValidatorFactory = new DefaultConstraintValidatorFactory();
+    protected ConstraintValidatorFactory constraintValidatorFactory = defaultConstraintValidatorFactory;
+
+    private TraversableResolver defaultTraversableResolver = new DefaultTraversableResolver();
+    private TraversableResolver traversableResolver = defaultTraversableResolver;
+
+    protected ParameterNameProvider defaultParameterNameProvider = new DefaultParameterNameProvider();
+    protected ParameterNameProvider parameterNameProvider = defaultParameterNameProvider;
+
+    protected BootstrapConfiguration  bootstrapConfiguration;
+
+    protected Collection<ExecutableType> executableValidation;
+
+    // BEGIN DEFAULTS
+    /**
+     * false = dirty flag (to prevent from multiple parsing validation.xml)
+     */
+    private boolean prepared = false;
+    // END DEFAULTS
+
+    private Set<InputStream> mappingStreams = new HashSet<InputStream>();
+    private Map<String, String> properties = new HashMap<String, String>();
+    private boolean ignoreXmlConfiguration = false;
+
+    private volatile ValidationParser parser;
+
+    /**
+     * Create a new ConfigurationImpl instance.
+     * @param aState
+     * @param aProvider
+     */
+    public ConfigurationImpl(BootstrapState aState, ValidationProvider<?> aProvider) {
+        if (aProvider != null) {
+            this.provider = aProvider;
+            this.providerResolver = null;
+        } else if (aState != null) {
+            this.provider = null;
+            if (aState.getValidationProviderResolver() == null) {
+                providerResolver = aState.getDefaultValidationProviderResolver();
+            } else {
+                providerResolver = aState.getValidationProviderResolver();
+            }
+        } else {
+            throw new ValidationException("either provider or state are required");
+        }
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ApacheValidatorConfiguration traversableResolver(TraversableResolver resolver) {
+        traversableResolver = resolver;
+        this.prepared = false;
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     * Ignore data from the <i>META-INF/validation.xml</i> file if this
+     * method is called.
+     *
+     * @return this
+     */
+    public ApacheValidatorConfiguration ignoreXmlConfiguration() {
+        ignoreXmlConfiguration = true;
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ConfigurationImpl messageInterpolator(MessageInterpolator resolver) {
+        this.messageInterpolator = resolver;
+        this.prepared = false;
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public ConfigurationImpl constraintValidatorFactory(
+          ConstraintValidatorFactory constraintFactory) {
+        this.constraintValidatorFactory = constraintFactory;
+        this.prepared = false;
+        return this;
+    }
+
+    public ApacheValidatorConfiguration parameterNameProvider(ParameterNameProvider parameterNameProvider) {
+        this.parameterNameProvider = parameterNameProvider;
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     * Add a stream describing constraint mapping in the Bean Validation
+     * XML format.
+     *
+     * @return this
+     */
+    public ApacheValidatorConfiguration addMapping(InputStream stream) {
+        mappingStreams.add(IOs.convertToMarkableInputStream(stream));
+        return this;
+    }
+
+    /**
+     * {@inheritDoc}
+     * Add a provider specific property. This property is equivalent to
+     * XML configuration properties.
+     * If we do not know how to handle the property, we silently ignore it.
+     *
+     * @return this
+     */
+    public ApacheValidatorConfiguration addProperty(String name, String value) {
+        properties.put(name, value);
+        return this;
+    }
+
+    public MessageInterpolator getDefaultMessageInterpolator() {
+        return defaultMessageInterpolator;
+    }
+
+    public TraversableResolver getDefaultTraversableResolver() {
+        return defaultTraversableResolver;
+    }
+
+    public ConstraintValidatorFactory getDefaultConstraintValidatorFactory() {
+        return defaultConstraintValidatorFactory;
+    }
+
+    public ParameterNameProvider getDefaultParameterNameProvider() {
+        return defaultParameterNameProvider;
+    }
+
+    /**
+     * {@inheritDoc}
+     * Return a map of non type-safe custom properties.
+     *
+     * @return null
+     */
+    public Map<String, String> getProperties() {
+        return properties;
+    }
+
+    /**
+     * {@inheritDoc}
+     * Returns true if Configuration.ignoreXMLConfiguration() has been called.
+     * In this case, we ignore META-INF/validation.xml
+     *
+     * @return true
+     */
+    public boolean isIgnoreXmlConfiguration() {
+        return ignoreXmlConfiguration;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public Set<InputStream> getMappingStreams() {
+        return mappingStreams;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public MessageInterpolator getMessageInterpolator() {
+        return messageInterpolator;
+    }
+
+    public BootstrapConfiguration getBootstrapConfiguration() {
+        return createBootstrapConfiguration();
+    }
+
+    /**
+     * {@inheritDoc}
+     * main factory method to build a ValidatorFactory
+     *
+     * @throws ValidationException if the ValidatorFactory cannot be built
+     */
+    public ValidatorFactory buildValidatorFactory() {
+        if (System.getSecurityManager() == null) {
+            return doPrivBuildValidatorFactory(this);
+        }
+        return AccessController.doPrivileged(new PrivilegedAction<ValidatorFactory>() {
+            public ValidatorFactory run() {
+                return doPrivBuildValidatorFactory(ConfigurationImpl.this);
+            }
+        });
+    }
+
+    public ValidatorFactory doPrivBuildValidatorFactory(final ConfigurationImpl impl) {
+        prepare();
+        parser.ensureValidatorFactoryCanBeBuilt();
+        if (provider != null) {
+            return provider.buildValidatorFactory(impl);
+        } else {
+            return findProvider().buildValidatorFactory(impl);
+        }
+    }
+
+    public ConfigurationImpl prepare() {
+        if (prepared) {
+            return this;
+        }
+
+        createBootstrapConfiguration();
+        parser.applyConfigWithInstantiation(this); // instantiate the config if needed
+
+        // TODO: maybe find a better way to communicate between validator factory and config
+        if (getBootstrapConfiguration().isExecutableValidationEnabled()) {
+            getProperties().put(Properties.EXECUTABLE_VALIDATION_TYPES, executableValidationTypesAsString());
+        }
+
+        prepared = true;
+        return this;
+    }
+
+    private BootstrapConfiguration createBootstrapConfiguration() {
+        if (parser == null) {
+            parser = parseValidationXml(); // already done if BootstrapConfiguration already looked up
+            bootstrapConfiguration = parser.getBootstrap();
+        }
+        return bootstrapConfiguration;
+    }
+
+    /** Check whether a validation.xml file exists and parses it with JAXB */
+    private ValidationParser parseValidationXml() {
+        return ValidationParser.processValidationConfig(getProperties().get(Properties.VALIDATION_XML_PATH), this, ignoreXmlConfiguration);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @return the constraint validator factory of this configuration.
+     */
+    public ConstraintValidatorFactory getConstraintValidatorFactory() {
+        return constraintValidatorFactory;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    public TraversableResolver getTraversableResolver() {
+        return traversableResolver;
+    }
+
+    public ParameterNameProvider getParameterNameProvider() {
+        return parameterNameProvider;
+    }
+
+    /**
+     * Get the configured {@link ValidationProvider}.
+     * @return {@link ValidationProvider}
+     */
+    public ValidationProvider<?> getProvider() {
+        return provider;
+    }
+
+    private ValidationProvider<?> findProvider() {
+        if (providerClass != null) {
+            for (ValidationProvider<?> provider : providerResolver
+                  .getValidationProviders()) {
+                if (providerClass.isAssignableFrom(provider.getClass())) {
+                    return provider;
+                }
+            }
+            throw new ValidationException(
+                  "Unable to find suitable provider: " + providerClass);
+        } else {
+            List<ValidationProvider<?>> providers = providerResolver.getValidationProviders();
+            return providers.get(0);
+        }
+    }
+
+    /**
+     * Set {@link ValidationProvider} class.
+     * @param providerClass
+     */
+    public void setProviderClass(Class<? extends ValidationProvider<?>> providerClass) {
+        this.providerClass = providerClass;
+    }
+
+    public void setExecutableValidation(final Collection<ExecutableType> executableValidation) {
+        this.executableValidation = executableValidation;
+    }
+
+    public Collection<ExecutableType> getExecutableValidation() {
+        return executableValidation;
+    }
+
+    private String executableValidationTypesAsString() {
+        if (executableValidation == null || executableValidation.isEmpty()) {
+            return "";
+        }
+
+        final StringBuilder builder = new StringBuilder();
+        for (final ExecutableType type : executableValidation) {
+            builder.append(type.name()).append(",");
+        }
+        final String s = builder.toString();
+        return s.substring(0, s.length() - 1);
+    }
+
+    public Closeable getClosable() {
+        return parser;
+    }
+
+    public ValidationParser getParser() {
+        return parser;
+    }
+
+    public void setParser(ValidationParser parser) {
+        this.parser = parser;
+    }
+}

Added: bval/branches/bval-11/bval-jsr/src/main/java/org/apache/bval/jsr/ConstraintAnnotationAttributes.java
URL: http://svn.apache.org/viewvc/bval/branches/bval-11/bval-jsr/src/main/java/org/apache/bval/jsr/ConstraintAnnotationAttributes.java?rev=1517540&view=auto
==============================================================================
--- bval/branches/bval-11/bval-jsr/src/main/java/org/apache/bval/jsr/ConstraintAnnotationAttributes.java (added)
+++ bval/branches/bval-11/bval-jsr/src/main/java/org/apache/bval/jsr/ConstraintAnnotationAttributes.java Mon Aug 26 13:59:15 2013
@@ -0,0 +1,221 @@
+/**
+ *  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.jsr;
+
+import org.apache.bval.util.reflection.Reflection;
+import org.apache.commons.lang3.reflect.TypeUtils;
+
+import javax.validation.Constraint;
+import javax.validation.ConstraintTarget;
+import javax.validation.Payload;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Method;
+import java.lang.reflect.Type;
+import java.security.AccessController;
+import java.security.PrivilegedAction;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+/**
+ * Defines the well-known attributes of {@link Constraint} annotations.
+ * 
+ * @version $Rev: 1165923 $ $Date: 2011-09-06 18:07:53 -0500 (Tue, 06 Sep 2011) $
+ */
+public enum ConstraintAnnotationAttributes {
+    /**
+     * "message"
+     */
+    MESSAGE(false, "message"),
+
+    /**
+     * "groups"
+     */
+    GROUPS(false, "groups"),
+
+    /**
+     * "payload"
+     */
+    PAYLOAD(false, "payload"),
+
+    /**
+     * "validationAppliesTo"
+     */
+    VALIDATION_APPLIES_TO(true, "validationAppliesTo"),
+
+    /**
+     * "value" for multi-valued constraints
+     */
+    VALUE(true, "value");
+
+    @SuppressWarnings("unused")
+    private static class Types {
+        String message;
+        Class<?>[] groups;
+        Class<? extends Payload>[] payload;
+        Annotation[] value;
+        ConstraintTarget validationAppliesTo;
+    }
+
+    private final Type type;
+    private final boolean permitNullDefaultValue;
+    private final String attributeName;
+
+    private ConstraintAnnotationAttributes(final boolean permitNullDefaultValue, final String name) {
+        this.permitNullDefaultValue = permitNullDefaultValue;
+        this.attributeName = name;
+        try {
+            this.type = Types.class.getDeclaredField(getAttributeName()).getGenericType();
+        } catch (Exception e) {
+            // should never happen
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
+     * Get the expected type of the represented attribute.
+     */
+    public Type getType() {
+        return type;
+    }
+
+    /**
+     * Get the attribute name represented.
+     * 
+     * @return String
+     */
+    public String getAttributeName() {
+        return attributeName;
+    }
+
+    /**
+     * Put <code>value</code> into a map with <code>this.attributeName</code> as
+     * key.
+     * 
+     * @param <V>
+     * @param map
+     * @param value
+     * @return previous value mapped to <code>this.attributeName</code>
+     */
+    public <V> Object put(Map<? super String, ? super V> map, V value) {
+        return map.put(getAttributeName(), value);
+    }
+
+    /**
+     * Get the value of <code>this.attributeName</code> from <code>map</code>.
+     * 
+     * @param <V>
+     * @param map
+     * @return V if you say so
+     */
+    public <V> V get(Map<? super String, ? super V> map) {
+        @SuppressWarnings("unchecked")
+        final V result = (V) map.get(getAttributeName());
+        if (!TypeUtils.isInstance(result, getType())) {
+            throw new IllegalStateException(String.format("Invalid '%s' value: %s", getAttributeName(), result));
+        }
+        return result;
+    }
+
+    public <C extends Annotation> Worker<C> analyze(final Class<C> clazz) {
+        if (clazz.getName().startsWith("javax.validation.constraint.")) { // cache only APIs classes to avoid memory leaks
+            Worker<C> w = Worker.class.cast(WORKER_CACHE.get(clazz));
+            if (w == null) {
+                w = new Worker<C>(clazz);
+                WORKER_CACHE.putIfAbsent(clazz, w);
+                return w;
+            }
+        }
+        return new Worker<C>(clazz);
+    }
+
+    // this is static but related to Worker
+    private static final ConcurrentMap<Class<?>, Worker<?>> WORKER_CACHE = new ConcurrentHashMap<Class<?>, Worker<?>>();
+    private static final ConcurrentMap<Class<?>, ConcurrentMap<String, Method>> METHOD_BY_NAME_AND_CLASS = new ConcurrentHashMap<Class<?>, ConcurrentMap<String, Method>>();
+    private static final Method NULL_METHOD;
+    static {
+        try {
+            NULL_METHOD = Object.class.getMethod("hashCode"); // whatever, the only constraint here is to not use a constraint method, this value is used to cache null
+        } catch (NoSuchMethodException e) {
+            throw new RuntimeException("Impossible normally");
+        }
+    }
+    public class Worker<C extends Annotation> {
+
+        public final Method method;
+
+        /**
+         * Create a new Worker instance.
+         * @param constraintType to handle
+         */
+        Worker(final Class<C> constraintType) {
+            method = findMethod(constraintType, attributeName);
+        }
+
+        private Method findMethod(final Class<C> constraintType, final String attributeName) {
+            ConcurrentMap<String, Method> cache = METHOD_BY_NAME_AND_CLASS.get(constraintType);
+            if (cache == null) {
+                cache = new ConcurrentHashMap<String, Method>();
+                final ConcurrentMap<String, Method> old = METHOD_BY_NAME_AND_CLASS.putIfAbsent(constraintType, cache);
+                if (old != null) {
+                    cache = old;
+                }
+            }
+
+            final Method found = cache.get(attributeName);
+            if (found != null) {
+                return found;
+            }
+            final Method m = Reflection.INSTANCE.getPublicMethod(constraintType, attributeName);
+            if (m == null) {
+                cache.putIfAbsent(attributeName, NULL_METHOD);
+                return null;
+            }
+            final Method oldMtd = cache.putIfAbsent(attributeName, m);
+            if (oldMtd != null) {
+                return oldMtd;
+            }
+            if (!m.isAccessible()) {
+                m.setAccessible(true);
+            }
+            return m;
+        }
+
+        public boolean isValid() {
+            return method != null && method != NULL_METHOD;
+        }
+
+        public Object read(final Annotation constraint) {
+            if (System.getSecurityManager() == null) {
+                return doInvoke(constraint);
+            }
+            return AccessController.doPrivileged(new PrivilegedAction<Object>() {
+                public Object run() {
+                    return doInvoke(constraint);
+                }
+            });
+        }
+
+        private Object doInvoke(final Annotation constraint) {
+            try {
+                return method.invoke(constraint);
+            } catch (Exception e) {
+                throw new RuntimeException(e);
+            }
+        }
+    }
+}



Mime
View raw message