/*
* Hibernate Validator, declare and validate application constraints
*
* License: Apache License, Version 2.0
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
*/
package org.hibernate.validator.internal.cdi;
import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.EnumSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import javax.enterprise.event.Observes;
import javax.enterprise.inject.Default;
import javax.enterprise.inject.spi.AfterBeanDiscovery;
import javax.enterprise.inject.spi.AnnotatedCallable;
import javax.enterprise.inject.spi.AnnotatedConstructor;
import javax.enterprise.inject.spi.AnnotatedMethod;
import javax.enterprise.inject.spi.AnnotatedType;
import javax.enterprise.inject.spi.Bean;
import javax.enterprise.inject.spi.BeanManager;
import javax.enterprise.inject.spi.BeforeBeanDiscovery;
import javax.enterprise.inject.spi.Extension;
import javax.enterprise.inject.spi.ProcessAnnotatedType;
import javax.enterprise.inject.spi.ProcessBean;
import javax.enterprise.inject.spi.WithAnnotations;
import javax.enterprise.util.AnnotationLiteral;
import javax.validation.BootstrapConfiguration;
import javax.validation.Configuration;
import javax.validation.Constraint;
import javax.validation.Valid;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import javax.validation.executable.ExecutableType;
import javax.validation.executable.ValidateOnExecution;
import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.PropertyDescriptor;
import org.hibernate.validator.cdi.HibernateValidator;
import org.hibernate.validator.internal.cdi.interceptor.ValidationEnabledAnnotatedType;
import org.hibernate.validator.internal.cdi.interceptor.ValidationInterceptor;
import org.hibernate.validator.internal.util.Contracts;
import org.hibernate.validator.internal.util.ExecutableHelper;
import org.hibernate.validator.internal.util.ReflectionHelper;
import org.hibernate.validator.internal.util.TypeResolutionHelper;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
import static org.hibernate.validator.internal.util.CollectionHelper.newHashSet;
/**
* A CDI portable extension which integrates Bean Validation with CDI. It registers the following objects:
* <ul>
* <li>
* Beans for {@link ValidatorFactory} and {@link Validator} representing default validator factory and validator as
* configured via {@code META-INF/validation.xml}. These beans will have the {@code Default} qualifier and in addition
* the {@code HibernateValidator} qualifier if Hibernate Validator is the default validation provider.</li>
* <li>In case Hibernate Validator is <em>not</em> the default provider, another pair of beans will be registered in
* addition which are qualified with the {@code HibernateValidator} qualifier.</li>
* </ul>
* Neither of these beans will be registered in case there is already another bean with the same type and qualifier(s),
* e.g. registered by another portable extension or the application itself.
*
* @author Gunnar Morling
* @author Hardy Ferentschik
*/
public class ValidationExtension implements Extension {
private static final Log log = LoggerFactory.make();
private static final EnumSet<ExecutableType> ALL_EXECUTABLE_TYPES =
EnumSet.of( ExecutableType.CONSTRUCTORS, ExecutableType.NON_GETTER_METHODS, ExecutableType.GETTER_METHODS );
private static final EnumSet<ExecutableType> DEFAULT_EXECUTABLE_TYPES =
EnumSet.of( ExecutableType.CONSTRUCTORS, ExecutableType.NON_GETTER_METHODS );
@SuppressWarnings("serial")
private final Annotation defaultQualifier = new AnnotationLiteral<Default>() {
};
@SuppressWarnings("serial")
private final Annotation hibernateValidatorQualifier = new AnnotationLiteral<HibernateValidator>() {
};
private final ExecutableHelper executableHelper;
/**
* Used for identifying constrained classes
*/
private final Validator validator;
private final ValidatorFactory validatorFactory;
private final Set<ExecutableType> globalExecutableTypes;
private final boolean isExecutableValidationEnabled;
private Bean<?> defaultValidatorFactoryBean;
private Bean<?> hibernateValidatorFactoryBean;
private Bean<?> defaultValidatorBean;
private Bean<?> hibernateValidatorBean;
public ValidationExtension() {
Configuration<?> config = Validation.byDefaultProvider().configure();
BootstrapConfiguration bootstrap = config.getBootstrapConfiguration();
globalExecutableTypes = bootstrap.getDefaultValidatedExecutableTypes();
isExecutableValidationEnabled = bootstrap.isExecutableValidationEnabled();
validatorFactory = config.buildValidatorFactory();
validator = validatorFactory.getValidator();
executableHelper = new ExecutableHelper( new TypeResolutionHelper() );
}
/**
* Used to register the method validation interceptor binding annotation.
*
* @param beforeBeanDiscoveryEvent event fired before the bean discovery process starts
* @param beanManager the bean manager.
*/
public void beforeBeanDiscovery(@Observes BeforeBeanDiscovery beforeBeanDiscoveryEvent,
final BeanManager beanManager) {
Contracts.assertNotNull( beforeBeanDiscoveryEvent, "The BeforeBeanDiscovery event cannot be null" );
Contracts.assertNotNull( beanManager, "The BeanManager cannot be null" );
// Register the interceptor explicitly. This way, no beans.xml is needed
AnnotatedType<ValidationInterceptor> annotatedType = beanManager.createAnnotatedType( ValidationInterceptor.class );
beforeBeanDiscoveryEvent.addAnnotatedType( annotatedType );
}
/**
* Registers beans for {@code ValidatorFactory} and {@code Validator} if not yet present.
*
* @param afterBeanDiscoveryEvent event fired after the bean discovery phase.
* @param beanManager the bean manager.
*/
public void afterBeanDiscovery(@Observes AfterBeanDiscovery afterBeanDiscoveryEvent, BeanManager beanManager) {
Contracts.assertNotNull( afterBeanDiscoveryEvent, "The AfterBeanDiscovery event cannot be null" );
Contracts.assertNotNull( beanManager, "The BeanManager cannot be null" );
ValidationProviderHelper defaultProviderHelper = ValidationProviderHelper.forDefaultProvider( validatorFactory );
ValidationProviderHelper hvProviderHelper = ValidationProviderHelper.forHibernateValidator();
// register default VF if none has been provided by the application or another PE
if ( defaultValidatorFactoryBean == null ) {
defaultValidatorFactoryBean = new ValidatorFactoryBean( beanManager, defaultProviderHelper );
if ( hibernateValidatorFactoryBean == null && defaultProviderHelper.isHibernateValidator() ) {
hibernateValidatorFactoryBean = defaultValidatorFactoryBean;
}
afterBeanDiscoveryEvent.addBean( defaultValidatorFactoryBean );
}
// register VF with @HibernateValidator qualifier in case it hasn't been contributed by the application and the
// default VF registered by ourselves isn't for Hibernate Validator
if ( hibernateValidatorFactoryBean == null ) {
hibernateValidatorFactoryBean = new ValidatorFactoryBean( beanManager, hvProviderHelper );
afterBeanDiscoveryEvent.addBean( hibernateValidatorFactoryBean );
}
// register default validator if required
if ( defaultValidatorBean == null ) {
defaultValidatorBean = new ValidatorBean( beanManager, defaultValidatorFactoryBean, defaultProviderHelper );
if ( hibernateValidatorBean == null && defaultProviderHelper.isHibernateValidator() ) {
hibernateValidatorBean = defaultValidatorBean;
}
afterBeanDiscoveryEvent.addBean( defaultValidatorBean );
}
// register validator with @HibernateValidator if required
if ( hibernateValidatorBean == null ) {
hibernateValidatorBean = new ValidatorBean( beanManager, hibernateValidatorFactoryBean, hvProviderHelper );
afterBeanDiscoveryEvent.addBean( hibernateValidatorBean );
}
}
/**
* Watches the {@code ProcessBean} event in order to determine whether beans for {@code ValidatorFactory} and
* {@code Validator} already have been registered by some other component.
*
* @param processBeanEvent event fired for each enabled bean.
*/
public void processBean(@Observes ProcessBean<?> processBeanEvent) {
Contracts.assertNotNull( processBeanEvent, "The ProcessBean event cannot be null" );
Bean<?> bean = processBeanEvent.getBean();
if ( bean.getTypes().contains( ValidatorFactory.class ) || bean instanceof ValidatorFactoryBean ) {
if ( bean.getQualifiers().contains( defaultQualifier ) ) {
defaultValidatorFactoryBean = bean;
}
if ( bean.getQualifiers().contains( hibernateValidatorQualifier ) ) {
hibernateValidatorFactoryBean = bean;
}
}
else if ( bean.getTypes().contains( Validator.class ) || bean instanceof ValidatorBean ) {
if ( bean.getQualifiers().contains( defaultQualifier ) ) {
defaultValidatorBean = bean;
}
if ( bean.getQualifiers().contains( hibernateValidatorQualifier ) ) {
hibernateValidatorBean = bean;
}
}
}
/**
* Used to register the method validation interceptor bindings.
*
* @param processAnnotatedTypeEvent event fired for each annotated type
* @param <T> the annotated type
*/
public <T> void processAnnotatedType(@Observes @WithAnnotations({
Constraint.class,
Valid.class,
ValidateOnExecution.class
}) ProcessAnnotatedType<T> processAnnotatedTypeEvent) {
Contracts.assertNotNull( processAnnotatedTypeEvent, "The ProcessAnnotatedType event cannot be null" );
// validation globally disabled
if ( !isExecutableValidationEnabled ) {
return;
}
AnnotatedType<T> type = processAnnotatedTypeEvent.getAnnotatedType();
Set<AnnotatedCallable<? super T>> constrainedCallables = determineConstrainedCallables( type );
if ( !constrainedCallables.isEmpty() ) {
ValidationEnabledAnnotatedType<T> wrappedType = new ValidationEnabledAnnotatedType<T>(
type,
constrainedCallables
);
processAnnotatedTypeEvent.setAnnotatedType( wrappedType );
}
}
private <T> Set<AnnotatedCallable<? super T>> determineConstrainedCallables(AnnotatedType<T> type) {
Set<AnnotatedCallable<? super T>> callables = newHashSet();
BeanDescriptor beanDescriptor = validator.getConstraintsForClass( type.getJavaClass() );
determineConstrainedConstructors( type, beanDescriptor, callables );
determineConstrainedMethods( type, beanDescriptor, callables );
return callables;
}
private <T> void determineConstrainedMethods(AnnotatedType<T> type, BeanDescriptor beanDescriptor, Set<AnnotatedCallable<? super T>> callables) {
List<Method> overriddenAndImplementedMethods = InheritedMethodsHelper.getAllMethods( type.getJavaClass() );
for ( AnnotatedMethod<? super T> annotatedMethod : type.getMethods() ) {
Method method = annotatedMethod.getJavaMember();
boolean isGetter = ReflectionHelper.isGetterMethod( method );
// obtain @ValidateOnExecution from the top-most method in the hierarchy
Method methodForExecutableTypeRetrieval = replaceWithOverriddenOrInterfaceMethod( method, overriddenAndImplementedMethods );
EnumSet<ExecutableType> classLevelExecutableTypes = executableTypesDefinedOnType( methodForExecutableTypeRetrieval.getDeclaringClass() );
EnumSet<ExecutableType> memberLevelExecutableType = executableTypesDefinedOnMethod( methodForExecutableTypeRetrieval, isGetter );
ExecutableType currentExecutableType = isGetter ? ExecutableType.GETTER_METHODS : ExecutableType.NON_GETTER_METHODS;
// validation is enabled per default, so explicit configuration can just veto whether
// validation occurs
if ( veto( classLevelExecutableTypes, memberLevelExecutableType, currentExecutableType ) ) {
continue;
}
boolean needsValidation;
if ( isGetter ) {
needsValidation = isGetterConstrained( method, beanDescriptor );
}
else {
needsValidation = isNonGetterConstrained( method, beanDescriptor );
}
if ( needsValidation ) {
callables.add( annotatedMethod );
}
}
}
private <T> void determineConstrainedConstructors(AnnotatedType<T> type, BeanDescriptor beanDescriptor, Set<AnnotatedCallable<? super T>> callables) {
Class<?> clazz = type.getJavaClass();
EnumSet<ExecutableType> classLevelExecutableTypes = executableTypesDefinedOnType( clazz );
for ( AnnotatedConstructor<T> annotatedConstructor : type.getConstructors() ) {
Constructor<?> constructor = annotatedConstructor.getJavaMember();
EnumSet<ExecutableType> memberLevelExecutableType = executableTypesDefinedOnConstructor( constructor );
if ( veto( classLevelExecutableTypes, memberLevelExecutableType, ExecutableType.CONSTRUCTORS ) ) {
continue;
}
if ( beanDescriptor.getConstraintsForConstructor( constructor.getParameterTypes() ) != null ) {
callables.add( annotatedConstructor );
}
}
}
private boolean isNonGetterConstrained(Method method, BeanDescriptor beanDescriptor) {
return beanDescriptor.getConstraintsForMethod( method.getName(), method.getParameterTypes() ) != null;
}
private boolean isGetterConstrained(Method method, BeanDescriptor beanDescriptor) {
String propertyName = ReflectionHelper.getPropertyName( method );
PropertyDescriptor propertyDescriptor = beanDescriptor.getConstraintsForProperty( propertyName );
return propertyDescriptor != null && propertyDescriptor.findConstraints()
.declaredOn( ElementType.METHOD )
.hasConstraints();
}
private boolean veto(EnumSet<ExecutableType> classLevelExecutableTypes,
EnumSet<ExecutableType> memberLevelExecutableType,
ExecutableType currentExecutableType) {
if ( !memberLevelExecutableType.isEmpty() ) {
return !memberLevelExecutableType.contains( currentExecutableType )
&& !memberLevelExecutableType.contains( ExecutableType.IMPLICIT );
}
if ( !classLevelExecutableTypes.isEmpty() ) {
return !classLevelExecutableTypes.contains( currentExecutableType )
&& !classLevelExecutableTypes.contains( ExecutableType.IMPLICIT );
}
return !globalExecutableTypes.contains( currentExecutableType );
}
private EnumSet<ExecutableType> executableTypesDefinedOnType(Class<?> clazz) {
ValidateOnExecution validateOnExecutionAnnotation = clazz.getAnnotation( ValidateOnExecution.class );
EnumSet<ExecutableType> executableTypes = commonExecutableTypeChecks( validateOnExecutionAnnotation );
if ( executableTypes.contains( ExecutableType.IMPLICIT ) ) {
return DEFAULT_EXECUTABLE_TYPES;
}
return executableTypes;
}
private EnumSet<ExecutableType> executableTypesDefinedOnMethod(Method method, boolean isGetter) {
ValidateOnExecution validateOnExecutionAnnotation = method.getAnnotation( ValidateOnExecution.class );
EnumSet<ExecutableType> executableTypes = commonExecutableTypeChecks( validateOnExecutionAnnotation );
if ( executableTypes.contains( ExecutableType.IMPLICIT ) ) {
if ( isGetter ) {
executableTypes.add( ExecutableType.GETTER_METHODS );
}
else {
executableTypes.add( ExecutableType.NON_GETTER_METHODS );
}
}
return executableTypes;
}
private EnumSet<ExecutableType> executableTypesDefinedOnConstructor(Constructor<?> constructor) {
ValidateOnExecution validateOnExecutionAnnotation = constructor.getAnnotation(
ValidateOnExecution.class
);
EnumSet<ExecutableType> executableTypes = commonExecutableTypeChecks( validateOnExecutionAnnotation );
if ( executableTypes.contains( ExecutableType.IMPLICIT ) ) {
executableTypes.add( ExecutableType.CONSTRUCTORS );
}
return executableTypes;
}
private EnumSet<ExecutableType> commonExecutableTypeChecks(ValidateOnExecution validateOnExecutionAnnotation) {
if ( validateOnExecutionAnnotation == null ) {
return EnumSet.noneOf( ExecutableType.class );
}
EnumSet<ExecutableType> executableTypes = EnumSet.noneOf( ExecutableType.class );
if ( validateOnExecutionAnnotation.type().length == 0 ) { // HV-757
executableTypes.add( ExecutableType.NONE );
}
else {
Collections.addAll( executableTypes, validateOnExecutionAnnotation.type() );
}
// IMPLICIT cannot be mixed 10.1.2 of spec - Mixing IMPLICIT and other executable types is illegal
if ( executableTypes.contains( ExecutableType.IMPLICIT ) && executableTypes.size() > 1 ) {
throw log.getMixingImplicitWithOtherExecutableTypesException();
}
// NONE can be removed 10.1.2 of spec - A list containing NONE and other types of executables is equivalent to a
// list containing the types of executables without NONE.
if ( executableTypes.contains( ExecutableType.NONE ) && executableTypes.size() > 1 ) {
executableTypes.remove( ExecutableType.NONE );
}
// 10.1.2 of spec - A list containing ALL and other types of executables is equivalent to a list containing only ALL
if ( executableTypes.contains( ExecutableType.ALL ) ) {
executableTypes = ALL_EXECUTABLE_TYPES;
}
return executableTypes;
}
public Method replaceWithOverriddenOrInterfaceMethod(Method method, List<Method> allMethodsOfType) {
LinkedList<Method> list = new LinkedList<Method>( allMethodsOfType );
Iterator<Method> iterator = list.descendingIterator();
while ( iterator.hasNext() ) {
Method overriddenOrInterfaceMethod = iterator.next();
if ( executableHelper.overrides( method, overriddenOrInterfaceMethod ) ) {
if ( method.getAnnotation( ValidateOnExecution.class ) != null ) {
throw log.getValidateOnExecutionOnOverriddenOrInterfaceMethodException( method );
}
return overriddenOrInterfaceMethod;
}
}
return method;
}
}