You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@bval.apache.org by rm...@apache.org on 2013/08/26 15:59:20 UTC
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...
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);
+ }
+ }
+ }
+}