Package org.apache.bval.jsr303

Source Code of org.apache.bval.jsr303.ClassValidator

/*
* 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.reflect.Constructor;
import java.lang.reflect.Modifier;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import javax.validation.ConstraintViolation;
import javax.validation.ValidationException;
import javax.validation.groups.Default;
import javax.validation.metadata.BeanDescriptor;

import org.apache.bval.DynamicMetaBean;
import org.apache.bval.MetaBeanFinder;
import org.apache.bval.jsr303.groups.Group;
import org.apache.bval.jsr303.groups.Groups;
import org.apache.bval.jsr303.groups.GroupsComputer;
import org.apache.bval.jsr303.util.ClassHelper;
import org.apache.bval.jsr303.util.NodeImpl;
import org.apache.bval.jsr303.util.PathImpl;
import org.apache.bval.jsr303.util.PathNavigation;
import org.apache.bval.jsr303.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.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;

// 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: 1166451 $ $Date: 2011-09-07 17:32:26 -0500 (Wed, 07 Sep 2011) $
*
* @author Roman Stumm
* @author Carlos Vara
*/
public class ClassValidator implements CascadingPropertyValidator {
    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();

    /**
     * Create a new ClassValidator instance.
     *
     * @param factoryContext
     */
    public ClassValidator(ApacheFactoryContext factoryContext) {
        this.factoryContext = factoryContext;
    }

    /**
     * Create a new ClassValidator instance.
     *
     * @param factory
     * @deprecated provided for backward compatibility
     */
    public ClassValidator(ApacheValidatorFactory factory) {
        this(factory.usingContext());
    }

    /**
     * Get the metabean finder associated with this validator.
     *
     * @return a MetaBeanFinder
     * @see org.apache.bval.MetaBeanManagerFactory#getFinder()
     */
    protected MetaBeanFinder getMetaBeanFinder() {
        return 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 {

            Class<T> objectClass = (Class<T>) object.getClass();
            MetaBean objectMetaBean = getMetaBeanFinder().findForClass(objectClass);

            final GroupValidationContext<T> context = createContext(objectMetaBean, object, objectClass, groups);
            final ConstraintValidationListener<T> result = context.getListener();
            final Groups sequence = context.getGroups();

            // 1. process groups
            for (Group current : sequence.getGroups()) {
                context.setCurrentGroup(current);
                validateBeanNet(context);
            }

            // 2. process sequences
            for (List<Group> eachSeq : sequence.getSequences()) {
                for (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();
        } catch (RuntimeException ex) {
            throw unrecoverableValidationError(ex, object);
        }
    }

    /**
     * {@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(Class<?> clazz) {
        if (clazz == null) {
            throw new IllegalArgumentException("Class cannot be null");
        }
        try {
            MetaBean metaBean = getMetaBeanFinder().findForClass(clazz);
            BeanDescriptorImpl edesc = metaBean.getFeature(Jsr303Features.Bean.BEAN_DESCRIPTOR);
            if (edesc == null) {
                edesc = createBeanDescriptor(metaBean);
                metaBean.putFeature(Jsr303Features.Bean.BEAN_DESCRIPTOR, edesc);
            }
            return edesc;
        } catch (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())) {
            @SuppressWarnings("unchecked")
            final T result = (T) this;
            return result;
        } 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");
        }
    }

    private <T> T newInstance(final Class<T> cls) {
        return AccessController.doPrivileged(new PrivilegedAction<T>() {
            public T run() {
                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 validationContext 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 = (ConstraintValidationListener<?>) 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
                Group currentGroup = context.getCurrentGroup();
                for (Group each : defaultGroups) {
                    context.setCurrentGroup(each);
                    ValidationHelper.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
                List<Class<?>> classHierarchy = new ArrayList<Class<?>>();
                ClassHelper.fillFullClassHierarchyAsList(classHierarchy, context.getMetaBean().getBeanClass());
                Class<?> initialOwner = context.getCurrentOwner();

                // For each owner in the hierarchy
                for (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
                    List<Group> ownerDefaultGroups =
                        context.getMetaBean().getFeature("{GroupSequence:" + owner.getCanonicalName() + "}");
                    for (Group each : ownerDefaultGroups) {
                        context.setCurrentGroup(each);
                        ValidationHelper.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 {
            ValidationHelper.validateBean(context);
        }

        // ### Then, the cascaded beans (@Valid)
        for (MetaProperty prop : context.getMetaBean().getProperties()) {
            validateCascadedBean(context, prop);
        }

    }

    /**
     * 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(GroupValidationContext<?> context, MetaProperty prop) {
        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(Jsr303Features.Property.REF_GROUPS);
            for (AccessStrategy each : access) {
                if (isCascadable(context, prop, each)) {
                    // modify context state for relationship-target bean
                    context.moveDown(prop, each);
                    // Now, if the related bean is an instance of Map/Array/etc,
                    ValidationHelper
                        .validateContext(context, new Jsr303ValidationCallback(context), 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();
        NodeImpl node = new NodeImpl(prop.getName());
        if (beanPath == null) {
            beanPath = PathImpl.create(null);
        }
        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(Jsr303Features.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) {
        ConstraintValidationListener<T> listener = new ConstraintValidationListener<T>(object, objectClass);
        GroupValidationContextImpl<T> context =
            new GroupValidationContextImpl<T>(listener, this.factoryContext.getMessageInterpolator(),
                this.factoryContext.getTraversableResolver(), metaBean);
        context.setBean(object, metaBean);
        context.setGroups(groupsComputer.computeGroups(groups));
        return context;
    }

    /**
     * Create a {@link BeanDescriptorImpl}
     *
     * @param metaBean
     * @return {@link BeanDescriptorImpl} instance
     */
    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.");
        }
    }

    /**
     * Dispatches a call from {@link #validate()} to {@link ClassValidator#validateBeanNet(GroupValidationContext)} with
     * the current context set.
     */
    protected class Jsr303ValidationCallback implements ValidationHelper.ValidateCallback {

        private final GroupValidationContext<?> context;

        public Jsr303ValidationCallback(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(getMetaBeanFinder());
            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(getMetaBeanFinder().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));
        }
    }

}
TOP

Related Classes of org.apache.bval.jsr303.ClassValidator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.