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

org.apache.bval.jsr303.xml.ValidationMappingParser Maven / Gradle / Ivy

There is a newer version: 4.0.1
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.xml;


import java.io.InputStream;
import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.Payload;
import javax.validation.ValidationException;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;

import org.apache.bval.jsr303.ApacheValidatorFactory;
import org.apache.bval.jsr303.ConstraintAnnotationAttributes;
import org.apache.bval.jsr303.util.EnumerationConverter;
import org.apache.bval.jsr303.util.IOUtils;
import org.apache.bval.jsr303.util.SecureActions;
import org.apache.bval.util.FieldAccess;
import org.apache.bval.util.MethodAccess;
import org.apache.commons.beanutils.ConvertUtils;
import org.apache.commons.beanutils.Converter;
import org.apache.commons.lang3.StringUtils;


/**
 * Uses JAXB to parse constraints.xml based on validation-mapping-1.0.xsd.
*/ @SuppressWarnings("restriction") public class ValidationMappingParser { // private static final Log log = LogFactory.getLog(ValidationMappingParser.class); private static final String VALIDATION_MAPPING_XSD = "META-INF/validation-mapping-1.0.xsd"; private static final Set RESERVED_PARAMS = Collections.unmodifiableSet(EnumSet.of( ConstraintAnnotationAttributes.GROUPS, ConstraintAnnotationAttributes.MESSAGE, ConstraintAnnotationAttributes.PAYLOAD)); private final Set> processedClasses; private final ApacheValidatorFactory factory; /** * Create a new ValidationMappingParser instance. * @param factory */ public ValidationMappingParser(ApacheValidatorFactory factory) { this.factory = factory; this.processedClasses = new HashSet>(); } /** * Parse files with constraint mappings and collect information in the factory. * * @param xmlStreams - one or more contraints.xml file streams to parse */ public void processMappingConfig(Set xmlStreams) throws ValidationException { for (InputStream xmlStream : xmlStreams) { ConstraintMappingsType mapping = parseXmlMappings(xmlStream); String defaultPackage = mapping.getDefaultPackage(); processConstraintDefinitions(mapping.getConstraintDefinition(), defaultPackage); for (BeanType bean : mapping.getBean()) { Class beanClass = loadClass(bean.getClazz(), defaultPackage); if (!processedClasses.add(beanClass)) { // spec: A given class must not be described more than once amongst all // the XML mapping descriptors. throw new ValidationException( beanClass.getName() + " has already be configured in xml."); } factory.getAnnotationIgnores() .setDefaultIgnoreAnnotation(beanClass, bean.isIgnoreAnnotations()); processClassLevel(bean.getClassType(), beanClass, defaultPackage); processFieldLevel(bean.getField(), beanClass, defaultPackage); processPropertyLevel(bean.getGetter(), beanClass, defaultPackage); processedClasses.add(beanClass); } } } /** @param in XML stream to parse using the validation-mapping-1.0.xsd */ private ConstraintMappingsType parseXmlMappings(InputStream in) { ConstraintMappingsType mappings; try { JAXBContext jc = JAXBContext.newInstance(ConstraintMappingsType.class); Unmarshaller unmarshaller = jc.createUnmarshaller(); unmarshaller.setSchema(getSchema()); StreamSource stream = new StreamSource(in); JAXBElement root = unmarshaller.unmarshal(stream, ConstraintMappingsType.class); mappings = root.getValue(); } catch (JAXBException e) { throw new ValidationException("Failed to parse XML deployment descriptor file.", e); } finally { IOUtils.closeQuietly(in); } return mappings; } /** @return validation-mapping-1.0.xsd based schema */ private Schema getSchema() { return ValidationParser.getSchema(VALIDATION_MAPPING_XSD); } private void processClassLevel(ClassType classType, Class beanClass, String defaultPackage) { if (classType == null) { return; } // ignore annotation if (classType.isIgnoreAnnotations() != null) { factory.getAnnotationIgnores() .setIgnoreAnnotationsOnClass(beanClass, classType.isIgnoreAnnotations()); } // group sequence Class[] groupSequence = createGroupSequence(classType.getGroupSequence(), defaultPackage); if (groupSequence != null) { factory.addDefaultSequence(beanClass, groupSequence); } // constraints for (ConstraintType constraint : classType.getConstraint()) { MetaConstraint metaConstraint = createConstraint(constraint, beanClass, null, defaultPackage); factory.addMetaConstraint(beanClass, metaConstraint); } } @SuppressWarnings("unchecked") private MetaConstraint createConstraint( ConstraintType constraint, Class beanClass, Member member, String defaultPackage) { Class annotationClass = (Class) loadClass(constraint.getAnnotation(), defaultPackage); AnnotationProxyBuilder annoBuilder = new AnnotationProxyBuilder(annotationClass); if (constraint.getMessage() != null) { annoBuilder.setMessage(constraint.getMessage()); } annoBuilder.setGroups(getGroups(constraint.getGroups(), defaultPackage)); annoBuilder.setPayload(getPayload(constraint.getPayload(), defaultPackage)); for (ElementType elementType : constraint.getElement()) { String name = elementType.getName(); checkValidName(name); Class returnType = getAnnotationParameterType(annotationClass, name); Object elementValue = getElementValue(elementType, returnType, defaultPackage); annoBuilder.putValue(name, elementValue); } return new MetaConstraint(beanClass, member, annoBuilder.createAnnotation()); } private void checkValidName(String name) { for (ConstraintAnnotationAttributes attr : RESERVED_PARAMS) { if (attr.getAttributeName().equals(name)) { throw new ValidationException(name + " is a reserved parameter name."); } } } private Class getAnnotationParameterType( final Class annotationClass, final String name) { final Method m = doPrivileged(SecureActions.getPublicMethod(annotationClass, name)); if (m == null) { throw new ValidationException("Annotation of type " + annotationClass.getName() + " does not contain a parameter " + name + "."); } return m.getReturnType(); } private Object getElementValue(ElementType elementType, Class returnType, String defaultPackage) { removeEmptyContentElements(elementType); boolean isArray = returnType.isArray(); if (!isArray) { if (elementType.getContent().size() != 1) { throw new ValidationException( "Attempt to specify an array where single value is expected."); } return getSingleValue(elementType.getContent().get(0), returnType, defaultPackage); } else { List values = new ArrayList(); for (Serializable s : elementType.getContent()) { values.add(getSingleValue(s, returnType.getComponentType(), defaultPackage)); } return values.toArray( (Object[]) Array.newInstance(returnType.getComponentType(), values.size())); } } private void removeEmptyContentElements(ElementType elementType) { List contentToDelete = new ArrayList(); for (Serializable content : elementType.getContent()) { if (content instanceof String && ((String) content).matches("[\\n ].*")) { contentToDelete.add(content); } } elementType.getContent().removeAll(contentToDelete); } @SuppressWarnings("unchecked") private Object getSingleValue(Serializable serializable, Class returnType, String defaultPackage) { Object returnValue; if (serializable instanceof String) { String value = (String) serializable; returnValue = convertToResultType(returnType, value, defaultPackage); } else if (serializable instanceof JAXBElement && ((JAXBElement) serializable).getDeclaredType() .equals(String.class)) { JAXBElement elem = (JAXBElement) serializable; String value = (String) elem.getValue(); returnValue = convertToResultType(returnType, value, defaultPackage); } else if (serializable instanceof JAXBElement && ((JAXBElement) serializable).getDeclaredType() .equals(AnnotationType.class)) { JAXBElement elem = (JAXBElement) serializable; AnnotationType annotationType = (AnnotationType) elem.getValue(); try { Class annotationClass = (Class) returnType; returnValue = createAnnotation(annotationType, annotationClass, defaultPackage); } catch (ClassCastException e) { throw new ValidationException("Unexpected parameter value"); } } else { throw new ValidationException("Unexpected parameter value"); } return returnValue; } private Object convertToResultType(Class returnType, String value, String defaultPackage) { /** * Class is represented by the fully qualified class name of the class. * spec: Note that if the raw string is unqualified, * default package is taken into account. */ if (returnType.equals(Class.class)) { value = toQualifiedClassName(value, defaultPackage); } /* Converter lookup */ Converter converter = ConvertUtils.lookup(returnType); if (converter == null && returnType.isEnum()) { converter = EnumerationConverter.getInstance(); } if (converter != null) { return converter.convert(returnType, value); } else { return converter; } } private Annotation createAnnotation(AnnotationType annotationType, Class returnType, String defaultPackage) { AnnotationProxyBuilder metaAnnotation = new AnnotationProxyBuilder(returnType); for (ElementType elementType : annotationType.getElement()) { String name = elementType.getName(); Class parameterType = getAnnotationParameterType(returnType, name); Object elementValue = getElementValue(elementType, parameterType, defaultPackage); metaAnnotation.putValue(name, elementValue); } return metaAnnotation.createAnnotation(); } private Class[] getGroups(GroupsType groupsType, String defaultPackage) { if (groupsType == null) { return new Class[]{}; } List> groupList = new ArrayList>(); for (JAXBElement groupClass : groupsType.getValue()) { groupList.add(loadClass(groupClass.getValue(), defaultPackage)); } return groupList.toArray(new Class[groupList.size()]); } @SuppressWarnings("unchecked") private Class[] getPayload(PayloadType payloadType, String defaultPackage) { if (payloadType == null) { return new Class[]{}; } List> payloadList = new ArrayList>(); for (JAXBElement groupClass : payloadType.getValue()) { Class payload = loadClass(groupClass.getValue(), defaultPackage); if (!Payload.class.isAssignableFrom(payload)) { throw new ValidationException("Specified payload class " + payload.getName() + " does not implement javax.validation.Payload"); } else { payloadList.add((Class) payload); } } return payloadList.toArray(new Class[payloadList.size()]); } private Class[] createGroupSequence(GroupSequenceType groupSequenceType, String defaultPackage) { if (groupSequenceType != null) { Class[] groupSequence = new Class[groupSequenceType.getValue().size()]; int i=0; for (JAXBElement groupName : groupSequenceType.getValue()) { Class group = loadClass(groupName.getValue(), defaultPackage); groupSequence[i++] = group; } return groupSequence; } else { return null; } } private void processFieldLevel(List fields, Class beanClass, String defaultPackage) { List fieldNames = new ArrayList(); for (FieldType fieldType : fields) { String fieldName = fieldType.getName(); if (fieldNames.contains(fieldName)) { throw new ValidationException(fieldName + " is defined more than once in mapping xml for bean " + beanClass.getName()); } else { fieldNames.add(fieldName); } final Field field = doPrivileged(SecureActions.getDeclaredField(beanClass, fieldName)); if (field == null) { throw new ValidationException( beanClass.getName() + " does not contain the fieldType " + fieldName); } // ignore annotations boolean ignoreFieldAnnotation = fieldType.isIgnoreAnnotations() == null ? false : fieldType.isIgnoreAnnotations(); if (ignoreFieldAnnotation) { factory.getAnnotationIgnores().setIgnoreAnnotationsOnMember(field); } // valid if (fieldType.getValid() != null) { factory.addValid(beanClass, new FieldAccess(field)); } // constraints for (ConstraintType constraintType : fieldType.getConstraint()) { MetaConstraint constraint = createConstraint(constraintType, beanClass, field, defaultPackage); factory.addMetaConstraint(beanClass, constraint); } } } private void processPropertyLevel(List getters, Class beanClass, String defaultPackage) { List getterNames = new ArrayList(); for (GetterType getterType : getters) { String getterName = getterType.getName(); if (getterNames.contains(getterName)) { throw new ValidationException(getterName + " is defined more than once in mapping xml for bean " + beanClass.getName()); } else { getterNames.add(getterName); } final Method method = getGetter(beanClass, getterName); if (method == null) { throw new ValidationException( beanClass.getName() + " does not contain the property " + getterName); } // ignore annotations boolean ignoreGetterAnnotation = getterType.isIgnoreAnnotations() == null ? false : getterType.isIgnoreAnnotations(); if (ignoreGetterAnnotation) { factory.getAnnotationIgnores().setIgnoreAnnotationsOnMember(method); } // valid if (getterType.getValid() != null) { factory.addValid(beanClass, new MethodAccess(getterName, method)); } // constraints for (ConstraintType constraintType : getterType.getConstraint()) { MetaConstraint metaConstraint = createConstraint(constraintType, beanClass, method, defaultPackage); factory.addMetaConstraint(beanClass, metaConstraint); } } } @SuppressWarnings("unchecked") private void processConstraintDefinitions( List constraintDefinitionList, String defaultPackage) { for (ConstraintDefinitionType constraintDefinition : constraintDefinitionList) { String annotationClassName = constraintDefinition.getAnnotation(); Class clazz = loadClass(annotationClassName, defaultPackage); if (!clazz.isAnnotation()) { throw new ValidationException(annotationClassName + " is not an annotation"); } Class annotationClass = (Class) clazz; ValidatedByType validatedByType = constraintDefinition.getValidatedBy(); List>> classes = new ArrayList>>(); /* If include-existing-validator is set to false, ConstraintValidator defined on the constraint annotation are ignored. */ if (validatedByType.isIncludeExistingValidators() != null && validatedByType.isIncludeExistingValidators()) { /* If set to true, the list of ConstraintValidators described in XML are concatenated to the list of ConstraintValidator described on the annotation to form a new array of ConstraintValidator evaluated. */ classes.addAll(findConstraintValidatorClasses(annotationClass)); } for (JAXBElement validatorClassName : validatedByType.getValue()) { Class> validatorClass; validatorClass = (Class>) loadClass(validatorClassName.getValue()); if (!ConstraintValidator.class.isAssignableFrom(validatorClass)) { throw new ValidationException( validatorClass + " is not a constraint validator class"); } /* Annotation based ConstraintValidator come before XML based ConstraintValidator in the array. The new list is returned by ConstraintDescriptor.getConstraintValidatorClasses(). */ if (!classes.contains(validatorClass)) classes.add(validatorClass); } if (factory.getConstraintsCache().containsConstraintValidator(annotationClass)) { throw new ValidationException("Constraint validator for " + annotationClass.getName() + " already configured."); } else { factory.getConstraintsCache().putConstraintValidator(annotationClass, classes.toArray(new Class[classes.size()])); } } } private List>> findConstraintValidatorClasses( Class annotationType) { List>> classes = new ArrayList>>(); Class>[] validator = factory.getDefaultConstraints().getValidatorClasses(annotationType); if (validator != null) { classes .addAll(Arrays.asList(validator)); } else { Class>[] validatedBy = annotationType .getAnnotation(Constraint.class) .validatedBy(); classes.addAll(Arrays.asList(validatedBy)); } return classes; } private Class loadClass(String className, String defaultPackage) { return loadClass(toQualifiedClassName(className, defaultPackage)); } private String toQualifiedClassName(String className, String defaultPackage) { if (!isQualifiedClass(className)) { className = defaultPackage + "." + className; } return className; } private boolean isQualifiedClass(String clazz) { return clazz.contains("."); } private static T doPrivileged(final PrivilegedAction action) { if (System.getSecurityManager() != null) { return AccessController.doPrivileged(action); } else { return action.run(); } } private static Method getGetter(final Class clazz, final String propertyName) { return doPrivileged(new PrivilegedAction() { public Method run() { try { final String p = StringUtils.capitalize(propertyName); try { return clazz.getMethod("get" + p); } catch (NoSuchMethodException e) { return clazz.getMethod("is" + p); } } catch (NoSuchMethodException e) { return null; } } }); } private Class loadClass(final String className) { ClassLoader loader = doPrivileged(SecureActions.getContextClassLoader()); if (loader == null) loader = getClass().getClassLoader(); try { return Class.forName(className, true, loader); } catch (ClassNotFoundException ex) { throw new ValidationException("Unable to load class: " + className, ex); } } }