| /* |
| * 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 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; |
| |
| import javax.validation.ClockProvider; |
| 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 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.descriptor.DescriptorManager; |
| import org.apache.bval.jsr.metadata.MetadataBuilders; |
| import org.apache.bval.jsr.util.AnnotationsManager; |
| import org.apache.bval.jsr.valueextraction.ValueExtractors; |
| 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.ObjectUtils; |
| import org.apache.bval.util.StringUtils; |
| 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.weaver.privilizer.Privileged; |
| import org.apache.commons.weaver.privilizer.Privilizing; |
| import org.apache.commons.weaver.privilizer.Privilizing.CallTo; |
| |
| /** |
| * 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 MessageInterpolator messageResolver; |
| private TraversableResolver traversableResolver; |
| private ConstraintValidatorFactory constraintValidatorFactory; |
| private ParameterNameProvider parameterNameProvider; |
| private ClockProvider clockProvider; |
| private final Map<String, String> properties; |
| private final AnnotationsManager annotationsManager; |
| private final DescriptorManager descriptorManager = new DescriptorManager(this); |
| private final MetadataBuilders metadataBuilders = new MetadataBuilders(); |
| private final ValueExtractors valueExtractors = new ValueExtractors(); |
| |
| /** |
| * 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<>(); |
| 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<>(); |
| 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: |
| final Class<? extends MetaBeanFactory> factoryClass = |
| loadClass(clsName).asSubclass(MetaBeanFactory.class); |
| 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<>(configuration.getProperties()); |
| defaultSequences = new HashMap<>(); |
| validAccesses = new ConcurrentHashMap<>(); |
| constraintMap = new ConcurrentHashMap<>(); |
| |
| parameterNameProvider = configuration.getParameterNameProvider(); |
| messageResolver = configuration.getMessageInterpolator(); |
| traversableResolver = configuration.getTraversableResolver(); |
| constraintValidatorFactory = configuration.getConstraintValidatorFactory(); |
| clockProvider = configuration.getClockProvider(); |
| |
| if (ConfigurationImpl.class.isInstance(configuration)) { |
| toClose.add(ConfigurationImpl.class.cast(configuration).getClosable()); |
| } |
| |
| new ValidationMappingParser(this).processMappingConfig(configuration.getMappingStreams()); |
| |
| defaultMetaBeanFinder = buildMetaBeanFinder(); |
| |
| configuration.getValueExtractors().forEach(valueExtractors::add); |
| annotationsManager = new AnnotationsManager(this); |
| } |
| |
| /** |
| * 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 |
| */ |
| @Override |
| public Validator getValidator() { |
| return usingContext().getValidator(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * |
| * @return the validator factory's context |
| */ |
| @Override |
| 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} |
| */ |
| @Override |
| 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; |
| } |
| } |
| |
| public void setClockProvider(final ClockProvider clockProvider) { |
| if (clockProvider != null) { |
| this.clockProvider = clockProvider; |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| 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} |
| */ |
| @Override |
| public ConstraintValidatorFactory getConstraintValidatorFactory() { |
| return constraintValidatorFactory; |
| } |
| |
| @Override |
| public ParameterNameProvider getParameterNameProvider() { |
| return parameterNameProvider; |
| } |
| |
| @Override |
| public ClockProvider getClockProvider() { |
| return clockProvider; |
| } |
| |
| @Override |
| 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. |
| */ |
| @Override |
| 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 = Reflection.toClass(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 AnnotationIgnores}. |
| * |
| * @return AnnotationIgnores |
| */ |
| public AnnotationIgnores getAnnotationIgnores() { |
| return annotationIgnores; |
| } |
| |
| /** |
| * Get the constraint cache used. |
| * |
| * @return {@link ConstraintCached} |
| */ |
| public ConstraintCached getConstraintsCache() { |
| return constraintsCache; |
| } |
| |
| /** |
| * Get the {@link AnnotationsManager}. |
| * |
| * @return {@link AnnotationsManager} |
| */ |
| public AnnotationsManager getAnnotationsManager() { |
| return annotationsManager; |
| } |
| |
| /** |
| * Get the {@link DescriptorManager}. |
| * |
| * @return {@link DescriptorManager} |
| */ |
| public DescriptorManager getDescriptorManager() { |
| return descriptorManager; |
| } |
| |
| /** |
| * Get the {@link ValueExtractors}. |
| * |
| * @return {@link ValueExtractors} |
| */ |
| public ValueExtractors getValueExtractors() { |
| return valueExtractors; |
| } |
| |
| /** |
| * 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<>(); |
| 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<>(); |
| 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.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)); |
| } |
| |
| public MetadataBuilders getMetadataBuilders() { |
| return metadataBuilders; |
| } |
| |
| private static Class<?>[] safeArray(Class<?>... array) { |
| return array == null || array.length == 0 ? ObjectUtils.EMPTY_CLASS_ARRAY : array.clone(); |
| } |
| |
| /** |
| * 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 = Reflection.getDeclaredConstructor(cls, ApacheValidatorFactory.this.getClass()); |
| if (c != null) { |
| return c.newInstance(this); |
| } |
| c = Reflection.getDeclaredConstructor(cls, getClass()); |
| if (c != null) { |
| return c.newInstance(this); |
| } |
| return cls.getConstructor().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); |
| } |
| } |
| } |