Skip to content

Commit

Permalink
HV-1718 Support validating unconstrained beans with PredefinedScopeVa…
Browse files Browse the repository at this point in the history
…lidatorFactory
  • Loading branch information
gsmet committed Jun 12, 2019
1 parent 8d914a8 commit f2d5f06
Show file tree
Hide file tree
Showing 3 changed files with 237 additions and 31 deletions.
Expand Up @@ -8,44 +8,57 @@

import static org.hibernate.validator.internal.util.CollectionHelper.newArrayList;

import java.lang.invoke.MethodHandles;
import java.lang.annotation.ElementType;
import java.lang.reflect.Executable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import javax.validation.metadata.BeanDescriptor;
import javax.validation.metadata.ConstraintDescriptor;
import javax.validation.metadata.ConstructorDescriptor;
import javax.validation.metadata.ElementDescriptor.ConstraintFinder;
import javax.validation.metadata.MethodDescriptor;
import javax.validation.metadata.MethodType;
import javax.validation.metadata.PropertyDescriptor;
import javax.validation.metadata.Scope;

import org.hibernate.validator.internal.engine.ConstraintCreationContext;
import org.hibernate.validator.internal.engine.MethodValidationConfiguration;
import org.hibernate.validator.internal.engine.groups.Sequence;
import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator;
import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData;
import org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataBuilder;
import org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl;
import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData;
import org.hibernate.validator.internal.metadata.aggregated.PropertyMetaData;
import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptions;
import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptionsImpl;
import org.hibernate.validator.internal.metadata.core.MetaConstraint;
import org.hibernate.validator.internal.metadata.facets.Cascadable;
import org.hibernate.validator.internal.metadata.provider.AnnotationMetaDataProvider;
import org.hibernate.validator.internal.metadata.provider.MetaDataProvider;
import org.hibernate.validator.internal.metadata.raw.BeanConfiguration;
import org.hibernate.validator.internal.properties.javabean.JavaBeanHelper;
import org.hibernate.validator.internal.util.CollectionHelper;
import org.hibernate.validator.internal.util.ExecutableHelper;
import org.hibernate.validator.internal.util.ExecutableParameterNameProvider;
import org.hibernate.validator.internal.util.classhierarchy.ClassHierarchyHelper;
import org.hibernate.validator.internal.util.classhierarchy.Filters;
import org.hibernate.validator.internal.util.logging.Log;
import org.hibernate.validator.internal.util.logging.LoggerFactory;
import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer;

