diff --git a/cdi/src/main/java/org/hibernate/validator/cdi/internal/ValidatorFactoryBean.java b/cdi/src/main/java/org/hibernate/validator/cdi/internal/ValidatorFactoryBean.java index 342342f282..eec74f1042 100644 --- a/cdi/src/main/java/org/hibernate/validator/cdi/internal/ValidatorFactoryBean.java +++ b/cdi/src/main/java/org/hibernate/validator/cdi/internal/ValidatorFactoryBean.java @@ -22,6 +22,8 @@ import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.Instance; +import javax.enterprise.inject.literal.NamedLiteral; import javax.enterprise.inject.spi.Bean; import javax.enterprise.inject.spi.BeanManager; import javax.enterprise.inject.spi.InjectionPoint; @@ -37,12 +39,15 @@ import javax.validation.ValidatorFactory; import javax.validation.valueextraction.ValueExtractor; +import org.hibernate.validator.HibernateValidatorConfiguration; +import org.hibernate.validator.cdi.spi.BeanNames; import org.hibernate.validator.internal.engine.valueextraction.ValueExtractorDescriptor; import org.hibernate.validator.internal.util.CollectionHelper; import org.hibernate.validator.internal.util.classhierarchy.ClassHierarchyHelper; import org.hibernate.validator.internal.util.privilegedactions.GetClassLoader; import org.hibernate.validator.internal.util.privilegedactions.GetInstancesFromServiceLoader; import org.hibernate.validator.internal.util.privilegedactions.LoadClass; +import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer; /** * A {@link Bean} representing a {@link ValidatorFactory}. There is one instance of this type representing the default @@ -126,6 +131,22 @@ public ValidatorFactory create(CreationalContext ctx) { config.parameterNameProvider( createParameterNameProvider( config ) ); config.clockProvider( createClockProvider( config ) ); + if ( config instanceof HibernateValidatorConfiguration ) { + HibernateValidatorConfiguration hvConfig = (HibernateValidatorConfiguration) config; + Instance beanMetaDataClassNormalizerInstance = + beanManager.createInstance() + .select( + BeanMetaDataClassNormalizer.class, + NamedLiteral.of( BeanNames.BEAN_META_DATA_CLASS_NORMALIZER ) + ); + if ( beanMetaDataClassNormalizerInstance.isResolvable() ) { + BeanMetaDataClassNormalizer normalizer = beanMetaDataClassNormalizerInstance.get(); + destructibleResources.add( new DestructibleBeanInstance<>( beanManager, normalizer ) ); + + hvConfig.beanMetaDataClassNormalizer( normalizer ); + } + } + addValueExtractorBeans( config ); return config.buildValidatorFactory(); diff --git a/cdi/src/main/java/org/hibernate/validator/cdi/spi/BeanNames.java b/cdi/src/main/java/org/hibernate/validator/cdi/spi/BeanNames.java new file mode 100644 index 0000000000..cb9f1da7f5 --- /dev/null +++ b/cdi/src/main/java/org/hibernate/validator/cdi/spi/BeanNames.java @@ -0,0 +1,16 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.cdi.spi; + +public final class BeanNames { + + private BeanNames() { + } + + public static final String BEAN_META_DATA_CLASS_NORMALIZER = "hibernate-validator-bean-meta-data-class-normalizer"; + +} diff --git a/cdi/src/test/java/org/hibernate/validator/test/cdi/internal/beanmetadataclassnormalizer/CustomProxy.java b/cdi/src/test/java/org/hibernate/validator/test/cdi/internal/beanmetadataclassnormalizer/CustomProxy.java new file mode 100644 index 0000000000..5bb6b09212 --- /dev/null +++ b/cdi/src/test/java/org/hibernate/validator/test/cdi/internal/beanmetadataclassnormalizer/CustomProxy.java @@ -0,0 +1,10 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.test.cdi.internal.beanmetadataclassnormalizer; + +public interface CustomProxy { +} diff --git a/cdi/src/test/java/org/hibernate/validator/test/cdi/internal/beanmetadataclassnormalizer/CustomProxyBeanMetaDataClassNormalizer.java b/cdi/src/test/java/org/hibernate/validator/test/cdi/internal/beanmetadataclassnormalizer/CustomProxyBeanMetaDataClassNormalizer.java new file mode 100644 index 0000000000..2e59a3819e --- /dev/null +++ b/cdi/src/test/java/org/hibernate/validator/test/cdi/internal/beanmetadataclassnormalizer/CustomProxyBeanMetaDataClassNormalizer.java @@ -0,0 +1,89 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.test.cdi.internal.beanmetadataclassnormalizer; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Type; +import java.util.Collections; +import java.util.Set; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.context.spi.CreationalContext; +import javax.enterprise.inject.literal.NamedLiteral; +import javax.enterprise.inject.spi.Bean; +import javax.enterprise.inject.spi.InjectionPoint; + +import org.hibernate.validator.cdi.spi.BeanNames; +import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer; + + +public class CustomProxyBeanMetaDataClassNormalizer + implements BeanMetaDataClassNormalizer, Bean { + + @Override + public Class normalize(Class clazz) { + if ( CustomProxy.class.isAssignableFrom( clazz ) ) { + return clazz.getSuperclass(); + } + return clazz; + } + + @Override + public Class getBeanClass() { + return BeanMetaDataClassNormalizer.class; + } + + @Override + public Set getInjectionPoints() { + return Collections.emptySet(); + } + + @Override + public boolean isNullable() { + return false; + } + + @Override + public BeanMetaDataClassNormalizer create(CreationalContext creationalContext) { + return new CustomProxyBeanMetaDataClassNormalizer(); + } + + @Override + public void destroy(BeanMetaDataClassNormalizer beanMetaDataClassNormalizer, + CreationalContext creationalContext) { + // Nothing to do + } + + @Override + public Set getTypes() { + return Collections.singleton( BeanMetaDataClassNormalizer.class ); + } + + @Override + public Set getQualifiers() { + return Collections.singleton( NamedLiteral.of( BeanNames.BEAN_META_DATA_CLASS_NORMALIZER ) ); + } + + @Override + public Class getScope() { + return ApplicationScoped.class; + } + + @Override + public String getName() { + return BeanNames.BEAN_META_DATA_CLASS_NORMALIZER; + } + + @Override + public Set> getStereotypes() { + return Collections.emptySet(); + } + + @Override + public boolean isAlternative() { + return false; + } +} diff --git a/cdi/src/test/java/org/hibernate/validator/test/cdi/internal/beanmetadataclassnormalizer/CustomProxyBeanMetadataClassNormalizerCdiExtension.java b/cdi/src/test/java/org/hibernate/validator/test/cdi/internal/beanmetadataclassnormalizer/CustomProxyBeanMetadataClassNormalizerCdiExtension.java new file mode 100644 index 0000000000..7ea42a77f8 --- /dev/null +++ b/cdi/src/test/java/org/hibernate/validator/test/cdi/internal/beanmetadataclassnormalizer/CustomProxyBeanMetadataClassNormalizerCdiExtension.java @@ -0,0 +1,18 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.test.cdi.internal.beanmetadataclassnormalizer; + +import javax.enterprise.event.Observes; +import javax.enterprise.inject.spi.AfterBeanDiscovery; +import javax.enterprise.inject.spi.Extension; + +public class CustomProxyBeanMetadataClassNormalizerCdiExtension implements Extension { + + public void addEjbProxyNormalizer(@Observes AfterBeanDiscovery afterBeanDiscovery) { + afterBeanDiscovery.addBean( new CustomProxyBeanMetaDataClassNormalizer() ); + } +} diff --git a/cdi/src/test/java/org/hibernate/validator/test/cdi/internal/beanmetadataclassnormalizer/ExtensionProvidedBeanMetadataClassNormalizerTest.java b/cdi/src/test/java/org/hibernate/validator/test/cdi/internal/beanmetadataclassnormalizer/ExtensionProvidedBeanMetadataClassNormalizerTest.java new file mode 100644 index 0000000000..e7b7778506 --- /dev/null +++ b/cdi/src/test/java/org/hibernate/validator/test/cdi/internal/beanmetadataclassnormalizer/ExtensionProvidedBeanMetadataClassNormalizerTest.java @@ -0,0 +1,102 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.test.cdi.internal.beanmetadataclassnormalizer; + +import static org.assertj.core.api.Assertions.assertThat; + +import javax.inject.Inject; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import javax.validation.constraints.DecimalMax; +import javax.validation.constraints.NotNull; + +import org.hibernate.validator.cdi.HibernateValidator; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.testng.Arquillian; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; + +import org.testng.annotations.Test; + +public class ExtensionProvidedBeanMetadataClassNormalizerTest extends Arquillian { + + @Deployment + public static JavaArchive createDeployment() { + return ShrinkWrap.create( JavaArchive.class ) + .addAsManifestResource( EmptyAsset.INSTANCE, "beans.xml" ) + // Register the CDI extension that provides the normalizer bean + .addAsManifestResource( + new StringAsset( CustomProxyBeanMetadataClassNormalizerCdiExtension.class.getName() ), + "services/javax.enterprise.inject.spi.Extension" + ); + } + + @HibernateValidator + @Inject + ValidatorFactory validatorFactory; + + @HibernateValidator + @Inject + Validator validator; + + @Inject + ValidatorFactory defaultValidatorFactory; + + @Inject + Validator defaultValidator; + + @Test + public void testProxyMetadataIgnoredWithQualifiedValidator() throws Exception { + assertThat( validator ).isNotNull(); + doTest( validator ); + } + + @Test + public void testProxyMetadataIgnoredWithDefaultValidator() throws Exception { + assertThat( defaultValidator ).isNotNull(); + doTest( defaultValidator ); + } + + @Test + public void testProxyMetadataIgnoredWithQualifiedValidatorFactory() throws Exception { + assertThat( validatorFactory ).isNotNull(); + doTest( validatorFactory.getValidator() ); + } + + @Test + public void testProxyMetadataIgnoredWithDefaultValidatorFactory() throws Exception { + assertThat( defaultValidatorFactory ).isNotNull(); + doTest( defaultValidatorFactory.getValidator() ); + } + + private void doTest(Validator validator) { + assertThat( validator ).isNotNull(); + /* + * Even though we pass an instance of the proxy class that has invalid annotations, + * we expect those to be ignored + * because of the class normalizer we defined. + */ + assertThat( validator.validate( new TestEntityProxy() ) ).hasSize( 1 ); + } + + public static class TestEntity { + @NotNull + private String foo; + } + + public static class TestEntityProxy extends TestEntity implements CustomProxy { + /* + * This is invalid, but should be ignored because it's defined in a proxy class which gets ignored + * because of the class normalizer we defined. + */ + @DecimalMax(value = "foo") + private String foo; + } +} diff --git a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java index d2f2c7030a..44955071e5 100644 --- a/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java +++ b/engine/src/main/java/org/hibernate/validator/BaseHibernateValidatorConfiguration.java @@ -25,6 +25,7 @@ import org.hibernate.validator.constraints.ParameterScriptAssert; import org.hibernate.validator.constraints.ScriptAssert; import org.hibernate.validator.spi.messageinterpolation.LocaleResolver; +import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer; import org.hibernate.validator.spi.nodenameprovider.PropertyNodeNameProvider; import org.hibernate.validator.spi.properties.GetterPropertySelectionStrategy; import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator; @@ -423,4 +424,7 @@ default S locales(Locale... locales) { */ @Incubating S localeResolver(LocaleResolver localeResolver); + + @Incubating + S beanMetaDataClassNormalizer(BeanMetaDataClassNormalizer beanMetaDataClassNormalizer); } diff --git a/engine/src/main/java/org/hibernate/validator/PredefinedScopeHibernateValidatorConfiguration.java b/engine/src/main/java/org/hibernate/validator/PredefinedScopeHibernateValidatorConfiguration.java index 50be18cc58..1f4a85118d 100644 --- a/engine/src/main/java/org/hibernate/validator/PredefinedScopeHibernateValidatorConfiguration.java +++ b/engine/src/main/java/org/hibernate/validator/PredefinedScopeHibernateValidatorConfiguration.java @@ -9,8 +9,6 @@ import java.util.Locale; import java.util.Set; -import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer; - /** * Extension of {@link HibernateValidatorConfiguration} with additional methods dedicated to defining the predefined * scope of bean validation e.g. validated classes, constraint validators... @@ -31,7 +29,4 @@ public interface PredefinedScopeHibernateValidatorConfiguration extends BaseHibe @Incubating @Deprecated PredefinedScopeHibernateValidatorConfiguration initializeLocales(Set locales); - - @Incubating - PredefinedScopeHibernateValidatorConfiguration beanMetaDataClassNormalizer(BeanMetaDataClassNormalizer beanMetaDataClassNormalizer); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/AbstractConfigurationImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/AbstractConfigurationImpl.java index 7c5d580c95..f2a4b11465 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/AbstractConfigurationImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/AbstractConfigurationImpl.java @@ -57,6 +57,7 @@ import org.hibernate.validator.internal.xml.config.ValidationBootstrapParameters; import org.hibernate.validator.internal.xml.config.ValidationXmlParser; import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator; +import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer; import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator; import org.hibernate.validator.spi.messageinterpolation.LocaleResolver; import org.hibernate.validator.spi.nodenameprovider.PropertyNodeNameProvider; @@ -118,6 +119,7 @@ public abstract class AbstractConfigurationImpl locales = Collections.emptySet(); private Locale defaultLocale = Locale.getDefault(); private LocaleResolver localeResolver; + private BeanMetaDataClassNormalizer beanMetaDataClassNormalizer; protected AbstractConfigurationImpl(BootstrapState state) { this(); @@ -597,6 +599,23 @@ public Set> getDefaultValueExtractors() { return ValueExtractorManager.getDefaultValueExtractors(); } + @Override + public T beanMetaDataClassNormalizer(BeanMetaDataClassNormalizer beanMetaDataClassNormalizer) { + if ( LOG.isDebugEnabled() ) { + if ( beanMetaDataClassNormalizer != null ) { + LOG.debug( "Setting custom BeanMetaDataClassNormalizer of type " + beanMetaDataClassNormalizer.getClass() + .getName() ); + } + } + this.beanMetaDataClassNormalizer = beanMetaDataClassNormalizer; + return thisAsT(); + } + + public BeanMetaDataClassNormalizer getBeanMetaDataClassNormalizer() { + return beanMetaDataClassNormalizer; + } + + public final Set getProgrammaticMappings() { return programmaticMappings; } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeConfigurationImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeConfigurationImpl.java index 7c8be7fa88..3b8aadca30 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeConfigurationImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeConfigurationImpl.java @@ -18,7 +18,6 @@ import org.hibernate.validator.PredefinedScopeHibernateValidatorConfiguration; import org.hibernate.validator.internal.util.CollectionHelper; import org.hibernate.validator.internal.util.Contracts; -import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer; /** * @author Guillaume Smet @@ -28,8 +27,6 @@ public class PredefinedScopeConfigurationImpl extends AbstractConfigurationImpl< private Set> beanClassesToInitialize; - private BeanMetaDataClassNormalizer beanMetaDataClassNormalizer; - public PredefinedScopeConfigurationImpl(BootstrapState state) { super( state ); } @@ -55,16 +52,6 @@ public PredefinedScopeHibernateValidatorConfiguration initializeLocales(Set hibernateSpecificConfig) { if ( hibernateSpecificConfig.getBeanMetaDataClassNormalizer() != null ) { return hibernateSpecificConfig.getBeanMetaDataClassNormalizer(); } diff --git a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java index 9fb2a7705b..581b597443 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/engine/ValidatorFactoryImpl.java @@ -9,6 +9,7 @@ import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineAllowMultipleCascadedValidationOnReturnValues; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineAllowOverridingMethodAlterParameterConstraint; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineAllowParallelMethodsDefineParameterConstraints; +import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineBeanMetaDataClassNormalizer; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintMappings; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineConstraintValidatorPayload; import static org.hibernate.validator.internal.engine.ValidatorFactoryConfigurationHelper.determineExternalClassLoader; @@ -59,6 +60,7 @@ import org.hibernate.validator.internal.util.logging.LoggerFactory; import org.hibernate.validator.internal.util.stereotypes.Immutable; import org.hibernate.validator.internal.util.stereotypes.ThreadSafe; +import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer; import org.hibernate.validator.spi.properties.GetterPropertySelectionStrategy; import org.hibernate.validator.spi.scripting.ScriptEvaluatorFactory; @@ -122,6 +124,8 @@ public class ValidatorFactoryImpl implements HibernateValidatorFactory { private final JavaBeanHelper javaBeanHelper; + private final BeanMetaDataClassNormalizer beanMetadataClassNormalizer; + private final ValidationOrderGenerator validationOrderGenerator; public ValidatorFactoryImpl(ConfigurationState configurationState) { @@ -171,6 +175,7 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) { this.executableHelper = new ExecutableHelper( typeResolutionHelper ); this.javaBeanHelper = new JavaBeanHelper( ValidatorFactoryConfigurationHelper.determineGetterPropertySelectionStrategy( hibernateSpecificConfig, properties, externalClassLoader ), ValidatorFactoryConfigurationHelper.determinePropertyNodeNameProvider( hibernateSpecificConfig, properties, externalClassLoader ) ); + this.beanMetadataClassNormalizer = determineBeanMetaDataClassNormalizer( hibernateSpecificConfig ); // HV-302; don't load XmlMappingParser if not necessary if ( configurationState.getMappingStreams().isEmpty() ) { @@ -306,6 +311,7 @@ Validator createValidator(ConstraintValidatorFactory constraintValidatorFactory, executableHelper, validatorFactoryScopedContext.getParameterNameProvider(), javaBeanHelper, + beanMetadataClassNormalizer, validationOrderGenerator, buildMetaDataProviders(), methodValidationConfiguration diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/BeanMetaDataManagerImpl.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/BeanMetaDataManagerImpl.java index 722d92aa2f..b9b3e6f8ae 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/BeanMetaDataManagerImpl.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/BeanMetaDataManagerImpl.java @@ -34,6 +34,7 @@ import org.hibernate.validator.internal.util.ExecutableParameterNameProvider; import org.hibernate.validator.internal.util.classhierarchy.ClassHierarchyHelper; import org.hibernate.validator.internal.util.stereotypes.Immutable; +import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer; /** * This manager is in charge of providing all constraint related meta data @@ -92,6 +93,8 @@ public class BeanMetaDataManagerImpl implements BeanMetaDataManager { */ private final ExecutableHelper executableHelper; + private final BeanMetaDataClassNormalizer beanMetaDataClassNormalizer; + private final ValidationOrderGenerator validationOrderGenerator; /** @@ -105,12 +108,14 @@ public BeanMetaDataManagerImpl(ConstraintCreationContext constraintCreationConte ExecutableHelper executableHelper, ExecutableParameterNameProvider parameterNameProvider, JavaBeanHelper javaBeanHelper, + BeanMetaDataClassNormalizer beanMetaDataClassNormalizer, ValidationOrderGenerator validationOrderGenerator, List optionalMetaDataProviders, MethodValidationConfiguration methodValidationConfiguration) { this.constraintCreationContext = constraintCreationContext; this.executableHelper = executableHelper; this.parameterNameProvider = parameterNameProvider; + this.beanMetaDataClassNormalizer = beanMetaDataClassNormalizer; this.validationOrderGenerator = validationOrderGenerator; this.methodValidationConfiguration = methodValidationConfiguration; @@ -142,26 +147,31 @@ public BeanMetaDataManagerImpl(ConstraintCreationContext constraintCreationConte } @Override + // TODO Some of these casts from BeanMetadata to BeanMetadata may not be safe. + // Maybe we should return a wrapper around the BeanMetadata if the normalized class is different from beanClass? @SuppressWarnings("unchecked") public BeanMetaData getBeanMetaData(Class beanClass) { Contracts.assertNotNull( beanClass, MESSAGES.beanTypeCannotBeNull() ); + Class normalizedBeanClass = beanMetaDataClassNormalizer.normalize( beanClass ); + // First, let's do a simple lookup as it's the default case - BeanMetaData beanMetaData = (BeanMetaData) beanMetaDataCache.get( beanClass ); + BeanMetaData beanMetaData = (BeanMetaData) beanMetaDataCache.get( normalizedBeanClass ); if ( beanMetaData != null ) { - return beanMetaData; + return (BeanMetaData) beanMetaData; } - beanMetaData = createBeanMetaData( beanClass ); - BeanMetaData previousBeanMetaData = (BeanMetaData) beanMetaDataCache.putIfAbsent( beanClass, beanMetaData ); + beanMetaData = createBeanMetaData( normalizedBeanClass ); + BeanMetaData previousBeanMetaData = + (BeanMetaData) beanMetaDataCache.putIfAbsent( normalizedBeanClass, beanMetaData ); // we return the previous value if not null if ( previousBeanMetaData != null ) { - return previousBeanMetaData; + return (BeanMetaData) previousBeanMetaData; } - return beanMetaData; + return (BeanMetaData) beanMetaData; } @Override diff --git a/engine/src/main/java/org/hibernate/validator/internal/metadata/DefaultBeanMetaDataClassNormalizer.java b/engine/src/main/java/org/hibernate/validator/internal/metadata/DefaultBeanMetaDataClassNormalizer.java index a40a987786..14e6e40f37 100644 --- a/engine/src/main/java/org/hibernate/validator/internal/metadata/DefaultBeanMetaDataClassNormalizer.java +++ b/engine/src/main/java/org/hibernate/validator/internal/metadata/DefaultBeanMetaDataClassNormalizer.java @@ -18,7 +18,7 @@ public class DefaultBeanMetaDataClassNormalizer implements BeanMetaDataClassNormalizer { @Override - public Class normalize(Class beanClass) { + public Class normalize(Class beanClass) { return beanClass; } } diff --git a/engine/src/main/java/org/hibernate/validator/metadata/BeanMetaDataClassNormalizer.java b/engine/src/main/java/org/hibernate/validator/metadata/BeanMetaDataClassNormalizer.java index e5d50b005f..a637f4c53b 100644 --- a/engine/src/main/java/org/hibernate/validator/metadata/BeanMetaDataClassNormalizer.java +++ b/engine/src/main/java/org/hibernate/validator/metadata/BeanMetaDataClassNormalizer.java @@ -35,5 +35,5 @@ public interface BeanMetaDataClassNormalizer { * @param beanClass the original bean class * @return the normalized class */ - Class normalize(Class beanClass); + Class normalize(Class beanClass); } diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/ValidatorFactoryBeanMetadataClassNormalizerTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/ValidatorFactoryBeanMetadataClassNormalizerTest.java new file mode 100644 index 0000000000..33e930c256 --- /dev/null +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/ValidatorFactoryBeanMetadataClassNormalizerTest.java @@ -0,0 +1,120 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.test.internal.engine; + +import static org.hibernate.validator.testutil.ConstraintViolationAssert.assertThat; +import static org.hibernate.validator.testutil.ConstraintViolationAssert.pathWith; +import static org.hibernate.validator.testutil.ConstraintViolationAssert.violationOf; + +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import javax.validation.ConstraintViolation; +import javax.validation.Validation; +import javax.validation.ValidationException; +import javax.validation.Validator; +import javax.validation.ValidatorFactory; +import javax.validation.constraints.Email; +import javax.validation.valueextraction.Unwrapping; + +import org.hibernate.validator.HibernateValidator; +import org.hibernate.validator.internal.engine.ValidatorFactoryImpl; +import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer; + +import org.testng.annotations.Test; + +/** + * Test for {@link ValidatorFactoryImpl}. + * + * @author Gunnar Morling + */ +public class ValidatorFactoryBeanMetadataClassNormalizerTest { + + @Test(expectedExceptions = ValidationException.class, + expectedExceptionsMessageRegExp = ".*No suitable value extractor found for type interface java.util.List.*") + public void testBeanMetaDataClassNormalizerNoNormalizer() throws NoSuchMethodException { + ValidatorFactory validatorFactory = Validation.byDefaultProvider() + .configure() + .buildValidatorFactory(); + + Validator validator = validatorFactory.getValidator(); + + // As the proxy defines invalid constraints (see BeanProxy), we expect this to fail + validator.forExecutables().validateParameters( + new BeanProxy(), BeanProxy.class.getMethod( "setEmails", List.class ), + new Object[] { Arrays.asList( "notAnEmail" ) } + ); + } + + @Test + public void testBeanMetaDataClassNormalizer() throws NoSuchMethodException { + ValidatorFactory validatorFactory = Validation.byProvider( HibernateValidator.class ) + .configure() + .beanMetaDataClassNormalizer( new MyProxyInterfaceBeanMetaDataClassNormalizer() ) + .buildValidatorFactory(); + + Validator validator = validatorFactory.getValidator(); + + Set> violations = validator.forExecutables().validateParameters( + new BeanProxy(), BeanProxy.class.getMethod( "setEmails", List.class ), + new Object[] { Arrays.asList( "notAnEmail" ) } + ); + + assertThat( violations ).containsOnlyViolations( + violationOf( Email.class ).withPropertyPath( + pathWith().method( "setEmails" ) + .parameter( "emails", 0 ) + .containerElement( "", true, null, 0, List.class, 0 ) + ) + ); + } + + private static class Bean { + + private List emails; + + public Bean() { + } + + public Bean(List emails) { + this.emails = emails; + } + + public List getEmails() { + return emails; + } + + public void setEmails(@Email(payload = Unwrapping.Unwrap.class) List emails) { + this.emails = emails; + } + } + + private interface MyProxyInterface { + } + + private static class MyProxyInterfaceBeanMetaDataClassNormalizer implements BeanMetaDataClassNormalizer { + + @Override + public Class normalize(Class beanClass) { + if ( MyProxyInterface.class.isAssignableFrom( beanClass ) ) { + return beanClass.getSuperclass(); + } + + return beanClass; + } + } + + private static class BeanProxy extends Bean implements MyProxyInterface { + // The proxy dropped the generics, but kept constraint annotations, + // which will cause trouble unless its metadata is ignored. + @Override + @SuppressWarnings("unchecked") + public void setEmails(@Email(payload = Unwrapping.Unwrap.class) List emails) { + super.setEmails( emails ); + } + } +} diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java index 6c4a45fcbd..074645b79f 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/engine/path/PathImplTest.java @@ -35,6 +35,7 @@ import org.hibernate.validator.internal.engine.path.PathImpl; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl; +import org.hibernate.validator.internal.metadata.DefaultBeanMetaDataClassNormalizer; import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; import org.hibernate.validator.internal.metadata.provider.MetaDataProvider; import org.hibernate.validator.internal.properties.DefaultGetterPropertySelectionStrategy; @@ -198,6 +199,7 @@ public void testCreationOfExecutablePath() throws Exception { new ExecutableHelper( new TypeResolutionHelper() ), new ExecutableParameterNameProvider( new DefaultParameterNameProvider() ), new JavaBeanHelper( new DefaultGetterPropertySelectionStrategy(), new DefaultPropertyNodeNameProvider() ), + new DefaultBeanMetaDataClassNormalizer(), new ValidationOrderGenerator(), Collections.emptyList(), new MethodValidationConfiguration.Builder().build() diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/BeanMetaDataManagerTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/BeanMetaDataManagerTest.java index 55ec5dd048..d83b3ce7fe 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/BeanMetaDataManagerTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/BeanMetaDataManagerTest.java @@ -26,6 +26,7 @@ import org.hibernate.validator.internal.engine.MethodValidationConfiguration; import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl; +import org.hibernate.validator.internal.metadata.DefaultBeanMetaDataClassNormalizer; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaDataImpl; import org.hibernate.validator.internal.metadata.provider.MetaDataProvider; @@ -57,6 +58,7 @@ public void setUpBeanMetaDataManager() { new ExecutableHelper( new TypeResolutionHelper() ), new ExecutableParameterNameProvider( new DefaultParameterNameProvider() ), new JavaBeanHelper( new DefaultGetterPropertySelectionStrategy(), new DefaultPropertyNodeNameProvider() ), + new DefaultBeanMetaDataClassNormalizer(), new ValidationOrderGenerator(), Collections.emptyList(), new MethodValidationConfiguration.Builder().build() diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ExecutableMetaDataTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ExecutableMetaDataTest.java index dee545145b..f8a24308ed 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ExecutableMetaDataTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ExecutableMetaDataTest.java @@ -29,6 +29,7 @@ import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl; +import org.hibernate.validator.internal.metadata.DefaultBeanMetaDataClassNormalizer; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; import org.hibernate.validator.internal.metadata.descriptor.ConstraintDescriptorImpl; @@ -63,6 +64,7 @@ public void setupBeanMetaData() { new ExecutableHelper( new TypeResolutionHelper() ), new ExecutableParameterNameProvider( new DefaultParameterNameProvider() ), new JavaBeanHelper( new DefaultGetterPropertySelectionStrategy(), new DefaultPropertyNodeNameProvider() ), + new DefaultBeanMetaDataClassNormalizer(), new ValidationOrderGenerator(), Collections.emptyList(), new MethodValidationConfiguration.Builder().build() diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ParameterMetaDataTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ParameterMetaDataTest.java index cb8b39c6f1..e9a97a095e 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ParameterMetaDataTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/ParameterMetaDataTest.java @@ -30,6 +30,7 @@ import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl; +import org.hibernate.validator.internal.metadata.DefaultBeanMetaDataClassNormalizer; import org.hibernate.validator.internal.metadata.aggregated.BeanMetaData; import org.hibernate.validator.internal.metadata.aggregated.ExecutableMetaData; import org.hibernate.validator.internal.metadata.aggregated.ParameterMetaData; @@ -63,6 +64,7 @@ public void setupBeanMetaData() { new ExecutableHelper( new TypeResolutionHelper() ), new ExecutableParameterNameProvider( new DefaultParameterNameProvider() ), new JavaBeanHelper( new DefaultGetterPropertySelectionStrategy(), new DefaultPropertyNodeNameProvider() ), + new DefaultBeanMetaDataClassNormalizer(), new ValidationOrderGenerator(), Collections.emptyList(), new MethodValidationConfiguration.Builder().build() @@ -132,6 +134,7 @@ public void parameterNameInInheritanceHierarchy() throws Exception { new ExecutableHelper( new TypeResolutionHelper() ), new ExecutableParameterNameProvider( new SkewedParameterNameProvider() ), new JavaBeanHelper( new DefaultGetterPropertySelectionStrategy(), new DefaultPropertyNodeNameProvider() ), + new DefaultBeanMetaDataClassNormalizer(), new ValidationOrderGenerator(), Collections.emptyList(), new MethodValidationConfiguration.Builder().build() diff --git a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/PropertyMetaDataTest.java b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/PropertyMetaDataTest.java index cd83158b14..f73cb77d7d 100644 --- a/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/PropertyMetaDataTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/internal/metadata/aggregated/PropertyMetaDataTest.java @@ -23,6 +23,7 @@ import org.hibernate.validator.internal.engine.groups.ValidationOrderGenerator; import org.hibernate.validator.internal.metadata.BeanMetaDataManager; import org.hibernate.validator.internal.metadata.BeanMetaDataManagerImpl; +import org.hibernate.validator.internal.metadata.DefaultBeanMetaDataClassNormalizer; import org.hibernate.validator.internal.metadata.aggregated.PropertyMetaData; import org.hibernate.validator.internal.metadata.provider.MetaDataProvider; import org.hibernate.validator.internal.properties.DefaultGetterPropertySelectionStrategy; @@ -47,6 +48,7 @@ public void setupBeanMetaDataManager() { new ExecutableHelper( new TypeResolutionHelper() ), new ExecutableParameterNameProvider( new DefaultParameterNameProvider() ), new JavaBeanHelper( new DefaultGetterPropertySelectionStrategy(), new DefaultPropertyNodeNameProvider() ), + new DefaultBeanMetaDataClassNormalizer(), new ValidationOrderGenerator(), Collections.emptyList(), new MethodValidationConfiguration.Builder().build() diff --git a/engine/src/test/java/org/hibernate/validator/test/predefinedscope/PredefinedScopeValidatorFactoryTest.java b/engine/src/test/java/org/hibernate/validator/test/predefinedscope/PredefinedScopeValidatorFactoryTest.java index a9309cc767..2e94a98448 100644 --- a/engine/src/test/java/org/hibernate/validator/test/predefinedscope/PredefinedScopeValidatorFactoryTest.java +++ b/engine/src/test/java/org/hibernate/validator/test/predefinedscope/PredefinedScopeValidatorFactoryTest.java @@ -455,7 +455,7 @@ private interface MyProxyInterface { private static class MyProxyInterfaceBeanMetaDataClassNormalizer implements BeanMetaDataClassNormalizer { @Override - public Class normalize(Class beanClass) { + public Class normalize(Class beanClass) { if ( MyProxyInterface.class.isAssignableFrom( beanClass ) ) { return beanClass.getSuperclass(); } diff --git a/integration/pom.xml b/integration/pom.xml index 0dcf4f75fe..28eac40278 100644 --- a/integration/pom.xml +++ b/integration/pom.xml @@ -42,6 +42,11 @@ assertj-core test + + io.rest-assured + rest-assured + test + log4j log4j @@ -89,6 +94,12 @@ jakarta.annotation-api test + + + jakarta.ws.rs + jakarta.ws.rs-api + provided + org.jboss.arquillian.testng arquillian-testng-container diff --git a/integration/src/test/java/org/hibernate/validator/integration/wildfly/ejb/EjbIT.java b/integration/src/test/java/org/hibernate/validator/integration/wildfly/ejb/EjbIT.java new file mode 100644 index 0000000000..da4fef6bb3 --- /dev/null +++ b/integration/src/test/java/org/hibernate/validator/integration/wildfly/ejb/EjbIT.java @@ -0,0 +1,75 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.integration.wildfly.ejb; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.equalTo; + +import org.hibernate.validator.integration.AbstractArquillianIT; + +import org.jboss.arquillian.container.test.api.Deployment; +import org.jboss.arquillian.container.test.api.RunAsClient; +import org.jboss.shrinkwrap.api.asset.EmptyAsset; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.WebArchive; +import org.jboss.shrinkwrap.descriptor.api.Descriptors; + +import io.restassured.filter.log.ErrorLoggingFilter; +import io.restassured.http.ContentType; +import org.testng.annotations.Test; + +/** + * This is a reproducer for WFL-11566, but fixing the problem requires changes in WildFly. + * Thus it is ignored for now. + * See HV-1754. + */ +public class EjbIT extends AbstractArquillianIT { + private static final String WAR_FILE_NAME = EjbIT.class.getSimpleName() + ".war"; + private static final String APPLICATION_PATH = EjbIT.class.getSimpleName(); + + @Deployment + public static WebArchive createTestArchive() throws Exception { + return buildTestArchive( WAR_FILE_NAME ) + .addClass( EjbJaxRsResource.class ) + .addClass( JaxRsApplication.class ) + .addAsWebInfResource( EmptyAsset.INSTANCE, "beans.xml" ) + .addAsWebInfResource( + new StringAsset( + Descriptors.create( org.jboss.shrinkwrap.descriptor.api.webapp31.WebAppDescriptor.class ) + .exportAsString() + ), + "web.xml" + ); + } + + @Test(enabled = false) + @RunAsClient + public void testRestEasyWorks() { + given() + .filter( new ErrorLoggingFilter() ) + .body( "[\"a\", \"b\", \"c\"]" ) + .contentType( ContentType.JSON ) + .post( APPLICATION_PATH + "/put/list/" ) + .then() + .statusCode( 200 ) + .body( equalTo( "Hello bars a, b, c" ) ); + } + + @Test(enabled = false) + @RunAsClient + public void testValidationWorks() { + given() + .filter( new ErrorLoggingFilter() ) + .body( "[]" ) + .contentType( ContentType.JSON ) + .post( APPLICATION_PATH + "/put/list/" ) + .then() + .statusCode( 400 ) + .body( containsString( "must not be empty" ) ); + } +} diff --git a/integration/src/test/java/org/hibernate/validator/integration/wildfly/ejb/EjbJaxRsResource.java b/integration/src/test/java/org/hibernate/validator/integration/wildfly/ejb/EjbJaxRsResource.java new file mode 100644 index 0000000000..777ae195f6 --- /dev/null +++ b/integration/src/test/java/org/hibernate/validator/integration/wildfly/ejb/EjbJaxRsResource.java @@ -0,0 +1,30 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.integration.wildfly.ejb; + +import java.util.List; +import java.util.stream.Collectors; +import javax.ejb.Stateless; +import javax.validation.constraints.NotEmpty; +import javax.ws.rs.Consumes; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.core.MediaType; + +@Stateless +@Path("/") +public class EjbJaxRsResource { + + @POST + @Path("put/list") + @Consumes(MediaType.APPLICATION_JSON) + public String putList(@NotEmpty List a) { + return "Hello bars " + a.stream().collect( Collectors.joining( ", " ) ); + } + +} + diff --git a/integration/src/test/java/org/hibernate/validator/integration/wildfly/ejb/JaxRsApplication.java b/integration/src/test/java/org/hibernate/validator/integration/wildfly/ejb/JaxRsApplication.java new file mode 100644 index 0000000000..9f1d7e830c --- /dev/null +++ b/integration/src/test/java/org/hibernate/validator/integration/wildfly/ejb/JaxRsApplication.java @@ -0,0 +1,20 @@ +/* + * Hibernate Validator, declare and validate application constraints + * + * License: Apache License, Version 2.0 + * See the license.txt file in the root directory or . + */ +package org.hibernate.validator.integration.wildfly.ejb; + +import java.util.Collections; +import java.util.Set; +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +@ApplicationPath("") +public class JaxRsApplication extends Application { + @Override + public Set> getClasses() { + return Collections.emptySet(); + } +} diff --git a/pom.xml b/pom.xml index 0c5b300d7d..7127963687 100644 --- a/pom.xml +++ b/pom.xml @@ -150,6 +150,7 @@ 3.2.5 1.2.4 1.3.5 + 2.1.6 1.0.1 @@ -168,6 +169,7 @@ 3.8.0 4.12 3.4 + 4.1.2 2.4.12 27.1-jre 4.3.10.RELEASE @@ -430,6 +432,11 @@ assertj-core ${version.org.assertj.assertj-core} + + io.rest-assured + rest-assured + ${version.io.rest-assured} + org.jboss.arquillian arquillian-bom @@ -442,6 +449,11 @@ jakarta.annotation-api ${version.jakarta.annotation-api} + + jakarta.ws.rs + jakarta.ws.rs-api + ${version.jakarta.ws.rs-api} + jakarta.interceptor jakarta.interceptor-api