Skip to content

Commit c1b0d74

Browse files
committed
HV-828 Introducing ConstraintDefinitionContributor to allow for a pluggable discovery of additional constraint validator implementations.
1 parent be7a5a1 commit c1b0d74

19 files changed

+1052
-7
lines changed

engine/pom.xml

+1
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,7 @@
138138
<filtering>true</filtering>
139139
<directory>src/test/resources</directory>
140140
<includes>
141+
<include>META-INF/services/*</include>
141142
<include>**/*.properties</include>
142143
<include>**/*.xml</include>
143144
</includes>

engine/src/main/java/org/hibernate/validator/HibernateValidatorConfiguration.java

+23
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import javax.validation.Configuration;
2020

2121
import org.hibernate.validator.cfg.ConstraintMapping;
22+
import org.hibernate.validator.spi.constraintdefinition.ConstraintDefinitionContributor;
2223
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
2324
import org.hibernate.validator.spi.valuehandling.ValidatedValueUnwrapper;
2425

@@ -44,6 +45,12 @@ public interface HibernateValidatorConfiguration extends Configuration<Hibernate
4445
*/
4546
String VALIDATED_VALUE_HANDLERS = "hibernate.validator.validated_value_handlers";
4647

48+
/**
49+
* Property corresponding to the {@link #addConstraintDefinitionContributor(org.hibernate.validator.spi.constraintdefinition.ConstraintDefinitionContributor)}
50+
* method. Accepts a String with the comma-separated fully-qualified names of one or more {@link org.hibernate.validator.spi.constraintdefinition.ConstraintDefinitionContributor} implementations.
51+
*/
52+
String CONSTRAINT_DEFINITION_CONTRIBUTORS = "hibernate.validator.constraint_definition_contributors";
53+
4754
/**
4855
* <p>
4956
* Returns the {@link ResourceBundleLocator} used by the
@@ -118,4 +125,20 @@ public interface HibernateValidatorConfiguration extends Configuration<Hibernate
118125
* @hv.experimental This API is considered experimental and may change in future revisions
119126
*/
120127
HibernateValidatorConfiguration addValidatedValueHandler(ValidatedValueUnwrapper<?> handler);
128+
129+
/**
130+
* @return the default {@link org.hibernate.validator.spi.constraintdefinition.ConstraintDefinitionContributor}. Never {@code null}.
131+
*/
132+
ConstraintDefinitionContributor getDefaultConstraintDefinitionContributor();
133+
134+
/**
135+
* Registers the given {@code ConstraintDefinitionContributor} with the bootstrapped validator factory.
136+
*
137+
* @param contributor the {@code ConstraintDefinitionContributor} to register. Cannot be {@code null}.
138+
*
139+
* @return {@code this} following the chaining method pattern
140+
*
141+
* @hv.experimental This API is considered experimental and may change in future revisions
142+
*/
143+
HibernateValidatorConfiguration addConstraintDefinitionContributor(ConstraintDefinitionContributor contributor);
121144
}

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

+32-4
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@
2424
import java.util.List;
2525
import java.util.Map;
2626
import java.util.Set;
27-
2827
import javax.validation.BootstrapConfiguration;
2928
import javax.validation.ConstraintValidatorFactory;
3029
import javax.validation.MessageInterpolator;
@@ -55,6 +54,7 @@
5554
import org.hibernate.validator.internal.xml.ValidationXmlParser;
5655
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
5756
import org.hibernate.validator.resourceloading.PlatformResourceBundleLocator;
57+
import org.hibernate.validator.spi.constraintdefinition.ConstraintDefinitionContributor;
5858
import org.hibernate.validator.spi.resourceloading.ResourceBundleLocator;
5959
import org.hibernate.validator.spi.valuehandling.ValidatedValueUnwrapper;
6060

@@ -81,6 +81,7 @@ public class ConfigurationImpl implements HibernateValidatorConfiguration, Confi
8181
private final TraversableResolver defaultTraversableResolver;
8282
private final ConstraintValidatorFactory defaultConstraintValidatorFactory;
8383
private final ParameterNameProvider defaultParameterNameProvider;
84+
private final ConstraintDefinitionContributor defaultConstraintDefinitionContributor;
8485