public class PredefinedScopeBeanMetaDataManager implements BeanMetaDataManager {

private static final Log LOG = LoggerFactory.make( MethodHandles.lookup() );

private final BeanMetaDataClassNormalizer beanMetaDataClassNormalizer;

/**
* Used to cache the constraint meta data for validated entities
* Used to cache the constraint meta data for validated entities.
*/
private final Map<Class<?>, BeanMetaData<?>> beanMetaDataMap;
private final ConcurrentMap<Class<?>, BeanMetaData<?>> beanMetaDataMap = new ConcurrentHashMap<>();

public PredefinedScopeBeanMetaDataManager(ConstraintCreationContext constraintCreationContext,
ExecutableHelper executableHelper,
Expand All @@ -71,32 +84,37 @@ public PredefinedScopeBeanMetaDataManager(ConstraintCreationContext constraintCr
metaDataProviders.add( defaultProvider );
metaDataProviders.addAll( optionalMetaDataProviders );

Map<Class<?>, BeanMetaData<?>> tmpBeanMetadataMap = new HashMap<>();

for ( Class<?> validatedClass : beanClassesToInitialize ) {
Class<?> normalizedValidatedClass = beanMetaDataClassNormalizer.normalize( validatedClass );

@SuppressWarnings("unchecked")
List<Class<?>> classHierarchy = (List<Class<?>>) (Object) ClassHierarchyHelper.getHierarchy( validatedClass, Filters.excludeInterfaces() );
List<Class<?>> classHierarchy = (List<Class<?>>) (Object) ClassHierarchyHelper.getHierarchy( normalizedValidatedClass, Filters.excludeInterfaces() );

// note that the hierarchy also contains the initial class
for ( Class<?> hierarchyElement : classHierarchy ) {
tmpBeanMetadataMap.put( beanMetaDataClassNormalizer.normalize( hierarchyElement ),
if ( this.beanMetaDataMap.containsKey( hierarchyElement ) ) {
continue;
}

this.beanMetaDataMap.put( hierarchyElement,
createBeanMetaData( constraintCreationContext, executableHelper, parameterNameProvider,
javaBeanHelper, validationOrderGenerator, optionalMetaDataProviders, methodValidationConfiguration,
metaDataProviders, hierarchyElement ) );
}
}

this.beanMetaDataMap = CollectionHelper.toImmutableMap( tmpBeanMetadataMap );

this.beanMetaDataClassNormalizer = beanMetaDataClassNormalizer;
}

@SuppressWarnings("unchecked")
@Override
public <T> BeanMetaData<T> getBeanMetaData(Class<T> beanClass) {
BeanMetaData<T> beanMetaData = (BeanMetaData<T>) beanMetaDataMap.get( beanMetaDataClassNormalizer.normalize( beanClass ) );
Class<?> normalizedBeanClass = beanMetaDataClassNormalizer.normalize( beanClass );
BeanMetaData<T> beanMetaData = (BeanMetaData<T>) beanMetaDataMap.get( normalizedBeanClass );
if ( beanMetaData == null ) {
throw LOG.uninitializedBeanMetaData( beanClass );
// note that if at least one element of the hierarchy is constrained, the child classes should really be initialized
// otherwise they will be considered unconstrained.
beanMetaData = (BeanMetaData<T>) beanMetaDataMap.computeIfAbsent( normalizedBeanClass, UninitializedBeanMetaData::new );
}
return beanMetaData;
}
Expand Down Expand Up @@ -170,4 +188,179 @@ private static <T> List<BeanConfiguration<? super T>> getBeanConfigurationForHie

return configurations;
}

private static class UninitializedBeanMetaData<T> implements BeanMetaData<T> {

private final Class<T> beanClass;

private final BeanDescriptor beanDescriptor;

private final List<Class<? super T>> classHierarchy;

@SuppressWarnings("unchecked")
private UninitializedBeanMetaData(Class<T> beanClass) {
this.beanClass = beanClass;
this.classHierarchy = (List<Class<? super T>>) (Object) ClassHierarchyHelper.getHierarchy( beanClass, Filters.excludeInterfaces() );
this.beanDescriptor = new UninitializedBeanDescriptor( beanClass );
}

@Override
public Iterable<Cascadable> getCascadables() {
return Collections.emptyList();
}

@Override
public boolean hasCascadables() {
return false;
}

@Override
public Class<T> getBeanClass() {
return beanClass;
}

@Override
public boolean hasConstraints() {
return false;
}

@Override
public BeanDescriptor getBeanDescriptor() {
return beanDescriptor;
}

@Override
public PropertyMetaData getMetaDataFor(String propertyName) {
throw new IllegalStateException( "Metadata has not been initialized for bean of type " + beanClass.getName() );
}

@Override
public List<Class<?>> getDefaultGroupSequence(T beanState) {
throw new IllegalStateException( "Metadata has not been initialized for bean of type " + beanClass.getName() );
}

@Override
public Iterator<Sequence> getDefaultValidationSequence(T beanState) {
throw new IllegalStateException( "Metadata has not been initialized for bean of type " + beanClass.getName() );
}

@Override
public boolean isDefaultGroupSequenceRedefined() {
return false;
}

@Override
public Set<MetaConstraint<?>> getMetaConstraints() {
return Collections.emptySet();
}

@Override
public Set<MetaConstraint<?>> getDirectMetaConstraints() {
return Collections.emptySet();
}

@Override
public Optional<ExecutableMetaData> getMetaDataFor(Executable executable) throws IllegalArgumentException {
return Optional.empty();
}

@Override
public List<Class<? super T>> getClassHierarchy() {
return classHierarchy;
}
}

private static class UninitializedBeanDescriptor implements BeanDescriptor {

private final Class<?> elementClass;

private UninitializedBeanDescriptor(Class<?> elementClass) {
this.elementClass = elementClass;
}

@Override
public boolean hasConstraints() {
return false;
}

@Override
public Class<?> getElementClass() {
return elementClass;
}

@Override
public Set<ConstraintDescriptor<?>> getConstraintDescriptors() {
return Collections.emptySet();
}

@Override
public ConstraintFinder findConstraints() {
return UninitializedConstaintFinder.INSTANCE;
}

@Override
public boolean isBeanConstrained() {
return false;
}

@Override
public PropertyDescriptor getConstraintsForProperty(String propertyName) {
return null;
}

@Override
public Set<PropertyDescriptor> getConstrainedProperties() {
return Collections.emptySet();
}

@Override
public MethodDescriptor getConstraintsForMethod(String methodName, Class<?>... parameterTypes) {
return null;
}

@Override
public Set<MethodDescriptor> getConstrainedMethods(MethodType methodType, MethodType... methodTypes) {
return Collections.emptySet();
}

@Override
public ConstructorDescriptor getConstraintsForConstructor(Class<?>... parameterTypes) {
return null;
}

@Override
public Set<ConstructorDescriptor> getConstrainedConstructors() {
return Collections.emptySet();
}
}

private static class UninitializedConstaintFinder implements ConstraintFinder {

private static final UninitializedConstaintFinder INSTANCE = new UninitializedConstaintFinder();

@Override
public ConstraintFinder unorderedAndMatchingGroups(Class<?>... groups) {
return this;
}

@Override
public ConstraintFinder lookingAt(Scope scope) {
return this;
}

@Override
public ConstraintFinder declaredOn(ElementType... types) {
return this;
}

@Override
public Set<ConstraintDescriptor<?>> getConstraintDescriptors() {
return Collections.emptySet();
}

@Override
public boolean hasConstraints() {
return false;
}
}
}
Expand Up @@ -879,9 +879,6 @@ ConstraintDefinitionException getConstraintValidatorDefinitionConstraintMismatch
@Message(id = 248, value = "Unable to get an XML schema named %s.")
ValidationException unableToGetXmlSchema(String schemaResourceName);

@Message(id = 249, value = "Uninitialized bean metadata for class: %s. Please register your bean class as a class to initialize when initializing your ValidatorFactory.")
ValidationException uninitializedBeanMetaData(@FormatWith(ClassObjectFormatter.class) Class<?> clazz);

@Message(id = 250, value = "Uninitialized locale: %s. Please register your locale as a locale to initialize when initializing your ValidatorFactory.")
ValidationException uninitializedLocale(Locale locale);

Expand Down
Expand Up @@ -85,15 +85,15 @@ public void testValidation() throws NoSuchMethodException, SecurityException {
.property( "email" ) ) );
}

