blob: 1d7a62553affd856b15874a61a3d8af8365daf21 [file] [log] [blame]
/*
* 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.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.BiConsumer;
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 javax.validation.valueextraction.ValueExtractor;
import org.apache.bval.jsr.descriptor.DescriptorManager;
import org.apache.bval.jsr.groups.GroupsComputer;
import org.apache.bval.jsr.metadata.MetadataBuilder;
import org.apache.bval.jsr.metadata.MetadataBuilder.ForBean;
import org.apache.bval.jsr.metadata.MetadataBuilders;
import org.apache.bval.jsr.metadata.MetadataSource;
import org.apache.bval.jsr.util.AnnotationsManager;
import org.apache.bval.jsr.valueextraction.ValueExtractors;
import org.apache.bval.jsr.valueextraction.ValueExtractors.OnDuplicateContainerElementKey;
import org.apache.bval.util.CloseableAble;
import org.apache.bval.util.reflection.Reflection;
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;
/**
* 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;
}
private static ValueExtractors createBaseValueExtractors(ParticipantFactory participantFactory) {
final ValueExtractors result = new ValueExtractors(OnDuplicateContainerElementKey.OVERWRITE);
participantFactory.loadServices(ValueExtractor.class).forEach(result::add);
return result;
}
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 ConstraintCached constraintsCache = new ConstraintCached();
private final Map<Class<?>, Class<?>> unwrappedClassCache = new ConcurrentHashMap<>();
private final Collection<Closeable> toClose = new ArrayList<>();
private final GroupsComputer groupsComputer = new GroupsComputer();
private final ParticipantFactory participantFactory;
private final ValueExtractors valueExtractors;
private MessageInterpolator messageResolver;
private TraversableResolver traversableResolver;
private ConstraintValidatorFactory constraintValidatorFactory;
private ParameterNameProvider parameterNameProvider;
private ClockProvider clockProvider;
/**
* Create a new ApacheValidatorFactory instance.
*/
public ApacheValidatorFactory(ConfigurationState configuration) {
properties = new HashMap<>(configuration.getProperties());
parameterNameProvider = configuration.getParameterNameProvider();
messageResolver = configuration.getMessageInterpolator();
traversableResolver = configuration.getTraversableResolver();
constraintValidatorFactory = configuration.getConstraintValidatorFactory();
clockProvider = configuration.getClockProvider();
if (configuration instanceof CloseableAble) {
toClose.add(((CloseableAble) configuration).getCloseable());
}
participantFactory = new ParticipantFactory(Thread.currentThread().getContextClassLoader(),
ApacheValidatorFactory.class.getClassLoader());
toClose.add(participantFactory);
valueExtractors = createBaseValueExtractors(participantFactory).createChild();
configuration.getValueExtractors().forEach(valueExtractors::add);
annotationsManager = new AnnotationsManager(this);
loadAndVerifyUserCustomizations(configuration);
}
public Map<Class<?>, Class<?>> getUnwrappedClassCache() {
return unwrappedClassCache;
}
/**
* 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);
}
/**
* {@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 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;
}
public MetadataBuilders getMetadataBuilders() {
return metadataBuilders;
}
public GroupsComputer getGroupsComputer() {
return groupsComputer;
}
private void loadAndVerifyUserCustomizations(ConfigurationState configuration) {
@SuppressWarnings({ "unchecked", "rawtypes" })
final BiConsumer<Class<?>, ForBean<?>> addBuilder = (t, b) -> {
getMetadataBuilders().registerCustomBuilder((Class) t, (MetadataBuilder.ForBean) b);
};
participantFactory.loadServices(MetadataSource.class)
.forEach(ms -> {
ms.initialize(this);
ms.process(configuration, getConstraintsCache()::add, addBuilder);
});
}
}