8586
private ValidationProviderResolver providerResolver;
8687
private final ValidationBootstrapParameters validationBootstrapParameters;
@@ -126,11 +127,17 @@ private ConfigurationImpl() {
126127
)
127128
);
128129
}
129-
this.defaultResourceBundleLocator = new PlatformResourceBundleLocator( ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES );
130+
this.defaultResourceBundleLocator = new PlatformResourceBundleLocator(
131+
ResourceBundleMessageInterpolator.USER_VALIDATION_MESSAGES
132+
);
130133
this.defaultTraversableResolver = new DefaultTraversableResolver();
131134
this.defaultConstraintValidatorFactory = new ConstraintValidatorFactoryImpl();
132135
this.defaultParameterNameProvider = new DefaultParameterNameProvider();
133136
this.defaultMessageInterpolator = new ResourceBundleMessageInterpolator( defaultResourceBundleLocator );
137+
this.defaultConstraintDefinitionContributor = new ServiceLoaderBasedConstraintDefinitionContributor(
138+
typeResolutionHelper
139+
);
140+
this.addConstraintDefinitionContributor( defaultConstraintDefinitionContributor );
134141
}
135142

136143
@Override
@@ -232,6 +239,23 @@ public HibernateValidatorConfiguration addValidatedValueHandler(ValidatedValueUn
232239
return this;
233240
}
234241