@Test(expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "HV000249:.*")
@Test
public void testValidationOnUnknownBean() throws NoSuchMethodException, SecurityException {
Validator validator = getValidator();

Set<ConstraintViolation<UnknownBean>> violations = validator.validate( new UnknownBean() );
assertNoViolations( violations );
}

@Test(expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "HV000249:.*")
@Test
public void testValidationOnUnknownBeanMethodParameter() throws NoSuchMethodException, SecurityException {
Validator validator = getValidator();

Expand All @@ -106,7 +106,7 @@ public void testValidationOnUnknownBeanMethodParameter() throws NoSuchMethodExce
assertNoViolations( violations );
}

@Test(expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "HV000249:.*")
@Test
public void testValidationOnUnknownBeanMethodReturnValue() throws NoSuchMethodException, SecurityException {
Validator validator = getValidator();

Expand All @@ -115,7 +115,7 @@ public void testValidationOnUnknownBeanMethodReturnValue() throws NoSuchMethodEx
assertNoViolations( violations );
}

@Test(expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "HV000249:.*")
@Test
public void testValidationOnUnknownBeanConstructorParameters() throws NoSuchMethodException, SecurityException {
Validator validator = getValidator();

Expand All @@ -125,7 +125,7 @@ public void testValidationOnUnknownBeanConstructorParameters() throws NoSuchMeth
assertNoViolations( violations );
}

@Test(expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "HV000249:.*")
@Test
public void testValidationOnUnknownBeanConstructorReturnValue() throws NoSuchMethodException, SecurityException {
Validator validator = getValidator();

Expand Down Expand Up @@ -173,9 +173,10 @@ public void testUnavailableInitializedLocale() {
}

@TestForIssue(jiraKey = "HV-1681")
@Test(expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "HV000249:.*")
@Test
public void testValidOnUnknownBean() {
getValidator().validate( new AnotherBean() );
Set<ConstraintViolation<AnotherBean>> violations = getValidator().validate( new AnotherBean() );
assertNoViolations( violations );
}

@Test(expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "HV000250:.*")
Expand All @@ -196,11 +197,26 @@ public void testUninitializedLocale() {
}
}

@Test(expectedExceptions = ValidationException.class, expectedExceptionsMessageRegExp = "HV000249:.*")
@Test
public void testBeanMetaDataClassNormalizerNoNormalizer() {
Validator validator = getValidator();
// In this case, as we haven't registered any metadata for the hierarchy, even if we have constraints,
// we won't have any violations.
Set<ConstraintViolation<Bean>> violations = getValidator().validate( new BeanProxy() );
assertNoViolations( violations );

validator.validate( new BeanProxy() );
// Now let's register the metadata for Bean and see how it goes
Set<Class<?>> beanMetaDataToInitialize = new HashSet<>();
beanMetaDataToInitialize.add( Bean.class );

ValidatorFactory validatorFactory = Validation.byProvider( PredefinedScopeHibernateValidator.class )
.configure()
.initializeBeanMetaData( beanMetaDataToInitialize )
.initializeLocales( Collections.singleton( Locale.ENGLISH ) )
.buildValidatorFactory();

// As we don't have any metadata for BeanProxy, we consider it is not constrained at all.
violations = validatorFactory.getValidator().validate( new BeanProxy() );
assertNoViolations( violations );
}

@Test
Expand Down

0 comments on commit f2d5f06

Please sign in to comment.