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.IntrospectorMetaBeanFactory; import org.apache.bval.MetaBeanBuilder; import org.apache.bval.MetaBeanFactory; import org.apache.bval.MetaBeanFinder; import org.apache.bval.MetaBeanManager; import org.apache.bval.jsr.xml.AnnotationIgnores; import org.apache.bval.jsr.xml.MetaConstraint; import org.apache.bval.jsr.xml.ValidationMappingParser; import org.apache.bval.util.AccessStrategy; import org.apache.bval.util.reflection.Reflection; import org.apache.bval.xml.XMLMetaBeanBuilder; import org.apache.bval.xml.XMLMetaBeanFactory; import org.apache.bval.xml.XMLMetaBeanManager; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.ClassUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.reflect.ConstructorUtils; import org.apache.commons.weaver.privilizer.Privileged; import org.apache.commons.weaver.privilizer.Privilizing; import org.apache.commons.weaver.privilizer.Privilizing.CallTo; import javax.validation.ConstraintValidatorFactory; import javax.validation.MessageInterpolator; import javax.validation.ParameterNameProvider; import javax.validation.TraversableResolver; import javax.validation.Validation; import javax.validation.ValidationException; import javax.validation.Validator; import javax.validation.ValidatorFactory; import javax.validation.spi.ConfigurationState; import java.io.Closeable; import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; /** * Description: a factory is a complete configurated object that can create * validators.<br/> * This instance is not thread-safe.<br/> */ @Privilizing(@CallTo(Reflection.class)) public class ApacheValidatorFactory implements ValidatorFactory, Cloneable { private static volatile ApacheValidatorFactory DEFAULT_FACTORY; private static final ConstraintDefaults DEFAULT_CONSTRAINTS = new ConstraintDefaults(); private MessageInterpolator messageResolver; private TraversableResolver traversableResolver; private ConstraintValidatorFactory constraintValidatorFactory; private ParameterNameProvider parameterNameProvider; private final Map<String, String> properties; /** * information from xml parsing */ private final AnnotationIgnores annotationIgnores = new AnnotationIgnores(); private final ConstraintCached constraintsCache = new ConstraintCached(); private final Map<Class<?>, Class<?>[]> defaultSequences; /** * access strategies for properties with cascade validation @Valid support */ private final ConcurrentMap<Class<?>, List<AccessStrategy>> validAccesses; private final ConcurrentMap<Class<?>, List<MetaConstraint<?, ? extends Annotation>>> constraintMap; private final Collection<Closeable> toClose = new ArrayList<Closeable>(); private final MetaBeanFinder defaultMetaBeanFinder; /** * Create MetaBeanManager that uses factories: * <ol> * <li>if enabled by * {@link ApacheValidatorConfiguration.Properties#ENABLE_INTROSPECTOR}, an * {@link IntrospectorMetaBeanFactory}</li> * <li>{@link MetaBeanFactory} types (if any) specified by * {@link ApacheValidatorConfiguration.Properties#METABEAN_FACTORY_CLASSNAMES} * </li> * <li>if no {@link JsrMetaBeanFactory} has yet been specified (this * allows factory order customization), a {@link JsrMetaBeanFactory} * which handles both JSR303-XML and JSR303-Annotations</li> * <li>if enabled by * {@link ApacheValidatorConfiguration.Properties#ENABLE_METABEANS_XML}, an * {@link XMLMetaBeanFactory}</li> * </ol> * * @return a new instance of MetaBeanManager with adequate MetaBeanFactories */ protected MetaBeanFinder buildMetaBeanFinder() { final List<MetaBeanFactory> builders = new ArrayList<MetaBeanFactory>(); if (Boolean .parseBoolean(getProperties().get(ApacheValidatorConfiguration.Properties.ENABLE_INTROSPECTOR))) { builders.add(new IntrospectorMetaBeanFactory()); } final String[] factoryClassNames = StringUtils .split(getProperties().get(ApacheValidatorConfiguration.Properties.METABEAN_FACTORY_CLASSNAMES)); if (factoryClassNames != null) { for (String clsName : factoryClassNames) { // cast, relying on #createMetaBeanFactory to throw the exception if incompatible: @SuppressWarnings("unchecked") final Class<? extends MetaBeanFactory> factoryClass = (Class<? extends MetaBeanFactory>) loadClass( clsName); builders.add(createMetaBeanFactory(factoryClass)); } } boolean jsrFound = false; for (MetaBeanFactory builder : builders) { jsrFound |= builder instanceof JsrMetaBeanFactory; } if (!jsrFound) { builders.add(new JsrMetaBeanFactory(this)); } @SuppressWarnings("deprecation") final boolean enableMetaBeansXml = Boolean .parseBoolean(getProperties().get(ApacheValidatorConfiguration.Properties.ENABLE_METABEANS_XML)); if (enableMetaBeansXml) { XMLMetaBeanManagerCreator.addFactory(builders); } return createMetaBeanManager(builders); } /** * Convenience method to retrieve a default global ApacheValidatorFactory * * @return {@link ApacheValidatorFactory} */ public static ApacheValidatorFactory getDefault() { if (DEFAULT_FACTORY == null) { synchronized (ApacheValidatorFactory.class) { if (DEFAULT_FACTORY == null) { DEFAULT_FACTORY = Validation.byProvider(ApacheValidationProvider.class).configure() .buildValidatorFactory().unwrap(ApacheValidatorFactory.class); } } } return DEFAULT_FACTORY; } /** * Set a particular {@link ApacheValidatorFactory} instance as the default. * * @param aDefaultFactory */ public static void setDefault(ApacheValidatorFactory aDefaultFactory) { DEFAULT_FACTORY = aDefaultFactory; } /** * Create a new ApacheValidatorFactory instance. */ public ApacheValidatorFactory(ConfigurationState configuration) { properties = new HashMap<String, String>(configuration.getProperties()); defaultSequences = new HashMap<Class<?>, Class<?>[]>(); validAccesses = new ConcurrentHashMap<Class<?>, List<AccessStrategy>>(); constraintMap = new ConcurrentHashMap<Class<?>, List<MetaConstraint<?, ? extends Annotation>>>(); parameterNameProvider = configuration.getParameterNameProvider(); messageResolver = configuration.getMessageInterpolator(); traversableResolver = configuration.getTraversableResolver(); constraintValidatorFactory = configuration.getConstraintValidatorFactory(); if (ConfigurationImpl.class.isInstance(configuration)) { final ConfigurationImpl impl = ConfigurationImpl.class.cast(configuration); toClose.add(impl.getClosable()); } new ValidationMappingParser(this).processMappingConfig(configuration.getMappingStreams()); defaultMetaBeanFinder = buildMetaBeanFinder(); } /** * Get the property map of this {@link ApacheValidatorFactory}. * * @return Map<String, String> */ public Map<String, String> getProperties() { return properties; } /** * Shortcut method to create a new Validator instance with factory's * settings * * @return the new validator instance */ public Validator getValidator() { return usingContext().getValidator(); } /** * {@inheritDoc} * * @return the validator factory's context */ public ApacheFactoryContext usingContext() { return new ApacheFactoryContext(this, defaultMetaBeanFinder); } /** * {@inheritDoc} */ @Override public synchronized ApacheValidatorFactory clone() { try { return (ApacheValidatorFactory) super.clone(); } catch (CloneNotSupportedException e) { throw new InternalError(); // VM bug. } } /** * Set the {@link MessageInterpolator} used. * * @param messageResolver */ public final void setMessageInterpolator(MessageInterpolator messageResolver) { if (messageResolver != null) { this.messageResolver = messageResolver; } } /** * {@inheritDoc} */ public MessageInterpolator getMessageInterpolator() { return messageResolver; } /** * Set the {@link TraversableResolver} used. * * @param traversableResolver */ public final void setTraversableResolver(TraversableResolver traversableResolver) { if (traversableResolver != null) { this.traversableResolver = traversableResolver; } } public void setParameterNameProvider(final ParameterNameProvider parameterNameProvider) { if (parameterNameProvider != null) { this.parameterNameProvider = parameterNameProvider; } } /** * {@inheritDoc} */ public TraversableResolver getTraversableResolver() { return traversableResolver; } /** * Set the {@link ConstraintValidatorFactory} used. * * @param constraintValidatorFactory */ public final void setConstraintValidatorFactory(ConstraintValidatorFactory constraintValidatorFactory) { if (constraintValidatorFactory != null) { this.constraintValidatorFactory = constraintValidatorFactory; if (DefaultConstraintValidatorFactory.class.isInstance(constraintValidatorFactory)) { toClose.add(Closeable.class.cast(constraintValidatorFactory)); } } } /** * {@inheritDoc} */ public ConstraintValidatorFactory getConstraintValidatorFactory() { return constraintValidatorFactory; } public ParameterNameProvider getParameterNameProvider() { return parameterNameProvider; } public void close() { try { for (final Closeable c : toClose) { c.close(); } toClose.clear(); } catch (final Exception e) { // no-op } } /** * Return an object of the specified type to allow access to the * provider-specific API. If the Bean Validation provider implementation * does not support the specified class, the ValidationException 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. */ public <T> T unwrap(final Class<T> type) { if (type.isInstance(this)) { @SuppressWarnings("unchecked") final T result = (T) this; return result; } // 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.isInterface() || Modifier.isAbstract(type.getModifiers()))) { return newInstance(type); } try { final Class<?> cls = ClassUtils.getClass(type.getName() + "Impl"); if (type.isAssignableFrom(cls)) { @SuppressWarnings("unchecked") T result = (T) newInstance(cls); return result; } } catch (ClassNotFoundException e) { // do nothing } throw new ValidationException("Type " + type + " not supported"); } private <T> T newInstance(final Class<T> cls) { try { return Reflection.newInstance(cls); } catch (final RuntimeException e) { throw new ValidationException(e.getCause()); } } /** * Get the detected {@link ConstraintDefaults}. * * @return ConstraintDefaults */ public ConstraintDefaults getDefaultConstraints() { return DEFAULT_CONSTRAINTS; } /** * Get the detected {@link AnnotationIgnores}. * * @return AnnotationIgnores */ public AnnotationIgnores getAnnotationIgnores() { return annotationIgnores; } /** * Get the constraint cache used. * * @return {@link ConstraintCached} */ public ConstraintCached getConstraintsCache() { return constraintsCache; } /** * Add a meta-constraint to this {@link ApacheValidatorFactory}'s runtime * customizations. * * @param beanClass * @param metaConstraint */ public void addMetaConstraint(final Class<?> beanClass, final MetaConstraint<?, ?> metaConstraint) { List<MetaConstraint<?, ? extends Annotation>> slot = constraintMap.get(beanClass); if (slot == null) { slot = new ArrayList<MetaConstraint<?, ? extends Annotation>>(); final List<MetaConstraint<?, ? extends Annotation>> old = constraintMap.putIfAbsent(beanClass, slot); if (old != null) { slot = old; } } slot.add(metaConstraint); } /** * Mark a property of <code>beanClass</code> for nested validation. * * @param beanClass * @param accessStrategy * defining the property to validate */ public void addValid(Class<?> beanClass, AccessStrategy accessStrategy) { List<AccessStrategy> slot = validAccesses.get(beanClass); if (slot == null) { slot = new ArrayList<AccessStrategy>(); final List<AccessStrategy> old = validAccesses.putIfAbsent(beanClass, slot); if (old != null) { slot = old; } } slot.add(accessStrategy); } /** * Set the default group sequence for a particular bean class. * * @param beanClass * @param groupSequence */ public void addDefaultSequence(Class<?> beanClass, Class<?>... groupSequence) { defaultSequences.put(beanClass, safeArray(groupSequence)); } /** * Retrieve the runtime constraint configuration for a given class. * * @param <T> * @param beanClass * @return List of {@link MetaConstraint}s applicable to * <code>beanClass</code> */ public <T> List<MetaConstraint<T, ? extends Annotation>> getMetaConstraints(Class<T> beanClass) { final List<MetaConstraint<?, ? extends Annotation>> slot = constraintMap.get(beanClass); if (slot == null) { return Collections.emptyList(); } // noinspection RedundantCast @SuppressWarnings({ "unchecked", "rawtypes" }) final List<MetaConstraint<T, ? extends Annotation>> result = (List) slot; return Collections.unmodifiableList(result); } /** * Get the {@link AccessStrategy} {@link List} indicating nested bean * validations that must be triggered in the course of validating a * <code>beanClass</code> graph. * * @param beanClass * @return {@link List} of {@link AccessStrategy} */ public List<AccessStrategy> getValidAccesses(Class<?> beanClass) { final List<AccessStrategy> slot = validAccesses.get(beanClass); return slot == null ? Collections.<AccessStrategy>emptyList() : Collections.unmodifiableList(slot); } /** * Get the default group sequence configured for <code>beanClass</code>. * * @param beanClass * @return group Class array */ public Class<?>[] getDefaultSequence(Class<?> beanClass) { return safeArray(defaultSequences.get(beanClass)); } private static Class<?>[] safeArray(Class<?>... array) { return ArrayUtils.isEmpty(array) ? ArrayUtils.EMPTY_CLASS_ARRAY : ArrayUtils.clone(array); } /** * Create a {@link MetaBeanManager} using the specified builders. * * @param builders * {@link MetaBeanFactory} {@link List} * @return {@link MetaBeanManager} */ @SuppressWarnings("deprecation") protected MetaBeanFinder createMetaBeanManager(List<MetaBeanFactory> builders) { // as long as we support both: jsr (in the builders list) and xstream-xml metabeans: if (Boolean .parseBoolean(getProperties().get(ApacheValidatorConfiguration.Properties.ENABLE_METABEANS_XML))) { return XMLMetaBeanManagerCreator.createXMLMetaBeanManager(builders); } return new MetaBeanManager(new MetaBeanBuilder(builders.toArray(new MetaBeanFactory[builders.size()]))); } @Privileged private <F extends MetaBeanFactory> F createMetaBeanFactory(final Class<F> cls) { try { Constructor<F> c = ConstructorUtils.getMatchingAccessibleConstructor(cls, ApacheValidatorFactory.this.getClass()); if (c != null) { return c.newInstance(this); } c = ConstructorUtils.getMatchingAccessibleConstructor(cls, getClass()); if (c != null) { return c.newInstance(this); } return cls.newInstance(); } catch (Exception e) { throw new ValidationException(e); } } /** * separate class to prevent the classloader to immediately load optional * classes: XMLMetaBeanManager, XMLMetaBeanFactory, XMLMetaBeanBuilder that * might not be available in the classpath */ private static class XMLMetaBeanManagerCreator { static void addFactory(List<MetaBeanFactory> builders) { builders.add(new XMLMetaBeanFactory()); } /** * Create the {@link MetaBeanManager} to process JSR303 XML. Requires * bval-xstream at RT. * * @param builders meta bean builders * @return {@link MetaBeanManager} */ // NOTE - We return MetaBeanManager instead of XMLMetaBeanManager to // keep // bval-xstream an optional module. protected static MetaBeanManager createXMLMetaBeanManager(List<MetaBeanFactory> builders) { return new XMLMetaBeanManager( new XMLMetaBeanBuilder(builders.toArray(new MetaBeanFactory[builders.size()]))); } } private Class<?> loadClass(final String className) { try { return Class.forName(className, true, Reflection.getClassLoader(ApacheValidatorFactory.class)); } catch (ClassNotFoundException ex) { throw new ValidationException("Unable to load class: " + className, ex); } } }