org.apache.bval.jsr.xml.ValidationParser.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.bval.jsr.xml.ValidationParser.java

Source

/*
 * 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.xml;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.validation.ConstraintValidatorFactory;
import javax.validation.MessageInterpolator;
import javax.validation.ParameterNameProvider;
import javax.validation.TraversableResolver;
import javax.validation.ValidationException;
import javax.validation.executable.ExecutableType;
import javax.validation.spi.ValidationProvider;
import javax.xml.XMLConstants;
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 javax.xml.validation.SchemaFactory;

import org.apache.bval.jsr.BootstrapConfigurationImpl;
import org.apache.bval.jsr.ConfigurationImpl;
import org.apache.bval.jsr.util.IOs;
import org.apache.bval.util.reflection.Reflection;
import org.apache.commons.weaver.privilizer.Privileged;
import org.apache.commons.weaver.privilizer.Privilizing;
import org.apache.commons.weaver.privilizer.Privilizing.CallTo;
import org.xml.sax.SAXException;

/**
 * Description: uses jaxb to parse validation.xml<br/>
 */
@Privilizing(@CallTo(Reflection.class))
public class ValidationParser {
    private static final String DEFAULT_VALIDATION_XML_FILE = "META-INF/validation.xml";
    private static final String VALIDATION_CONFIGURATION_XSD = "META-INF/validation-configuration-1.1.xsd";
    private static final Logger log = Logger.getLogger(ValidationParser.class.getName());
    private static final ConcurrentMap<String, Schema> SCHEMA_CACHE = new ConcurrentHashMap<String, Schema>(1);

    private ValidationConfigType xmlConfig;
    private BootstrapConfigurationImpl bootstrap;
    private Collection<ValidationException> exceptions = new CopyOnWriteArrayList<ValidationException>();

    private ValidationParser() {
        // no-op
    }

    public void applyConfigWithInstantiation(ConfigurationImpl targetConfig) {
        if (xmlConfig == null) {
            return;
        }

        applyProviderClass(xmlConfig, targetConfig);
        applyMessageInterpolator(xmlConfig, targetConfig);
        applyTraversableResolver(xmlConfig, targetConfig);
        applyConstraintFactory(xmlConfig, targetConfig);
        applyParameterNameProvider(xmlConfig, targetConfig);
        applyMappingStreams(xmlConfig, targetConfig);
        applyProperties(xmlConfig, targetConfig);
    }

    public BootstrapConfigurationImpl getBootstrap() {
        return bootstrap;
    }

    public static String getValidationXmlFile(String file) {
        if (file == null) {
            return DEFAULT_VALIDATION_XML_FILE;
        }
        return file;
    }

    public static ValidationParser processValidationConfig(final String file, final ConfigurationImpl targetConfig,
            final boolean ignoreXml) {
        final ValidationParser parser = new ValidationParser();

        if (!ignoreXml) {
            parser.xmlConfig = parseXmlConfig(file);
        }

        if (parser.xmlConfig != null) {
            if (parser.xmlConfig.getExecutableValidation() == null) {
                final ExecutableValidationType value = new ExecutableValidationType();
                value.setEnabled(true);

                final DefaultValidatedExecutableTypesType defaultValidatedExecutableTypes = new DefaultValidatedExecutableTypesType();
                value.setDefaultValidatedExecutableTypes(defaultValidatedExecutableTypes);
                defaultValidatedExecutableTypes.getExecutableType().add(ExecutableType.CONSTRUCTORS);
                defaultValidatedExecutableTypes.getExecutableType().add(ExecutableType.NON_GETTER_METHODS);

                parser.xmlConfig.setExecutableValidation(value);
            }

            applySimpleConfig(parser.xmlConfig, targetConfig);

            parser.bootstrap = new BootstrapConfigurationImpl(parser.xmlConfig.getDefaultProvider(),
                    parser.xmlConfig.getConstraintValidatorFactory(), parser.xmlConfig.getMessageInterpolator(),
                    parser.xmlConfig.getTraversableResolver(), parser.xmlConfig.getParameterNameProvider(),
                    new CopyOnWriteArraySet<String>(parser.xmlConfig.getConstraintMapping()),
                    parser.xmlConfig.getExecutableValidation().getEnabled(),
                    new CopyOnWriteArraySet<ExecutableType>(targetConfig.getExecutableValidation()),
                    toMap(parser.xmlConfig.getProperty()));
            return parser;
        } else { // default config
            final CopyOnWriteArraySet<ExecutableType> executableTypes = new CopyOnWriteArraySet<ExecutableType>();
            executableTypes.add(ExecutableType.CONSTRUCTORS);
            executableTypes.add(ExecutableType.NON_GETTER_METHODS);

            parser.bootstrap = new BootstrapConfigurationImpl(null, null, null, null, null,
                    new CopyOnWriteArraySet<String>(), true, executableTypes, new HashMap<String, String>());

            targetConfig.setExecutableValidation(executableTypes);
        }

        return parser;
    }

