Skip to content

Commit e0d4b9d

Browse files
committed
HV-2031 Make PredefinedScopeHibernateValidatorFactory aware of constraint mappings defined purely in XML
1 parent 97f8505 commit e0d4b9d

File tree

6 files changed

+163
-44
lines changed

6 files changed

+163
-44
lines changed

engine/src/main/java/org/hibernate/validator/internal/engine/PredefinedScopeValidatorFactoryImpl.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
import java.lang.invoke.MethodHandles;
3030
import java.time.Duration;
3131
import java.util.Collections;
32+
import java.util.HashSet;
3233
import java.util.List;
3334
import java.util.Map;
3435
import java.util.Set;
@@ -188,9 +189,12 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState
188189
// or from programmatic mappings
189190
registerCustomConstraintValidators( constraintMappings, constraintHelper );
190191

192+
Set<Class<?>> beanClassesToInitialize = new HashSet<>( hibernateSpecificConfig.getBeanClassesToInitialize() );
193+
191194
XmlMetaDataProvider xmlMetaDataProvider;
192195
if ( mappingParser != null && mappingParser.createConstrainedElements() ) {
193196
xmlMetaDataProvider = new XmlMetaDataProvider( mappingParser );
197+
beanClassesToInitialize.addAll( xmlMetaDataProvider.configuredBeanClasses() );
194198
}
195199
else {
196200
xmlMetaDataProvider = null;
@@ -205,7 +209,7 @@ public PredefinedScopeValidatorFactoryImpl(ConfigurationState configurationState
205209
buildMetaDataProviders( constraintCreationContext, xmlMetaDataProvider, constraintMappings ),
206210
methodValidationConfiguration,
207211
determineBeanMetaDataClassNormalizer( hibernateSpecificConfig ),
208-
hibernateSpecificConfig.getBeanClassesToInitialize()
212+
beanClassesToInitialize
209213
);
210214

211215
if ( LOG.isDebugEnabled() ) {

engine/src/main/java/org/hibernate/validator/internal/metadata/core/ConstraintHelper.java

Lines changed: 95 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@
105105
import jakarta.validation.constraints.Size;
106106
import jakarta.validation.constraintvalidation.ValidationTarget;
107107

108+
import org.hibernate.validator.cfg.ConstraintMapping;
108109
import org.hibernate.validator.constraints.BitcoinAddress;
109110
import org.hibernate.validator.constraints.CodePointLength;
110111
import org.hibernate.validator.constraints.ConstraintComposition;
@@ -372,7 +373,7 @@
372373
* @author Gunnar Morling
373374
* @author Guillaume Smet
374375
*/
375-
public class ConstraintHelper {
376+
public abstract class ConstraintHelper {
376377

377378
public static final String GROUPS = "groups";
378379
public static final String PAYLOAD = "payload";
@@ -385,9 +386,6 @@ public class ConstraintHelper {
385386
private static final String JODA_TIME_CLASS_NAME = "org.joda.time.ReadableInstant";
386387
private static final String JAVA_MONEY_CLASS_NAME = "javax.money.MonetaryAmount";
387388

388-
@Immutable
389-
private final Map<Class<? extends Annotation>, List<? extends ConstraintValidatorDescriptor<?>>> enabledBuiltinConstraints;
390-
391389
private final ConcurrentMap<Class<? extends Annotation>, Boolean> externalConstraints = new ConcurrentHashMap<>();
392390

393391
private final ConcurrentMap<Class<? extends Annotation>, Boolean> multiValueConstraints = new ConcurrentHashMap<>();
@@ -399,21 +397,20 @@ public class ConstraintHelper {
399397
private Boolean jodaTimeInClassPath;
400398

401399
public static ConstraintHelper forAllBuiltinConstraints() {
402-
return new ConstraintHelper( new HashSet<>( Arrays.asList( BuiltinConstraint.values() ) ) );
400+
return new StaticConstraintHelper();
403401
}
404402

405403
public static ConstraintHelper forBuiltinConstraints(Set<String> enabledConstraints) {
406-
return new ConstraintHelper( BuiltinConstraint.resolve( enabledConstraints ) );
404+
return new DynamicConstraintHelper( BuiltinConstraint.resolve( enabledConstraints ) );
407405
}
408406

409407
@SuppressWarnings("deprecation")
410-
private ConstraintHelper(Set<BuiltinConstraint> enabledBuiltinConstraints) {
408+
protected Map<Class<? extends Annotation>, List<? extends ConstraintValidatorDescriptor<?>>> resolve(Set<BuiltinConstraint> enabledBuiltinConstraints) {
411409
if ( enabledBuiltinConstraints.isEmpty() ) {
412-
this.enabledBuiltinConstraints = Collections.emptyMap();
413-
return;
410+
return Collections.emptyMap();
414411
}
415412

416-
Map<Class<? extends Annotation>, List<ConstraintValidatorDescriptor<?>>> tmpConstraints = new HashMap<>();
413+
Map<Class<? extends Annotation>, List<? extends ConstraintValidatorDescriptor<?>>> tmpConstraints = new HashMap<>();
417414

418415
// Bean Validation constraints
419416

@@ -832,21 +829,27 @@ private ConstraintHelper(Set<BuiltinConstraint> enabledBuiltinConstraints) {
832829
putBuiltinConstraint( tmpConstraints, BitcoinAddress.class, BitcoinAddressValidator.class );
833830
}
834831

835-
this.enabledBuiltinConstraints = Collections.unmodifiableMap( tmpConstraints );
832+
return tmpConstraints;
836833
}
837834

838-
private static <A extends Annotation> void putBuiltinConstraint(Map<Class<? extends Annotation>, List<ConstraintValidatorDescriptor<?>>> validators,
839-
Class<A> constraintType) {
835+
private static <A extends Annotation> void putBuiltinConstraint(
836+
Map<Class<? extends Annotation>, List<? extends ConstraintValidatorDescriptor<?>>> validators,
837+
Class<A> constraintType
838+
) {
840839
validators.put( constraintType, Collections.emptyList() );
841840
}
842841

843-
private static <A extends Annotation> void putBuiltinConstraint(Map<Class<? extends Annotation>, List<ConstraintValidatorDescriptor<?>>> validators,
844-
Class<A> constraintType, Class<? extends ConstraintValidator<A, ?>> validatorType) {
842+
private static <A extends Annotation> void putBuiltinConstraint(
843+
Map<Class<? extends Annotation>, List<? extends ConstraintValidatorDescriptor<?>>> validators,
844+
Class<A> constraintType, Class<? extends ConstraintValidator<A, ?>> validatorType
845+
) {
845846
validators.put( constraintType, Collections.singletonList( ConstraintValidatorDescriptor.forBuiltinClass( validatorType, constraintType ) ) );
846847
}
847848

848-
private static <A extends Annotation> void putBuiltinConstraints(Map<Class<? extends Annotation>, List<ConstraintValidatorDescriptor<?>>> validators,
849-
Class<A> constraintType, List<Class<? extends ConstraintValidator<A, ?>>> validatorTypes) {
849+
private static <A extends Annotation> void putBuiltinConstraints(
850+
Map<Class<? extends Annotation>, List<? extends ConstraintValidatorDescriptor<?>>> validators,
851+
Class<A> constraintType, List<Class<? extends ConstraintValidator<A, ?>>> validatorTypes
852+
) {
850853
List<ConstraintValidatorDescriptor<?>> descriptors = new ArrayList<>( validatorTypes.size() );
851854

852855
for ( Class<? extends ConstraintValidator<A, ?>> validatorType : validatorTypes ) {
@@ -873,14 +876,13 @@ public static Set<String> getBuiltinConstraints() {
873876
* <li>internally registered validators for built-in constraints</li>
874877
* <li>XML configuration and</li>
875878
* <li>programmatically registered validators (see
876-
* {@link org.hibernate.validator.cfg.ConstraintMapping#constraintDefinition(Class)}).</li>
879+
* {@link ConstraintMapping#constraintDefinition(Class)}).</li>
877880
* </ul>
878881
*
879882
* The result is cached internally.
880883
*
881884
* @param annotationType The constraint annotation type.
882885
* @param <A> the type of the annotation
883-
*
884886
* @return The validator classes for the given type.
885887
*/
886888
public <A extends Annotation> List<ConstraintValidatorDescriptor<A>> getAllValidatorDescriptors(Class<A> annotationType) {
@@ -895,7 +897,6 @@ public <A extends Annotation> List<ConstraintValidatorDescriptor<A>> getAllValid
895897
* @param annotationType The annotation of interest.
896898
* @param validationTarget The target, either annotated element or parameters.
897899
* @param <A> the type of the annotation
898-
*
899900
* @return A list with matching validator descriptors.
900901
*/
901902
public <A extends Annotation> List<ConstraintValidatorDescriptor<A>> findValidatorDescriptors(Class<A> annotationType, ValidationTarget validationTarget) {
@@ -937,9 +938,8 @@ public <A extends Annotation> void putValidatorDescriptors(Class<A> annotationTy
937938
* Checks whether a given annotation is a multi value constraint or not.
938939
*
939940
* @param annotationType the annotation type to check.
940-
*
941941
* @return {@code true} if the specified annotation is a multi value constraints, {@code false}
942-
* otherwise.
942+
* otherwise.
943943
*/
944944
public boolean isMultiValueConstraint(Class<? extends Annotation> annotationType) {
945945
if ( isJdkAnnotation( annotationType ) ) {
@@ -974,7 +974,6 @@ public boolean isMultiValueConstraint(Class<? extends Annotation> annotationType
974974
*
975975
* @param multiValueConstraint the multi-value constraint annotation from which to retrieve the contained constraints
976976
* @param <A> the type of the annotation
977-
*
978977
* @return A list of constraint annotations, may be empty but never {@code null}.
979978
*/
980979
public <A extends Annotation> List<Annotation> getConstraintsFromMultiValueConstraint(A multiValueConstraint) {
@@ -997,7 +996,6 @@ public <A extends Annotation> List<Annotation> getConstraintsFromMultiValueConst
997996
* </ul>
998997
*
999998
* @param annotationType The annotation type to test.
1000-
*
1001999
* @return {@code true} if the annotation fulfills the above conditions, {@code false} otherwise.
10021000
*/
10031001
public boolean isConstraintAnnotation(Class<? extends Annotation> annotationType) {
@@ -1139,32 +1137,84 @@ private boolean isJavaMoneyInClasspath() {
11391137
* Returns the default validators for the given constraint type.
11401138
*
11411139
* @param annotationType The constraint annotation type.
1142-
*
11431140
* @return A list with the default validators as retrieved from
1144-
* {@link Constraint#validatedBy()} or the list of validators for
1145-
* built-in constraints.
1141+
* {@link Constraint#validatedBy()} or the list of validators for
1142+
* built-in constraints.
11461143
*/
11471144
@SuppressWarnings("unchecked")
1148-
private <A extends Annotation> List<ConstraintValidatorDescriptor<A>> getDefaultValidatorDescriptors(Class<A> annotationType) {
1149-
//safe cause all CV for a given annotation A are CV<A, ?>
1150-
final List<ConstraintValidatorDescriptor<A>> builtInValidators = (List<ConstraintValidatorDescriptor<A>>) enabledBuiltinConstraints
1151-
.get( annotationType );
1145+
protected abstract <A extends Annotation> List<ConstraintValidatorDescriptor<A>> getDefaultValidatorDescriptors(Class<A> annotationType);
11521146

1153-
if ( builtInValidators != null ) {
1154-
return builtInValidators;
1147+
private static boolean isClassPresent(String className) {
1148+
return IsClassPresent.action( className, ConstraintHelper.class.getClassLoader() );
1149+
}
1150+
1151+
private static class StaticConstraintHelper extends ConstraintHelper {
1152+
1153+
@Immutable
1154+
private final Map<Class<? extends Annotation>, List<? extends ConstraintValidatorDescriptor<?>>> enabledBuiltinConstraints;
1155+
1156+
private StaticConstraintHelper() {
1157+
this.enabledBuiltinConstraints = Collections.unmodifiableMap( resolve( new HashSet<>( Arrays.asList( BuiltinConstraint.values() ) ) ) );
11551158
}
11561159

1157-
Class<? extends ConstraintValidator<A, ?>>[] validatedBy = (Class<? extends ConstraintValidator<A, ?>>[]) annotationType
1158-
.getAnnotation( Constraint.class )
1159-
.validatedBy();
1160+
@SuppressWarnings("unchecked")
1161+
@Override
1162+
protected <A extends Annotation> List<ConstraintValidatorDescriptor<A>> getDefaultValidatorDescriptors(Class<A> annotationType) {
1163+
//safe cause all CV for a given annotation A are CV<A, ?>
1164+
final List<ConstraintValidatorDescriptor<A>> builtInValidators = (List<ConstraintValidatorDescriptor<A>>) enabledBuiltinConstraints
1165+
.get( annotationType );
1166+
1167+
if ( builtInValidators != null ) {
1168+
return builtInValidators;
1169+
}
1170+
1171+
Class<? extends ConstraintValidator<A, ?>>[] validatedBy = (Class<? extends ConstraintValidator<A, ?>>[]) annotationType
1172+
.getAnnotation( Constraint.class )
1173+
.validatedBy();
11601174

1161-
return Stream.of( validatedBy )
1162-
.map( c -> ConstraintValidatorDescriptor.forClass( c, annotationType ) )
1163-
.collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) );
1175+
return Stream.of( validatedBy )
1176+
.map( c -> ConstraintValidatorDescriptor.forClass( c, annotationType ) )
1177+
.collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) );
1178+
}
11641179
}
11651180

1166-
private static boolean isClassPresent(String className) {
1167-
return IsClassPresent.action( className, ConstraintHelper.class.getClassLoader() );
1181+
private static class DynamicConstraintHelper extends ConstraintHelper {
1182+
1183+
@Immutable
1184+
private final Map<Class<? extends Annotation>, List<? extends ConstraintValidatorDescriptor<?>>> enabledBuiltinConstraints;
1185+
1186+
private DynamicConstraintHelper(Set<BuiltinConstraint> initialConstraints) {
1187+
this.enabledBuiltinConstraints = new HashMap<>( resolve( initialConstraints ) );
1188+
}
1189+
1190+
@SuppressWarnings("unchecked")
1191+
@Override
1192+
protected <A extends Annotation> List<ConstraintValidatorDescriptor<A>> getDefaultValidatorDescriptors(Class<A> annotationType) {
1193+
//safe cause all CV for a given annotation A are CV<A, ?>
1194+
final List<ConstraintValidatorDescriptor<A>> builtInValidators = (List<ConstraintValidatorDescriptor<A>>) enabledBuiltinConstraints
1195+
.get( annotationType );
1196+
1197+
if ( builtInValidators != null ) {
1198+
return builtInValidators;
1199+
}
1200+
else {
1201+
// let's give it a try and see if we can find this constraint among the built-in ones:
1202+
Set<BuiltinConstraint> builtinConstraints = BuiltinConstraint.resolve( Collections.singleton( annotationType.getName() ) );
1203+
if ( !builtinConstraints.isEmpty() ) {
1204+
Map<Class<? extends Annotation>, List<? extends ConstraintValidatorDescriptor<?>>> additionalDescriptors = resolve( builtinConstraints );
1205+
enabledBuiltinConstraints.putAll( additionalDescriptors );
1206+
return (List<ConstraintValidatorDescriptor<A>>) additionalDescriptors.get( annotationType );
1207+
}
1208+
}
1209+
1210+
Class<? extends ConstraintValidator<A, ?>>[] validatedBy = (Class<? extends ConstraintValidator<A, ?>>[]) annotationType
1211+
.getAnnotation( Constraint.class )
1212+
.validatedBy();
1213+
1214+
return Stream.of( validatedBy )
1215+
.map( c -> ConstraintValidatorDescriptor.forClass( c, annotationType ) )
1216+
.collect( Collectors.collectingAndThen( Collectors.toList(), CollectionHelper::toImmutableList ) );
1217+
}
11681218
}
11691219

11701220
/**
@@ -1183,8 +1233,10 @@ private <A extends Annotation> void put(Class<A> annotationType, List<Constraint
11831233
constraintValidatorDescriptors.put( annotationType, validatorDescriptors );
11841234
}
11851235

1186-
private <A extends Annotation> List<ConstraintValidatorDescriptor<A>> computeIfAbsent(Class<A> annotationType,
1187-
Function<? super Class<A>, List<ConstraintValidatorDescriptor<A>>> mappingFunction) {
1236+
private <A extends Annotation> List<ConstraintValidatorDescriptor<A>> computeIfAbsent(
1237+
Class<A> annotationType,
1238+
Function<? super Class<A>, List<ConstraintValidatorDescriptor<A>>> mappingFunction
1239+
) {
11881240
return (List<ConstraintValidatorDescriptor<A>>) constraintValidatorDescriptors.computeIfAbsent(
11891241
annotationType,
11901242
(Function<? super Class<? extends Annotation>, ? extends List<? extends ConstraintValidatorDescriptor<?>>>) mappingFunction

engine/src/main/java/org/hibernate/validator/internal/metadata/provider/XmlMetaDataProvider.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import java.util.HashMap;
1010
import java.util.Map;
1111
import java.util.Set;
12+
import java.util.stream.Collectors;
1213

1314
import org.hibernate.validator.internal.metadata.core.AnnotationProcessingOptions;
1415
import org.hibernate.validator.internal.metadata.raw.BeanConfiguration;
@@ -66,4 +67,9 @@ public <T> BeanConfiguration<T> getBeanConfiguration(Class<T> beanClass) {
6667
public AnnotationProcessingOptions getAnnotationProcessingOptions() {
6768
return annotationProcessingOptions;
6869
}
70+
71+
public Set<Class<?>> configuredBeanClasses() {
72+
return configuredBeans.values().stream().map( BeanConfiguration::getBeanClass )
73+
.collect( Collectors.toSet() );
74+
}
6975
}

engine/src/test/java/org/hibernate/validator/test/predefinedscope/PredefinedScopeValidatorFactoryTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
import jakarta.validation.ValidatorFactory;
3535
import jakarta.validation.constraints.Email;
3636
import jakarta.validation.constraints.NotNull;
37+
import jakarta.validation.constraints.Positive;
3738

3839
import org.hibernate.validator.PredefinedScopeHibernateValidator;
3940
import org.hibernate.validator.metadata.BeanMetaDataClassNormalizer;
@@ -328,6 +329,29 @@ public String interpolate(String messageTemplate, Context context) {
328329
assertThat( violations ).containsOnlyViolations( violationOf( NotNull.class ).withMessage( "another string" ) );
329330
}
330331

332+
@Test
333+
public void testXmlDefinedConstraints() {
334+
// we assume that all the metadata is defined in the xml,
335+
// hence there is no built-in constraints nor beans to init:
336+
try (
337+
ValidatorFactory factory = Validation.byProvider( PredefinedScopeHibernateValidator.class )
338+
.configure()
339+
.builtinConstraints( Collections.emptySet() )
340+
.initializeBeanMetaData( Collections.emptySet() )
341+
.addMapping( PredefinedScopeValidatorFactoryTest.class.getResourceAsStream( "constraints-simplexmlbean.xml" ) )
342+
.buildValidatorFactory()
343+
) {
344+
Validator validator = factory.getValidator();
345+
346+
assertThat( validator.validate( new SimpleXmlBean() ) )
347+
.containsOnlyViolations(
348+
violationOf( Positive.class ).withMessage( "must be greater than 0" ),
349+
violationOf( NotNull.class ).withMessage( "must not be null" )
350+
);
351+
}
352+
353+
}
354+
331355
private static ValidatorFactory getValidatorFactory() {
332356
Set<Class<?>> beanMetaDataToInitialize = new HashSet<>();
333357
beanMetaDataToInitialize.add( Bean.class );
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/*
2+
* Hibernate Validator, declare and validate application constraints
3+
*
4+
* License: Apache License, Version 2.0
5+
* See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
6+
*/
7+
package org.hibernate.validator.test.predefinedscope;
8+
9+
public class SimpleXmlBean {
10+
public int id;
11+
public String name;
12+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
~ Hibernate Validator, declare and validate application constraints
4+
~
5+
~ License: Apache License, Version 2.0
6+
~ See the license.txt file in the root directory or <http://www.apache.org/licenses/LICENSE-2.0>.
7+
-->
8+
<constraint-mappings
9+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
10+
xsi:schemaLocation="https://jakarta.ee/xml/ns/validation/mapping https://jakarta.ee/xml/ns/validation/validation-mapping-3.0.xsd"
11+
xmlns="https://jakarta.ee/xml/ns/validation/mapping" version="3.0">
12+
13+
<bean class="org.hibernate.validator.test.predefinedscope.SimpleXmlBean">
14+
<field name="id">
15+
<constraint annotation="jakarta.validation.constraints.Positive"/>
16+
</field>
17+
<field name="name">
18+
<constraint annotation="jakarta.validation.constraints.NotNull"/>
19+
</field>
20+
</bean>
21+
</constraint-mappings>

0 commit comments

Comments
 (0)