242+
@Override
243+
public ConstraintDefinitionContributor getDefaultConstraintDefinitionContributor() {
244+
return defaultConstraintDefinitionContributor;
245+
}
246+
247+
public Set<ConstraintDefinitionContributor> getConstraintDefinitionContributors() {
248+
return validationBootstrapParameters.getConstraintDefinitionContributors();
249+
}
250+
251+
@Override
252+
public HibernateValidatorConfiguration addConstraintDefinitionContributor(ConstraintDefinitionContributor contributor) {
253+
Contracts.assertNotNull( contributor, MESSAGES.parameterMustNotBeNull( "contributor" ) );
254+
validationBootstrapParameters.addConstraintDefinitionContributor( contributor );
255+
256+
return this;
257+
}
258+
235259
@Override
236260
public final ValidatorFactory buildValidatorFactory() {
237261
parseValidationXml();
@@ -381,7 +405,9 @@ private void parseValidationXml() {
381405
}
382406
}
383407
else {
384-
ValidationBootstrapParameters xmlParameters = new ValidationBootstrapParameters( getBootstrapConfiguration() );
408+
ValidationBootstrapParameters xmlParameters = new ValidationBootstrapParameters(
409+
getBootstrapConfiguration()
410+
);
385411
applyXmlSettings( xmlParameters );
386412
}
387413
}
@@ -409,7 +435,9 @@ private void applyXmlSettings(ValidationBootstrapParameters xmlParameters) {
409435

410436
if ( validationBootstrapParameters.getConstraintValidatorFactory() == null ) {
411437
if ( xmlParameters.getConstraintValidatorFactory() != null ) {
412-
validationBootstrapParameters.setConstraintValidatorFactory( xmlParameters.getConstraintValidatorFactory() );
438+
validationBootstrapParameters.setConstraintValidatorFactory(
439+
xmlParameters.getConstraintValidatorFactory()
440+
);
413441
}
414442
else {
415443
validationBootstrapParameters.setConstraintValidatorFactory( defaultConstraintValidatorFactory );
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* JBoss, Home of Professional Open Source
3+
* Copyright 2010, Red Hat, Inc. and/or its affiliates, and individual contributors
4+
* by the @authors tag. See the copyright.txt in the distribution for a
5+
* full listing of individual contributors.
6+
*
7+
* Licensed under the Apache License, Version 2.0 (the "License");
8+
* you may not use this file except in compliance with the License.
9+
* You may obtain a copy of the License at
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.hibernate.validator.internal.engine;
18+
19+
import java.lang.annotation.Annotation;
20+
import java.security.AccessController;
21+
import java.security.PrivilegedAction;
22+
import java.util.ArrayList;
23+
import java.util.List;
24+
import java.util.Map;
25+
import javax.validation.ConstraintValidator;
26+
27+
import com.fasterxml.classmate.ResolvedType;
28+
29+
import org.hibernate.validator.internal.util.TypeResolutionHelper;
30+
import org.hibernate.validator.internal.util.privilegedactions.GetConstraintValidatorList;
31+
import org.hibernate.validator.spi.constraintdefinition.ConstraintDefinitionContributor;
32+
33+
import static org.hibernate.validator.internal.util.CollectionHelper.newHashMap;
34+
35+
/**
36+
* @author Hardy Ferentschik
37+
*/
38+
public class ServiceLoaderBasedConstraintDefinitionContributor implements ConstraintDefinitionContributor {
39+
/**
40+
* Used for resolving type parameters. Thread-safe.
41+
*/
42+
private final TypeResolutionHelper typeResolutionHelper;
43+
44+
public ServiceLoaderBasedConstraintDefinitionContributor(TypeResolutionHelper typeResolutionHelper) {
45+
this.typeResolutionHelper = typeResolutionHelper;
46+
}
47+
48+
@Override
49+
public void collectConstraintDefinitions(ConstraintDefinitionBuilder constraintDefinitionContributionBuilder) {
50+
Map<Class<?>, List<Class<?>>> customValidators = newHashMap();
51+
52+
// find additional constraint validators via the Java ServiceLoader mechanism
53+
GetConstraintValidatorList constraintValidatorListAction = new GetConstraintValidatorList();
54+
List<ConstraintValidator<?, ?>> discoveredConstraintValidators = run( constraintValidatorListAction );
55+
56+
for ( ConstraintValidator<?, ?> constraintValidator : discoveredConstraintValidators ) {
57+
Class<?> constraintValidatorClass = constraintValidator.getClass();
58+
Class<?> annotationType = determineAnnotationType( constraintValidatorClass );
59+
60+
List<Class<?>> validators = customValidators.get( annotationType );
61+
if ( annotationType != null && validators == null ) {
62+
validators = new ArrayList<Class<?>>();
63+
customValidators.put( annotationType, validators );
64+
}
65+
validators.add( constraintValidatorClass );
66+
}
67+
68+
for ( Map.Entry<Class<?>, List<Class<?>>> entry : customValidators.entrySet() ) {
69+
registerConstraintDefinition( constraintDefinitionContributionBuilder, entry.getKey(), entry.getValue() );
70+
}
71+
}
72+
73+
@SuppressWarnings("unchecked")
74+
private <A extends Annotation> void registerConstraintDefinition(ConstraintDefinitionBuilder builder,
75+
Class<?> constraintType,
76+
List<Class<?>> validatorTypes) {
77+
ConstraintDefinitionBuilderContext<A> context = builder
78+
.constraint( (Class<A>) constraintType )
79+
.includeExistingValidators( true );
80+
81+
for ( Class<?> validatorType : validatorTypes ) {
82+
context.validatedBy( (Class<? extends ConstraintValidator<A, ?>>) validatorType );
83+
}
84+
}
85+
86+
private Class<?> determineAnnotationType(Class<?> constraintValidatorClass) {
87+
ResolvedType resolvedType = typeResolutionHelper.getTypeResolver()
88+
.resolve( constraintValidatorClass );
89+
90+
return resolvedType.typeParametersFor( ConstraintValidator.class ).get( 0 ).getErasedType();
91+
}
92+
93+
/**
94+
* Runs the given privileged action, using a privileged block if required.
95+
* <p>
96+
* <b>NOTE:</b> This must never be changed into a publicly available method to avoid execution of arbitrary
97+
* privileged actions within HV's protection domain.
98+
*/
99+
private <T> T run(PrivilegedAction<T> action) {
100+
return System.getSecurityManager() != null ? AccessController.doPrivileged( action ) : action.run();
101+
}
102+
}
103+
104+

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

+70-1
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,15 @@
1616
*/
1717
package org.hibernate.validator.internal.engine;
1818

19+
import java.lang.annotation.Annotation;
1920
import java.security.AccessController;
2021
import java.security.PrivilegedAction;
2122
import java.util.Collections;
2223
import java.util.IdentityHashMap;
2324
import java.util.List;
2425
import java.util.Map;
2526
import java.util.Set;
26-
27+
import javax.validation.ConstraintValidator;
2728
import javax.validation.ConstraintValidatorFactory;
2829
import javax.validation.MessageInterpolator;
2930
import javax.validation.ParameterNameProvider;
@@ -35,6 +36,7 @@
3536
import org.hibernate.validator.HibernateValidatorContext;
3637
import org.hibernate.validator.HibernateValidatorFactory;
3738
import org.hibernate.validator.internal.cfg.context.DefaultConstraintMapping;
39+
import org.hibernate.validator.internal.engine.constraintdefinition.ConstraintDefinitionBuilderImpl;
3840
import org.hibernate.validator.internal.engine.constraintvalidation.ConstraintValidatorManager;
3941
import org.hibernate.validator.internal.metadata.BeanMetaDataManager;
4042
import org.hibernate.validator.internal.metadata.core.ConstraintHelper;
@@ -48,6 +50,8 @@
4850
import org.hibernate.validator.internal.util.privilegedactions.LoadClass;
4951
import org.hibernate.validator.internal.util.privilegedactions.NewInstance;
5052
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
53+
import org.hibernate.validator.internal.engine.constraintdefinition.ConstraintDefinitionContribution;
54+
import org.hibernate.validator.spi.constraintdefinition.ConstraintDefinitionContributor;
5155
import org.hibernate.validator.spi.valuehandling.ValidatedValueUnwrapper;
5256

5357
import static org.hibernate.validator.internal.util.CollectionHelper.newArrayList;
@@ -167,6 +171,7 @@ public ValidatorFactoryImpl(ConfigurationState configurationState) {
167171

168172
tmpValidatedValueHandlers.addAll( hibernateSpecificConfig.getValidatedValueHandlers() );
169173

174+
registerCustomConstraintValidators( hibernateSpecificConfig, properties );
170175
}
171176
this.constraintMappings = Collections.unmodifiableSet( tmpConstraintMappings );
172177

@@ -348,6 +353,70 @@ private List<ValidatedValueUnwrapper<?>> getPropertyConfiguredValidatedValueHand
348353
return handlers;
349354
}
350355

356+
/**
357+
* Returns a list with {@link org.hibernate.validator.spi.constraintdefinition.ConstraintDefinitionContributor} instances configured via the
358+
* {@link HibernateValidatorConfiguration#CONSTRAINT_DEFINITION_CONTRIBUTORS} property.
359+
*
360+
* @param properties the properties used to bootstrap the factory
361+
*
362+
* @return a list with property-configured {@link org.hibernate.validator.spi.constraintdefinition.ConstraintDefinitionContributor}s. May be empty but never {@code null}.
363+
*/
364+
private List<ConstraintDefinitionContributor> getPropertyConfiguredConstraintDefinitionContributors(
365+
Map<String, String> properties) {
366+
String propertyValue = properties.get( HibernateValidatorConfiguration.CONSTRAINT_DEFINITION_CONTRIBUTORS );
367+
368+
if ( propertyValue == null || propertyValue.isEmpty() ) {
369+
return Collections.emptyList();
370+
}
371+
372+
String[] constraintDefinitionContributorNames = propertyValue.split( "," );
373+
List<ConstraintDefinitionContributor> constraintDefinitionContributors = newArrayList(
374+
constraintDefinitionContributorNames.length
375+
);
376+
377+
for ( String fqcn : constraintDefinitionContributorNames ) {
378+
@SuppressWarnings("unchecked")
379+
Class<ConstraintDefinitionContributor> contributorType = (Class<ConstraintDefinitionContributor>)
380+
run( LoadClass.action( fqcn, ValidatorFactoryImpl.class ) );
381+
constraintDefinitionContributors.add(
382+
run( NewInstance.action( contributorType, "constraint definition contributor class" ) )
383+
);
384+
}
385+
386+
return constraintDefinitionContributors;
387+
}
388+
389+
private void registerCustomConstraintValidators(ConfigurationImpl hibernateSpecificConfig,
390+
Map<String, String> properties) {
391+
for ( ConstraintDefinitionContributor contributor : hibernateSpecificConfig.getConstraintDefinitionContributors() ) {
392+
registerConstraintValidators( contributor );
393+
}
394+
395+
for ( ConstraintDefinitionContributor contributor : getPropertyConfiguredConstraintDefinitionContributors(
396+
properties
397+
) ) {
398+
registerConstraintValidators( contributor );
399+
}
400+
}
401+
402+
private <A extends Annotation> void registerConstraintValidators(ConstraintDefinitionContributor contributor) {
403+
ConstraintDefinitionBuilderImpl builder = new ConstraintDefinitionBuilderImpl();
404+
contributor.collectConstraintDefinitions( builder );
405+
List<ConstraintDefinitionContribution<?>> constraintDefinitionContributions = builder.getConstraintValidatorContributions();
406+
for ( ConstraintDefinitionContribution<?> constraintDefinitionContribution : constraintDefinitionContributions ) {
407+
@SuppressWarnings("unchecked")
408+
Class<A> constraintType = (Class<A>) constraintDefinitionContribution.getConstraintType();
409+
@SuppressWarnings("unchecked")
410+
List<Class<? extends ConstraintValidator<A, ?>>> constraintValidatorTypes = (List) constraintDefinitionContribution
411+
.getConstraintValidators();
412+
constraintHelper.putValidatorClasses(
413+
constraintType,
414+
constraintValidatorTypes,
415+
constraintDefinitionContribution.keepDefaults()
416+
);
417+
}
418+
}
419+
351420
/**
352421
* Runs the given privileged action, using a privileged block if required.
353422
* <p>

0 commit comments

Comments
 (0)