    private static Map<String, String> toMap(final List<PropertyType> property) {
        final Map<String, String> map = new HashMap<String, String>();
        if (property != null) {
            for (final PropertyType p : property) {
                map.put(p.getName(), p.getValue());
            }
        }
        return map;
    }

    @Privileged
    private static ValidationConfigType parseXmlConfig(final String validationXmlFile) {
        InputStream inputStream = null;
        try {
            inputStream = getInputStream(getValidationXmlFile(validationXmlFile));
            if (inputStream == null) {
                log.log(Level.FINEST, String.format("No %s found. Using annotation based configuration only.",
                        validationXmlFile));
                return null;
            }

            log.log(Level.FINEST, String.format("%s found.", validationXmlFile));

            Schema schema = getSchema();
            JAXBContext jc = JAXBContext.newInstance(ValidationConfigType.class);
            Unmarshaller unmarshaller = jc.createUnmarshaller();
            unmarshaller.setSchema(schema);
            StreamSource stream = new StreamSource(inputStream);
            JAXBElement<ValidationConfigType> root = unmarshaller.unmarshal(stream, ValidationConfigType.class);
            return root.getValue();
        } catch (JAXBException e) {
            throw new ValidationException("Unable to parse " + validationXmlFile, e);
        } catch (IOException e) {
            throw new ValidationException("Unable to parse " + validationXmlFile, e);
        } finally {
            IOs.closeQuietly(inputStream);
        }
    }

    protected static InputStream getInputStream(final String path) throws IOException {
        final ClassLoader loader = Reflection.getClassLoader(ValidationParser.class);
        final InputStream inputStream = loader.getResourceAsStream(path);

        if (inputStream != null) {
            // spec says: If more than one META-INF/validation.xml file
            // is found in the classpath, a ValidationException is raised.
            final Enumeration<URL> urls = loader.getResources(path);
            if (urls.hasMoreElements()) {
                final String url = urls.nextElement().toString();
                while (urls.hasMoreElements()) {
                    if (!url.equals(urls.nextElement().toString())) { // complain when first duplicate found
                        throw new ValidationException("More than one " + path + " is found in the classpath");
                    }
                }
            }
        }

        return IOs.convertToMarkableInputStream(inputStream);
    }

    private static Schema getSchema() {
        return getSchema(VALIDATION_CONFIGURATION_XSD);
    }

    static Schema getSchema(final String xsd) {
        final Schema schema = SCHEMA_CACHE.get(xsd);
        if (schema != null) {
            return schema;
        }

        final ClassLoader loader = Reflection.getClassLoader(ValidationParser.class);
        final SchemaFactory sf = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        final URL schemaUrl = loader.getResource(xsd);
        try {
            Schema s = sf.newSchema(schemaUrl);
            final Schema old = SCHEMA_CACHE.putIfAbsent(xsd, s);
            if (old != null) {
                s = old;
            }
            return s;
        } catch (SAXException e) {
            log.log(Level.WARNING, String.format("Unable to parse schema: %s", xsd), e);
            return null;
        }
    }

    public static void applySimpleConfig(ValidationConfigType xmlConfig, ConfigurationImpl targetConfig) {
        applyExecutableValidation(xmlConfig, targetConfig);
    }

    private static void applyProperties(ValidationConfigType xmlConfig, ConfigurationImpl target) {
        for (final PropertyType property : xmlConfig.getProperty()) {
            target.addProperty(property.getName(), property.getValue());
        }
    }

    private static void applyExecutableValidation(final ValidationConfigType xmlConfig,
            final ConfigurationImpl targetConfig) {
        final CopyOnWriteArrayList<ExecutableType> executableTypes = new CopyOnWriteArrayList<ExecutableType>();
        if (xmlConfig.getExecutableValidation() != null && xmlConfig.getExecutableValidation().getEnabled()
                && xmlConfig.getExecutableValidation().getDefaultValidatedExecutableTypes() != null) {
            executableTypes.addAll(
                    xmlConfig.getExecutableValidation().getDefaultValidatedExecutableTypes().getExecutableType());
        }

        if (executableTypes.contains(ExecutableType.ALL)) {
            executableTypes.clear();
            executableTypes.add(ExecutableType.CONSTRUCTORS);
            executableTypes.add(ExecutableType.NON_GETTER_METHODS);
            executableTypes.add(ExecutableType.GETTER_METHODS);
        } else if (executableTypes.contains(ExecutableType.NONE)) { // if both are present ALL gains
            executableTypes.clear();
        }

        targetConfig.setExecutableValidation(executableTypes);
    }

    private void applyParameterNameProvider(final ValidationConfigType xmlConfig,
            final ConfigurationImpl targetConfig) {
        final String parameterNameProvider = xmlConfig.getParameterNameProvider();
        if (targetConfig.getParameterNameProvider() == targetConfig.getDefaultParameterNameProvider()) { // ref ==
            if (parameterNameProvider != null) {
                final Class<?> loaded = loadClass(parameterNameProvider);
                if (loaded != null) {
                    final Class<? extends ParameterNameProvider> clazz = loaded
                            .asSubclass(ParameterNameProvider.class);
                    targetConfig.parameterNameProviderClass(clazz);
                    log.log(Level.INFO, String.format("Using %s as validation provider.", parameterNameProvider));
                } else {
                    log.log(Level.SEVERE, "Can't load " + parameterNameProvider);
                }
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void applyProviderClass(ValidationConfigType xmlConfig, ConfigurationImpl target) {
        String providerClassName = xmlConfig.getDefaultProvider();
        if (providerClassName != null) {
            Class<? extends ValidationProvider<?>> clazz = (Class<? extends ValidationProvider<?>>) loadClass(
                    providerClassName);
            target.setProviderClass(clazz);
            log.log(Level.INFO, String.format("Using %s as validation provider.", providerClassName));
        }
    }

    @SuppressWarnings("unchecked")
    private void applyMessageInterpolator(ValidationConfigType xmlConfig, ConfigurationImpl target) {
        String messageInterpolatorClass = xmlConfig.getMessageInterpolator();
        if (target.getMessageInterpolator() == target.getDefaultMessageInterpolator()) { // ref ==
            if (messageInterpolatorClass != null) {
                Class<MessageInterpolator> clazz = (Class<MessageInterpolator>) loadClass(messageInterpolatorClass);
                target.messageInterpolatorClass(clazz);
                log.log(Level.INFO, String.format("Using %s as message interpolator.", messageInterpolatorClass));
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void applyTraversableResolver(ValidationConfigType xmlConfig, ConfigurationImpl target) {
        String traversableResolverClass = xmlConfig.getTraversableResolver();
        if (target.getTraversableResolver() == target.getDefaultTraversableResolver()) { // ref ==
            if (traversableResolverClass != null) {
                Class<TraversableResolver> clazz = (Class<TraversableResolver>) loadClass(traversableResolverClass);
                target.traversableResolverClass(clazz);
                log.log(Level.INFO, String.format("Using %s as traversable resolver.", traversableResolverClass));
            }
        }
    }

    @SuppressWarnings("unchecked")
    private void applyConstraintFactory(ValidationConfigType xmlConfig, ConfigurationImpl target) {
        String constraintFactoryClass = xmlConfig.getConstraintValidatorFactory();
        if (target.getConstraintValidatorFactory() == target.getDefaultConstraintValidatorFactory()) { // ref ==
            if (constraintFactoryClass != null) {
                Class<ConstraintValidatorFactory> clazz = (Class<ConstraintValidatorFactory>) loadClass(
                        constraintFactoryClass);
                target.constraintValidatorFactoryClass(clazz);
                log.log(Level.INFO, String.format("Using %s as constraint factory.", constraintFactoryClass));
            }
        }
    }

    private static void applyMappingStreams(ValidationConfigType xmlConfig, ConfigurationImpl target) {
        for (String rawMappingFileName : xmlConfig.getConstraintMapping()) {
            String mappingFileName = rawMappingFileName;
            if (mappingFileName.startsWith("/")) {
                // Classloader needs a path without a starting /
                mappingFileName = mappingFileName.substring(1);
            }
            log.log(Level.FINEST, String.format("Trying to open input stream for %s", mappingFileName));
            InputStream in;
            try {
                in = getInputStream(mappingFileName);
                if (in == null) {
                    throw new ValidationException(
                            "Unable to open input stream for mapping file " + mappingFileName);
                }
            } catch (IOException e) {
                throw new ValidationException("Unable to open input stream for mapping file " + mappingFileName, e);
            }
            target.addMapping(in);
        }
    }

    private Class<?> loadClass(final String className) {
        final ClassLoader loader = Reflection.getClassLoader(ValidationParser.class);
        try {
            return Class.forName(className, true, loader);
        } catch (final ClassNotFoundException ex) {
            // TCK check BootstrapConfig is present in all cases
            // so throw next exception later
            exceptions.add(new ValidationException("Unable to load class: " + className, ex));
            return null;
        }
    }

    public void ensureValidatorFactoryCanBeBuilt() {
        if (!exceptions.isEmpty()) {
            throw exceptions.iterator().next();
        }
    }
}