Java tutorial
/* * 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.MetaBeanFactory; import org.apache.bval.jsr.groups.Group; import org.apache.bval.jsr.util.ClassHelper; import org.apache.bval.jsr.xml.MetaConstraint; import org.apache.bval.model.Meta; import org.apache.bval.model.MetaBean; import org.apache.bval.model.MetaConstructor; import org.apache.bval.model.MetaMethod; import org.apache.bval.model.MetaParameter; import org.apache.bval.model.MetaProperty; import org.apache.bval.util.AccessStrategy; import org.apache.bval.util.FieldAccess; import org.apache.bval.util.MethodAccess; import org.apache.bval.util.reflection.Reflection; import org.apache.commons.weaver.privilizer.Privilizing; import org.apache.commons.weaver.privilizer.Privilizing.CallTo; import javax.validation.ConstraintDeclarationException; import javax.validation.GroupDefinitionException; import javax.validation.GroupSequence; import javax.validation.groups.ConvertGroup; import javax.validation.groups.Default; import java.lang.annotation.Annotation; import java.lang.annotation.ElementType; import java.lang.reflect.Constructor; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * Description: process the class annotations for JSR303 constraint validations to build the MetaBean with information * from annotations and JSR303 constraint mappings (defined in xml)<br/> */ @Privilizing(@CallTo(Reflection.class)) public class JsrMetaBeanFactory implements MetaBeanFactory { /** Shared log instance */ // of dubious utility as it's static :/ protected static final Logger log = Logger.getLogger(JsrMetaBeanFactory.class.getName()); /** {@link javax.validation.ValidatorFactory} used */ protected final ApacheValidatorFactory factory; /** * {@link AnnotationProcessor} used. */ protected AnnotationProcessor annotationProcessor; /** * Create a new Jsr303MetaBeanFactory instance. * * @param factory the validator factory. */ public JsrMetaBeanFactory(ApacheValidatorFactory factory) { this.factory = factory; this.annotationProcessor = new AnnotationProcessor(factory); } /** * {@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 final List<Class<?>> classSequence = ClassHelper.fillFullClassHierarchyAsList(new ArrayList<Class<?>>(), 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 (!factory.getAnnotationIgnores().isIgnoreAnnotations(beanClass)) { annotationProcessor.processAnnotations(null, beanClass, beanClass, null, new AppendValidationToMeta(metabean)); } final Collection<String> missingValid = new ArrayList<String>(); final Field[] fields = Reflection.getDeclaredFields(beanClass); for (final Field field : fields) { MetaProperty metaProperty = metabean.getProperty(field.getName()); // create a property for those fields for which there is not yet a // MetaProperty if (!factory.getAnnotationIgnores().isIgnoreAnnotations(field)) { AccessStrategy access = new FieldAccess(field); boolean create = metaProperty == null; if (create) { metaProperty = addMetaProperty(metabean, access); } if (!annotationProcessor.processAnnotations(metaProperty, beanClass, field, access, new AppendValidationToMeta(metaProperty)) && create) { metabean.putProperty(metaProperty.getName(), null); } if (field.getAnnotation(ConvertGroup.class) != null) { missingValid.add(field.getName()); } } } final Method[] methods = Reflection.getDeclaredMethods(beanClass); for (final Method method : methods) { if (method.isSynthetic() || method.isBridge()) { continue; } String propName = null; if (method.getParameterTypes().length == 0) { propName = MethodAccess.getPropertyName(method); } if (propName != null) { if (!factory.getAnnotationIgnores().isIgnoreAnnotations(method)) { AccessStrategy access = new MethodAccess(propName, method); MetaProperty metaProperty = metabean.getProperty(propName); boolean create = metaProperty == null; // create a property for those methods for which there is // not yet a MetaProperty if (create) { metaProperty = addMetaProperty(metabean, access); } if (!annotationProcessor.processAnnotations(metaProperty, beanClass, method, access, new AppendValidationToMeta(metaProperty)) && create) { metabean.putProperty(propName, null); } } } } addXmlConstraints(beanClass, metabean); for (final String name : missingValid) { final MetaProperty metaProperty = metabean.getProperty(name); if (metaProperty != null && metaProperty.getFeature(JsrFeatures.Property.REF_CASCADE) == null) { throw new ConstraintDeclarationException("@ConvertGroup needs @Valid"); } } missingValid.clear(); } /** * Add cascade validation and constraints from xml mappings * * @param beanClass * @param metabean * @throws IllegalAccessException * @throws InvocationTargetException */ private void addXmlConstraints(Class<?> beanClass, MetaBean metabean) throws IllegalAccessException, InvocationTargetException { for (final MetaConstraint<?, ? extends Annotation> metaConstraint : factory.getMetaConstraints(beanClass)) { Meta meta; AccessStrategy access = metaConstraint.getAccessStrategy(); boolean create = false; if (access == null) { // class level meta = null; } else if (access.getElementType() == ElementType.METHOD && !metaConstraint.getMember().getName().startsWith("get")) { // TODO: better getter test final Method method = Method.class.cast(metaConstraint.getMember()); meta = metabean.getMethod(method); final MetaMethod metaMethod; if (meta == null) { meta = new MetaMethod(metabean, method); metaMethod = MetaMethod.class.cast(meta); metabean.addMethod(method, metaMethod); } else { metaMethod = MetaMethod.class.cast(meta); } final Integer index = metaConstraint.getIndex(); if (index != null && index >= 0) { MetaParameter param = metaMethod.getParameter(index); if (param == null) { param = new MetaParameter(metaMethod, index); metaMethod.addParameter(index, param); } param.addAnnotation(metaConstraint.getAnnotation()); } else { metaMethod.addAnnotation(metaConstraint.getAnnotation()); } continue; } else if (access.getElementType() == ElementType.CONSTRUCTOR) { final Constructor<?> constructor = Constructor.class.cast(metaConstraint.getMember()); meta = metabean.getConstructor(constructor); final MetaConstructor metaConstructor; if (meta == null) { meta = new MetaConstructor(metabean, constructor); metaConstructor = MetaConstructor.class.cast(meta); metabean.addConstructor(constructor, metaConstructor); } else { metaConstructor = MetaConstructor.class.cast(meta); } final Integer index = metaConstraint.getIndex(); if (index != null && index >= 0) { MetaParameter param = metaConstructor.getParameter(index); if (param == null) { param = new MetaParameter(metaConstructor, index); metaConstructor.addParameter(index, param); } param.addAnnotation(metaConstraint.getAnnotation()); } else { metaConstructor.addAnnotation(metaConstraint.getAnnotation()); } continue; } else { // property level meta = metabean.getProperty(access.getPropertyName()); create = meta == null; if (create) { meta = addMetaProperty(metabean, access); } } if (!annotationProcessor.processAnnotation(metaConstraint.getAnnotation(), meta, beanClass, metaConstraint.getAccessStrategy(), new AppendValidationToMeta(meta == null ? metabean : meta), false) && create) { metabean.putProperty(access.getPropertyName(), null); } } for (final AccessStrategy access : factory.getValidAccesses(beanClass)) { if (access.getElementType() == ElementType.PARAMETER) { continue; } MetaProperty metaProperty = metabean.getProperty(access.getPropertyName()); boolean create = metaProperty == null; if (create) { metaProperty = addMetaProperty(metabean, access); } if (!annotationProcessor.addAccessStrategy(metaProperty, access) && create) { metabean.putProperty(access.getPropertyName(), null); } } } private void processGroupSequence(Class<?> beanClass, MetaBean metabean) { processGroupSequence(beanClass, metabean, JsrFeatures.Bean.GROUP_SEQUENCE); } private void processGroupSequence(Class<?> beanClass, MetaBean metabean, String key) { GroupSequence annotation = beanClass.getAnnotation(GroupSequence.class); List<Group> groupSeq = metabean.getFeature(key); if (groupSeq == null) { groupSeq = metabean.initFeature(key, new ArrayList<Group>(annotation == null ? 1 : annotation.value().length)); } Class<?>[] groupClasses = factory.getDefaultSequence(beanClass); if (groupClasses == null || groupClasses.length == 0) { if (annotation == null) { groupSeq.add(Group.DEFAULT); return; } else { groupClasses = annotation.value(); } } boolean containsDefault = false; for (final 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.log(Level.FINEST, String.format("Default group sequence for bean %s is: %s", beanClass.getName(), groupSeq)); } /** * Add a {@link MetaProperty} to a {@link MetaBean}. * @param parentMetaBean * @param access * @return the created {@link MetaProperty} */ public static MetaProperty addMetaProperty(MetaBean parentMetaBean, AccessStrategy access) { final MetaProperty result = new MetaProperty(); final String name = access.getPropertyName(); result.setName(name); result.setType(access.getJavaType()); parentMetaBean.putProperty(name, result); return result; } }