All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.bval.jsr303.Jsr303MetaBeanFactory Maven / Gradle / Ivy

There is a newer version: 4.0.0
Show newest version
/*
 * 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 org.apache.bval.MetaBeanFactory;
import org.apache.bval.Validate;
import org.apache.bval.jsr303.groups.Group;
import org.apache.bval.jsr303.util.ClassHelper;
import org.apache.bval.jsr303.util.ConstraintDefinitionValidator;
import org.apache.bval.jsr303.util.SecureActions;
import org.apache.bval.jsr303.util.TypeUtils;
import org.apache.bval.jsr303.xml.MetaConstraint;
import org.apache.bval.model.Features;
import org.apache.bval.model.MetaBean;
import org.apache.bval.model.MetaProperty;
import org.apache.bval.util.AccessStrategy;
import org.apache.bval.util.FieldAccess;
import org.apache.bval.util.MethodAccess;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.ClassUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.validation.*;
import javax.validation.groups.Default;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * Description: process the class annotations for JSR303 constraint validations
 * to build the MetaBean with information from annotations and JSR303 constraint
 * mappings (defined in xml)
*/ public class Jsr303MetaBeanFactory implements MetaBeanFactory { /** Shared log instance */ //of dubious utility as it's static :/ protected static final Logger log = LoggerFactory.getLogger(Jsr303MetaBeanFactory.class); /** Constant for the "value" annotation attribute specified in JSR303*/ protected static final String ANNOTATION_VALUE = "value"; public static final String ANNOTATION_PAYLOAD = "payload"; public static final String ANNOTATION_GROUPS = "groups"; public static final String ANNOTATION_MESSAGE = "message"; /** {@link ApacheFactoryContext} used */ protected final ApacheFactoryContext factoryContext; /** * Create a new Jsr303MetaBeanFactory instance. * @param factoryContext */ public Jsr303MetaBeanFactory(ApacheFactoryContext factoryContext) { this.factoryContext = factoryContext; } private ConstraintValidatorFactory getConstraintValidatorFactory() { return factoryContext.getConstraintValidatorFactory(); } private ConstraintDefaults getDefaultConstraints() { return factoryContext.getFactory().getDefaultConstraints(); } /** * {@inheritDoc} * Add the validation features to the metabean that come from JSR303 * annotations in the beanClass. */ public void buildMetaBean(MetaBean metabean) { try { final Class beanClass = metabean.getBeanClass(); processGroupSequence(beanClass, metabean); // process class, superclasses and interfaces List> classSequence = new ArrayList>(); ClassHelper.fillFullClassHierarchyAsList(classSequence, beanClass); // start with superclasses and go down the hierarchy so that // the child classes are processed last to have the chance to overwrite some declarations // of their superclasses and that they see what they inherit at the time of processing for (int i = classSequence.size() - 1; i >= 0; i--) { Class eachClass = classSequence.get(i); processClass(eachClass, metabean); processGroupSequence(eachClass, metabean, "{GroupSequence:"+eachClass.getCanonicalName()+"}"); } } catch (IllegalAccessException e) { throw new IllegalArgumentException(e); } catch (InvocationTargetException e) { throw new IllegalArgumentException(e.getTargetException()); } } /** * Process class annotations, field and method annotations. * @param beanClass * @param metabean * @throws IllegalAccessException * @throws InvocationTargetException */ private void processClass(Class beanClass, MetaBean metabean) throws IllegalAccessException, InvocationTargetException { // if NOT ignore class level annotations if (!factoryContext.getFactory().getAnnotationIgnores() .isIgnoreAnnotations(beanClass)) { processAnnotations(null, beanClass, beanClass, null, new AppendValidationToMeta(metabean)); } final Field[] fields = SecureActions.getDeclaredFields(beanClass); for (Field field : fields) { MetaProperty metaProperty = metabean.getProperty(field.getName()); // create a property for those fields for which there is not yet a MetaProperty if (!factoryContext.getFactory().getAnnotationIgnores() .isIgnoreAnnotations(field)) { if (metaProperty == null) { metaProperty = addMetaProperty(metabean, field.getName(), field.getType()); processAnnotations(metaProperty, beanClass, field, new FieldAccess(field), new AppendValidationToMeta(metaProperty));//) { } else { processAnnotations(metaProperty, beanClass, field, new FieldAccess(field), new AppendValidationToMeta(metaProperty)); } } } final Method[] methods = SecureActions.getDeclaredMethods(beanClass); for (Method method : methods) { String propName = null; if (method.getParameterTypes().length == 0) { propName = MethodAccess.getPropertyName(method); } if (propName != null) { if (!factoryContext.getFactory().getAnnotationIgnores() .isIgnoreAnnotations(method)) { MetaProperty metaProperty = metabean.getProperty(propName); // create a property for those methods for which there is not yet a MetaProperty if (metaProperty == null) { metaProperty = addMetaProperty(metabean, propName, method.getReturnType()); processAnnotations(metaProperty, beanClass, method, new MethodAccess(propName, method), new AppendValidationToMeta(metaProperty));//) { } else { processAnnotations(metaProperty, beanClass, method, new MethodAccess(propName, method), new AppendValidationToMeta(metaProperty)); } } } else if ( hasValidationConstraintsDefined(method) ) { throw new ValidationException("Property " + method.getName() + " does not follow javabean conventions."); } } addXmlConstraints(beanClass, metabean); } /** * Learn whether a given Method has validation constraints defined via JSR303 annotations. * @param method * @return true if constraints detected */ protected boolean hasValidationConstraintsDefined(Method method) { boolean ret = false; for ( Annotation annot : method.getDeclaredAnnotations() ) { if ( true == (ret = hasValidationConstraintsDefined(annot)) ) { break; } } return ret; } private boolean hasValidationConstraintsDefined(Annotation annot) { // If it is annotated with @Constraint if ( annot.annotationType().getAnnotation(Constraint.class) != null ) { return true; } boolean ret = false; // Check in case it is a multivalued constraint Object value = null; try { value = SecureActions.getAnnotationValue(annot, ANNOTATION_VALUE); } catch (IllegalAccessException e) { // Swallow it } catch (InvocationTargetException e) { // Swallow it } if ( value instanceof Annotation[] ) { for (Annotation annot2 : (Annotation[])value ) { if ( true == (ret = hasValidationConstraintsDefined(annot2)) ) { break; } } } return ret; } /** * Add cascade validation and constraints from xml mappings * @param beanClass * @param metabean * @throws IllegalAccessException * @throws InvocationTargetException */ @SuppressWarnings("unchecked") private void addXmlConstraints(Class beanClass, MetaBean metabean) throws IllegalAccessException, InvocationTargetException { for (MetaConstraint meta : factoryContext.getFactory() .getMetaConstraints(beanClass)) { MetaProperty metaProperty; if (meta.getAccessStrategy() == null) { // class level metaProperty = null; } else { // property level metaProperty = metabean.getProperty(meta.getAccessStrategy().getPropertyName()); if (metaProperty == null) { metaProperty = addMetaProperty(metabean, meta.getAccessStrategy().getPropertyName(), meta.getAccessStrategy().getJavaType()); } } Class>[] validatorClasses = findConstraintValidatorClasses(meta.getAnnotation(), null); applyConstraint( (Annotation) meta.getAnnotation(), (Class>[]) validatorClasses, metaProperty, beanClass, meta.getAccessStrategy(), new AppendValidationToMeta(metaProperty == null ? metabean : metaProperty)); } for (AccessStrategy access : factoryContext.getFactory().getValidAccesses(beanClass)) { MetaProperty metaProperty = metabean.getProperty(access.getPropertyName()); if (metaProperty == null) { metaProperty = addMetaProperty(metabean, access.getPropertyName(), access.getJavaType()); } processValid(metaProperty, access); } } private MetaProperty addMetaProperty(MetaBean parentMetaBean, String propName, Type type) { MetaProperty metaProperty; metaProperty = new MetaProperty(); metaProperty.setName(propName); metaProperty.setType(type); parentMetaBean.putProperty(propName, metaProperty); return metaProperty; } private boolean processAnnotations(MetaProperty prop, Class owner, AnnotatedElement element, AccessStrategy access, AppendValidation appender) throws IllegalAccessException, InvocationTargetException { boolean changed = false; for (Annotation annotation : element.getDeclaredAnnotations()) { changed |= processAnnotation(annotation, prop, owner, access, appender); } return changed; } private boolean processAnnotation(A annotation, MetaProperty prop, Class owner, AccessStrategy access, AppendValidation appender) throws IllegalAccessException, InvocationTargetException { if (annotation instanceof Valid) { return processValid(prop, access); } else if (annotation instanceof Validate) { return processValid(prop, access, ((Validate)annotation).groups()); } else { /** * An annotation is considered a constraint definition if its retention * policy contains RUNTIME and if the annotation itself is annotated with * javax.validation.Constraint. */ Constraint vcAnno = annotation.annotationType().getAnnotation(Constraint.class); if (vcAnno != null) { ConstraintDefinitionValidator.validateConstraintDefinition(annotation); Class>[] validatorClasses; validatorClasses = findConstraintValidatorClasses(annotation, vcAnno); return applyConstraint(annotation, validatorClasses, prop, owner, access, appender); } else { /** * Multi-valued constraints: * To support this requirement, the bean validation provider treats * regular annotations (annotations not annotated by @Constraint) * whose value element has a return type of an array of * constraint annotations in a special way. */ Object result = SecureActions.getAnnotationValue(annotation, ANNOTATION_VALUE); if (result != null && result instanceof Annotation[]) { boolean changed = false; for (Annotation each : (Annotation[]) result) { changed |= processAnnotation(each, prop, owner, access, appender); } return changed; } } } return false; } /** * Find available {@link ConstraintValidation} classes for a given constraint annotation. * @param annotation * @param vcAnno * @return {@link ConstraintValidation} implementation class array */ @SuppressWarnings("unchecked") protected Class>[] findConstraintValidatorClasses( A annotation, Constraint vcAnno) { if (vcAnno == null) { vcAnno = annotation.annotationType().getAnnotation(Constraint.class); } Class>[] validatorClasses; Class annotationType = (Class) annotation.annotationType(); validatorClasses = factoryContext.getFactory() .getConstraintsCache() .getConstraintValidators(annotationType); if (validatorClasses == null) { validatorClasses = (Class>[]) vcAnno.validatedBy(); if (validatorClasses.length == 0) { validatorClasses = getDefaultConstraints() .getValidatorClasses(annotationType); } } return validatorClasses; } private boolean processValid(MetaProperty prop, AccessStrategy access, Class... groups) { if (prop != null/* && prop.getMetaBean() == null*/) { AccessStrategy[] strategies = prop.getFeature(Features.Property.REF_CASCADE); prop.putFeature(Jsr303Features.Property.REF_GROUPS, groups); if (strategies == null) { strategies = new AccessStrategy[]{access}; prop.putFeature(Features.Property.REF_CASCADE, strategies); } else { if (!ArrayUtils.contains(strategies, access)) { AccessStrategy[] strategies_new = new AccessStrategy[strategies.length + 1]; System.arraycopy(strategies, 0, strategies_new, 0, strategies.length); strategies_new[strategies.length] = access; prop.putFeature(Features.Property.REF_CASCADE, strategies_new); } } return true; } return false; } private void processGroupSequence(Class beanClass, MetaBean metabean) { processGroupSequence(beanClass, metabean, Jsr303Features.Bean.GROUP_SEQUENCE); } private void processGroupSequence(Class beanClass, MetaBean metabean, String key) { GroupSequence annotation = beanClass.getAnnotation(GroupSequence.class); List groupSeq = metabean.getFeature(key); if (groupSeq == null) { groupSeq = new ArrayList(annotation == null ? 1 : annotation.value().length); metabean.putFeature(key, groupSeq); } Class[] groupClasses = factoryContext.getFactory().getDefaultSequence(beanClass); if (groupClasses == null || groupClasses.length == 0) { if (annotation == null) { groupSeq.add(Group.DEFAULT); return; } else { groupClasses = annotation.value(); } } boolean containsDefault = false; for (Class groupClass : groupClasses) { if (groupClass.getName().equals(beanClass.getName())) { groupSeq.add(Group.DEFAULT); containsDefault = true; } else if (groupClass.getName().equals(Default.class.getName())) { throw new GroupDefinitionException( "'Default.class' must not appear in @GroupSequence! Use '" + beanClass.getSimpleName() + ".class' instead."); } else { groupSeq.add(new Group(groupClass)); } } if (!containsDefault) { throw new GroupDefinitionException( "Redefined default group sequence must contain " + beanClass.getName()); } log.debug("Default group sequence for bean {} is: {}", beanClass.getName(), groupSeq); } /** * Apply a constraint to the specified appender. * @param annotation constraint annotation * @param constraintClasses known {@link ConstraintValidator} implementation classes for annotation * @param prop meta-property * @param owner type * @param access strategy * @param appender * @return success flag * @throws IllegalAccessException * @throws InvocationTargetException */ protected boolean applyConstraint(A annotation, Class>[] constraintClasses, MetaProperty prop, Class owner, AccessStrategy access, AppendValidation appender) throws IllegalAccessException, InvocationTargetException { final ConstraintValidator validator; if (constraintClasses != null && constraintClasses.length > 0) { Type type = determineTargetedType(owner, access); /** * spec says in chapter 3.5.3.: * The ConstraintValidator chosen to validate a * declared type T is the one where the type supported by the * ConstraintValidator is a supertype of T and where * there is no other ConstraintValidator whose supported type is a * supertype of T and not a supertype of the chosen * ConstraintValidator supported type. */ Map>> validatorTypes = (Map>>) TypeUtils.getValidatorsTypes(constraintClasses); final List assignableTypes = new ArrayList(constraintClasses.length); fillAssignableTypes(type, validatorTypes.keySet(), assignableTypes); reduceAssignableTypes(assignableTypes); checkOneType(assignableTypes, type, owner, annotation, access); validator = getConstraintValidatorFactory() .getInstance(validatorTypes.get(assignableTypes.get(0))); if ( validator == null ) { throw new ValidationException("Factory returned null validator for: " + validatorTypes.get(assignableTypes.get(0))); } // NOTE: validator initialization deferred until append phase } else { validator = null; } final AnnotationConstraintBuilder builder = new AnnotationConstraintBuilder( constraintClasses, validator, annotation, owner, access); // JSR-303 3.4.4: Add implicit groups if ( prop != null && prop.getParentMetaBean() != null ) { MetaBean parentMetaBean = prop.getParentMetaBean(); // If: // - the owner is an interface // - the class of the metabean being build is different than the owner // - and only the Default group is defined // Then: add the owner interface as implicit groups if ( builder.getConstraintValidation().getOwner().isInterface() && parentMetaBean.getBeanClass() != builder.getConstraintValidation().getOwner() && builder.getConstraintValidation().getGroups().size() == 1 && builder.getConstraintValidation().getGroups().contains(Default.class) ) { Set> groups = builder.getConstraintValidation().getGroups(); groups.add(builder.getConstraintValidation().getOwner()); builder.getConstraintValidation().setGroups(groups); } } // If already building a constraint composition tree, ensure that: // - the parent groups are inherited // - the parent payload is inherited if ( appender instanceof AppendValidationToBuilder ) { AppendValidationToBuilder avb = (AppendValidationToBuilder) appender; builder.getConstraintValidation().setGroups(avb.getInheritedGroups()); builder.getConstraintValidation().setPayload(avb.getInheritedPayload()); } // process composed constraints: // here are not other superclasses possible, because annotations do not inherit! processAnnotations(prop, owner, annotation.annotationType(), access, new AppendValidationToBuilder(builder)); // Even if the validator is null, it must be added to mimic the RI impl appender.append(builder.getConstraintValidation()); return true; } private void checkOneType(List types, Type targetType, Class owner, Annotation anno, AccessStrategy access) { if (types.isEmpty()) { StringBuilder buf = new StringBuilder() .append("No validator could be found for type ") .append(stringForType(targetType)) .append(". See: @") .append(anno.annotationType().getSimpleName()) .append(" at ").append(stringForLocation(owner, access)); throw new UnexpectedTypeException(buf.toString()); } else if (types.size() > 1) { StringBuilder buf = new StringBuilder(); buf.append("Ambiguous validators for type "); buf.append(stringForType(targetType)); buf.append(". See: @") .append(anno.annotationType().getSimpleName()) .append(" at ").append(stringForLocation(owner, access)); buf.append(". Validators are: "); boolean comma = false; for (Type each : types) { if (comma) buf.append(", "); comma = true; buf.append(each); } throw new UnexpectedTypeException(buf.toString()); } } /** implements spec chapter 3.5.3. ConstraintValidator resolution algorithm. */ private Type determineTargetedType(Class owner, AccessStrategy access) { // if the constraint declaration is hosted on a class or an interface, // the targeted type is the class or the interface. if (access == null) return owner; Type type = access.getJavaType(); if (type == null) return Object.class; if (type instanceof Class) type = ClassUtils.primitiveToWrapper((Class) type); return type; } private String stringForType(Type clazz) { if (clazz instanceof Class) { if (((Class) clazz).isArray()) { return ((Class) clazz).getComponentType().getName() + "[]"; } else { return ((Class) clazz).getName(); } } else { return clazz.toString(); } } private String stringForLocation(Class owner, AccessStrategy access) { if (access != null) { return access.toString(); } else { return owner.getName(); } } private void fillAssignableTypes(Type type, Set validatorsTypes, List suitableTypes) { for (Type validatorType : validatorsTypes) { if (TypeUtils.isAssignable(validatorType, type) && !suitableTypes.contains(validatorType)) { suitableTypes.add(validatorType); } } } /** * Tries to reduce all assignable classes down to a single class. * * @param assignableTypes The set of all classes which are assignable to the class of the value to be validated and * which are handled by at least one of the validators for the specified constraint. */ private void reduceAssignableTypes(List assignableTypes) { if (assignableTypes.size() <= 1) { return; // no need to reduce } boolean removed; do { removed = false; final Type type = assignableTypes.get(0); for (int i = 1; i < assignableTypes.size(); i++) { Type nextType = assignableTypes.get(i); if (TypeUtils.isAssignable(type, nextType)) { assignableTypes.remove(0); i--; removed = true; } else if (TypeUtils.isAssignable(nextType, type)) { assignableTypes.remove(i--); removed = true; } } } while (removed && assignableTypes.size() > 1); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy