• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

TAKETODAY / today-infrastructure / 18154768944

01 Oct 2025 07:26AM UTC coverage: 81.882% (-0.005%) from 81.887%
18154768944

push

github

web-flow
Merge pull request #290 from TAKETODAY/dev/jspecify

jspecify

59788 of 78013 branches covered (76.64%)

Branch coverage included in aggregate %.

141239 of 167496 relevant lines covered (84.32%)

3.6 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

81.55
today-context/src/main/java/infra/validation/beanvalidation/LocalValidatorFactoryBean.java
1
/*
2
 * Copyright 2017 - 2025 the original author or authors.
3
 *
4
 * This program is free software: you can redistribute it and/or modify
5
 * it under the terms of the GNU General Public License as published by
6
 * the Free Software Foundation, either version 3 of the License, or
7
 * (at your option) any later version.
8
 *
9
 * This program is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU General Public License
15
 * along with this program. If not, see [https://www.gnu.org/licenses/]
16
 */
17

18
package infra.validation.beanvalidation;
19

20
import org.hibernate.validator.messageinterpolation.ResourceBundleMessageInterpolator;
21
import org.jspecify.annotations.Nullable;
22

23
import java.io.IOException;
24
import java.io.InputStream;
25
import java.lang.reflect.Constructor;
26
import java.lang.reflect.Method;
27
import java.util.ArrayList;
28
import java.util.Arrays;
29
import java.util.HashMap;
30
import java.util.List;
31
import java.util.Map;
32
import java.util.Properties;
33
import java.util.function.Consumer;
34

35
import infra.beans.factory.DisposableBean;
36
import infra.beans.factory.InitializingBean;
37
import infra.context.ApplicationContext;
38
import infra.context.ApplicationContextAware;
39
import infra.context.MessageSource;
40
import infra.context.support.AbstractMessageSource;
41
import infra.core.DefaultParameterNameDiscoverer;
42
import infra.core.ParameterNameDiscoverer;
43
import infra.core.io.Resource;
44
import infra.lang.Assert;
45
import infra.lang.VisibleForTesting;
46
import infra.util.CollectionUtils;
47
import infra.util.ReflectionUtils;
48
import jakarta.validation.ClockProvider;
49
import jakarta.validation.Configuration;
50
import jakarta.validation.ConstraintValidatorFactory;
51
import jakarta.validation.MessageInterpolator;
52
import jakarta.validation.ParameterNameProvider;
53
import jakarta.validation.TraversableResolver;
54
import jakarta.validation.Validation;
55
import jakarta.validation.ValidationException;
56
import jakarta.validation.ValidationProviderResolver;
57
import jakarta.validation.Validator;
58
import jakarta.validation.ValidatorContext;
59
import jakarta.validation.ValidatorFactory;
60
import jakarta.validation.bootstrap.GenericBootstrap;
61
import jakarta.validation.bootstrap.ProviderSpecificBootstrap;
62

63
/**
64
 * This is the central class for {@code jakarta.validation} (JSR-303) setup in a Framework
65
 * application context: It bootstraps a {@code jakarta.validation.ValidationFactory} and
66
 * exposes it through the Framework {@link infra.validation.Validator} interface
67
 * as well as through the JSR-303 {@link Validator} interface and the
68
 * {@link ValidatorFactory} interface itself.
69
 *
70
 * <p>When talking to an instance of this bean through the Framework or JSR-303 Validator interfaces,
71
 * you'll be talking to the default Validator of the underlying ValidatorFactory. This is very
72
 * convenient in that you don't have to perform yet another call on the factory, assuming that
73
 * you will almost always use the default Validator anyway. This can also be injected directly
74
 * into any target dependency of type {@link infra.validation.Validator}!
75
 *
76
 * <p>This class is also being used by Framework's MVC configuration namespace, in case of the
77
 * {@code jakarta.validation} API being present but no explicit Validator having been configured.
78
 *
79
 * @author Juergen Hoeller
80
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
81
 * @see ValidatorFactory
82
 * @see Validator
83
 * @see Validation#buildDefaultValidatorFactory()
84
 * @see ValidatorFactory#getValidator()
85
 * @since 4.0
86
 */
87
public class LocalValidatorFactoryBean extends InfraValidatorAdapter
2✔
88
        implements ValidatorFactory, ApplicationContextAware, InitializingBean, DisposableBean {
89

90
  @SuppressWarnings("rawtypes")
91
  @Nullable
92
  private Class providerClass;
93

94
  @Nullable
95
  private ValidationProviderResolver validationProviderResolver;
96

97
  @Nullable
98
  private MessageInterpolator messageInterpolator;
99

100
  @Nullable
101
  private TraversableResolver traversableResolver;
102

103
  @Nullable
104
  private ConstraintValidatorFactory constraintValidatorFactory;
105

106
  @Nullable
1✔
107
  private ParameterNameDiscoverer parameterNameDiscoverer = ParameterNameDiscoverer.getSharedInstance();
2✔
108

109
  private Resource @Nullable [] mappingLocations;
110

111
  private final Map<String, String> validationPropertyMap = new HashMap<>();
6✔
112

113
  @Nullable
114
  private Consumer<Configuration<?>> configurationInitializer;
115

116
  @Nullable
117
  private ApplicationContext applicationContext;
118

119
  @Nullable
120
  @VisibleForTesting
121
  ValidatorFactory validatorFactory;
122

123
  /**
124
   * Specify the desired provider class, if any.
125
   * <p>If not specified, JSR-303's default search mechanism will be used.
126
   *
127
   * @see Validation#byProvider(Class)
128
   * @see Validation#byDefaultProvider()
129
   */
130
  @SuppressWarnings("rawtypes")
131
  public void setProviderClass(Class providerClass) {
132
    this.providerClass = providerClass;
3✔
133
  }
1✔
134

135
  /**
136
   * Specify a JSR-303 {@link ValidationProviderResolver} for bootstrapping the
137
   * provider of choice, as an alternative to {@code META-INF} driven resolution.
138
   */
139
  public void setValidationProviderResolver(ValidationProviderResolver validationProviderResolver) {
140
    this.validationProviderResolver = validationProviderResolver;
×
141
  }
×
142

143
  /**
144
   * Specify a custom MessageInterpolator to use for this ValidatorFactory
145
   * and its exposed default Validator.
146
   */
147
  public void setMessageInterpolator(@Nullable MessageInterpolator messageInterpolator) {
148
    this.messageInterpolator = messageInterpolator;
3✔
149
  }
1✔
150

151
  /**
152
   * Specify a custom Framework MessageSource for resolving validation messages,
153
   * instead of relying on JSR-303's default "ValidationMessages.properties" bundle
154
   * in the classpath. This may refer to a Framework context's shared "messageSource" bean,
155
   * or to some special MessageSource setup for validation purposes only.
156
   * <p><b>NOTE:</b> This feature requires Hibernate Validator 4.3 or higher on the classpath.
157
   * You may nevertheless use a different validation provider but Hibernate Validator's
158
   * {@link ResourceBundleMessageInterpolator} class must be accessible during configuration.
159
   * <p>Specify either this property or {@link #setMessageInterpolator "messageInterpolator"},
160
   * not both. If you would like to build a custom MessageInterpolator, consider deriving from
161
   * Hibernate Validator's {@link ResourceBundleMessageInterpolator} and passing in a
162
   * Framework-based {@code ResourceBundleLocator} when constructing your interpolator.
163
   * <p>In order for Hibernate's default validation messages to be resolved still, your
164
   * {@link MessageSource} must be configured for optional resolution (usually the default).
165
   * In particular, the {@code MessageSource} instance specified here should not apply
166
   * {@link AbstractMessageSource#setUseCodeAsDefaultMessage
167
   * "useCodeAsDefaultMessage"} behavior. Please double-check your setup accordingly.
168
   *
169
   * @see ResourceBundleMessageInterpolator
170
   */
171
  public void setValidationMessageSource(MessageSource messageSource) {
172
    this.messageInterpolator = HibernateValidatorDelegate.buildMessageInterpolator(messageSource);
4✔
173
  }
1✔
174

175
  /**
176
   * Specify a custom TraversableResolver to use for this ValidatorFactory
177
   * and its exposed default Validator.
178
   */
179
  public void setTraversableResolver(@Nullable TraversableResolver traversableResolver) {
180
    this.traversableResolver = traversableResolver;
3✔
181
  }
1✔
182

183
  /**
184
   * Specify a custom ConstraintValidatorFactory to use for this ValidatorFactory.
185
   * <p>Default is a {@link ContextConstraintValidatorFactory}, delegating to the
186
   * containing ApplicationContext for creating autowired ConstraintValidator instances.
187
   */
188
  public void setConstraintValidatorFactory(@Nullable ConstraintValidatorFactory constraintValidatorFactory) {
189
    this.constraintValidatorFactory = constraintValidatorFactory;
3✔
190
  }
1✔
191

192
  /**
193
   * Set the ParameterNameDiscoverer to use for resolving method and constructor
194
   * parameter names if needed for message interpolation.
195
   * <p>Default is a {@link DefaultParameterNameDiscoverer}.
196
   */
197
  public void setParameterNameDiscoverer(@Nullable ParameterNameDiscoverer parameterNameDiscoverer) {
198
    this.parameterNameDiscoverer = parameterNameDiscoverer;
3✔
199
  }
1✔
200

201
  /**
202
   * Specify resource locations to load XML constraint mapping files from, if any.
203
   */
204
  public void setMappingLocations(Resource... mappingLocations) {
205
    this.mappingLocations = mappingLocations;
3✔
206
  }
1✔
207

208
  /**
209
   * Specify bean validation properties to be passed to the validation provider.
210
   * <p>Can be populated with a String "value" (parsed via PropertiesEditor)
211
   * or a "props" element in XML bean definitions.
212
   *
213
   * @see Configuration#addProperty(String, String)
214
   */
215
  public void setValidationProperties(Properties jpaProperties) {
216
    CollectionUtils.mergePropertiesIntoMap(jpaProperties, this.validationPropertyMap);
4✔
217
  }
1✔
218

219
  /**
220
   * Specify bean validation properties to be passed to the validation provider as a Map.
221
   * <p>Can be populated with a "map" or "props" element in XML bean definitions.
222
   *
223
   * @see Configuration#addProperty(String, String)
224
   */
225
  public void setValidationPropertyMap(@Nullable Map<String, String> validationProperties) {
226
    if (validationProperties != null) {
2!
227
      this.validationPropertyMap.putAll(validationProperties);
4✔
228
    }
229
  }
1✔
230

231
  /**
232
   * Allow Map access to the bean validation properties to be passed to the validation provider,
233
   * with the option to add or override specific entries.
234
   * <p>Useful for specifying entries directly, for example via "validationPropertyMap[myKey]".
235
   */
236
  public Map<String, String> getValidationPropertyMap() {
237
    return this.validationPropertyMap;
3✔
238
  }
239

240
  /**
241
   * Specify a callback for customizing the Bean Validation {@code Configuration} instance,
242
   * as an alternative to overriding the {@link #postProcessConfiguration(Configuration)}
243
   * method in custom {@code LocalValidatorFactoryBean} subclasses.
244
   * <p>This enables convenient customizations for application purposes. Infrastructure
245
   * extensions may keep overriding the {@link #postProcessConfiguration} template method.
246
   */
247
  public void setConfigurationInitializer(Consumer<Configuration<?>> configurationInitializer) {
248
    this.configurationInitializer = configurationInitializer;
3✔
249
  }
1✔
250

251
  @Override
252
  public void setApplicationContext(@Nullable ApplicationContext applicationContext) {
253
    this.applicationContext = applicationContext;
3✔
254
  }
1✔
255

256
  @Override
257
  @SuppressWarnings({ "rawtypes", "unchecked" })
258
  public void afterPropertiesSet() {
259
    Configuration<?> configuration;
260
    if (this.providerClass != null) {
3✔
261
      ProviderSpecificBootstrap bootstrap = Validation.byProvider(this.providerClass);
4✔
262
      if (this.validationProviderResolver != null) {
3!
263
        bootstrap = bootstrap.providerResolver(this.validationProviderResolver);
×
264
      }
265
      configuration = bootstrap.configure();
3✔
266
    }
1✔
267
    else {
268
      GenericBootstrap bootstrap = Validation.byDefaultProvider();
2✔
269
      if (this.validationProviderResolver != null) {
3!
270
        bootstrap = bootstrap.providerResolver(this.validationProviderResolver);
×
271
      }
272
      configuration = bootstrap.configure();
3✔
273
    }
274

275
    // Try Hibernate Validator 5.2's externalClassLoader(ClassLoader) method
276
    if (this.applicationContext != null) {
3✔
277
      try {
278
        Method eclMethod = configuration.getClass().getMethod("externalClassLoader", ClassLoader.class);
11✔
279
        ReflectionUtils.invokeMethod(eclMethod, configuration, this.applicationContext.getClassLoader());
12✔
280
      }
281
      catch (NoSuchMethodException ex) {
×
282
        // Ignore - no Hibernate Validator 5.2+ or similar provider
283
      }
1✔
284
    }
285

286
    MessageInterpolator targetInterpolator = this.messageInterpolator;
3✔
287
    if (targetInterpolator == null) {
2✔
288
      targetInterpolator = configuration.getDefaultMessageInterpolator();
3✔
289
    }
290
    configuration.messageInterpolator(new LocaleContextMessageInterpolator(targetInterpolator));
7✔
291

292
    if (this.traversableResolver != null) {
3✔
293
      configuration.traversableResolver(this.traversableResolver);
5✔
294
    }
295

296
    ConstraintValidatorFactory targetConstraintValidatorFactory = this.constraintValidatorFactory;
3✔
297
    if (targetConstraintValidatorFactory == null && this.applicationContext != null) {
5✔
298
      targetConstraintValidatorFactory =
4✔
299
              new ContextConstraintValidatorFactory(this.applicationContext.getAutowireCapableBeanFactory());
3✔
300
    }
301
    if (targetConstraintValidatorFactory != null) {
2✔
302
      configuration.constraintValidatorFactory(targetConstraintValidatorFactory);
4✔
303
    }
304

305
    if (this.parameterNameDiscoverer != null) {
3!
306
      configureParameterNameProvider(this.parameterNameDiscoverer, configuration);
5✔
307
    }
308

309
    List<InputStream> mappingStreams = null;
2✔
310
    if (this.mappingLocations != null) {
3✔
311
      mappingStreams = new ArrayList<>(this.mappingLocations.length);
7✔
312
      for (Resource location : this.mappingLocations) {
15!
313
        try {
314
          InputStream stream = location.getInputStream();
×
315
          mappingStreams.add(stream);
×
316
          configuration.addMapping(stream);
×
317
        }
318
        catch (IOException ex) {
1✔
319
          closeMappingStreams(mappingStreams);
3✔
320
          throw new IllegalStateException("Cannot read mapping resource: " + location);
7✔
321
        }
×
322
      }
323
    }
324

325
    this.validationPropertyMap.forEach(configuration::addProperty);
8✔
326

327
    // Allow for custom post-processing before we actually build the ValidatorFactory.
328
    if (this.configurationInitializer != null) {
3✔
329
      this.configurationInitializer.accept(configuration);
4✔
330
    }
331
    postProcessConfiguration(configuration);
3✔
332

333
    try {
334
      this.validatorFactory = configuration.buildValidatorFactory();
4✔
335
      setTargetValidator(this.validatorFactory.getValidator());
5✔
336
    }
337
    finally {
338
      closeMappingStreams(mappingStreams);
3✔
339
    }
340
  }
1✔
341

342
  private void configureParameterNameProvider(ParameterNameDiscoverer discoverer, Configuration<?> configuration) {
343
    final ParameterNameProvider defaultProvider = configuration.getDefaultParameterNameProvider();
3✔
344
    configuration.parameterNameProvider(new ParameterNameProvider() {
21✔
345
      @Override
346
      public List<String> getParameterNames(Constructor<?> constructor) {
347
        String[] paramNames = discoverer.getParameterNames(constructor);
5✔
348
        return (paramNames != null ? Arrays.asList(paramNames) :
6!
349
                defaultProvider.getParameterNames(constructor));
×
350
      }
351

352
      @Override
353
      public List<String> getParameterNames(Method method) {
354
        String[] paramNames = discoverer.getParameterNames(method);
5✔
355
        return (paramNames != null ? Arrays.asList(paramNames) :
6✔
356
                defaultProvider.getParameterNames(method));
4✔
357
      }
358
    });
359
  }
1✔
360

361
  private void closeMappingStreams(@Nullable List<InputStream> mappingStreams) {
362
    if (CollectionUtils.isNotEmpty(mappingStreams)) {
3!
363
      for (InputStream stream : mappingStreams) {
×
364
        try {
365
          stream.close();
×
366
        }
367
        catch (IOException ignored) {
×
368
        }
×
369
      }
×
370
    }
371
  }
1✔
372

373
  /**
374
   * Post-process the given Bean Validation configuration,
375
   * adding to or overriding any of its settings.
376
   * <p>Invoked right before building the {@link ValidatorFactory}.
377
   *
378
   * @param configuration the Configuration object, pre-populated with
379
   * settings driven by LocalValidatorFactoryBean's properties
380
   */
381
  protected void postProcessConfiguration(Configuration<?> configuration) {
382
  }
1✔
383

384
  @Override
385
  public Validator getValidator() {
386
    return validatorFactory().getValidator();
4✔
387
  }
388

389
  @Override
390
  public ValidatorContext usingContext() {
391
    return validatorFactory().usingContext();
4✔
392
  }
393

394
  @Override
395
  public MessageInterpolator getMessageInterpolator() {
396
    return validatorFactory().getMessageInterpolator();
4✔
397
  }
398

399
  @Override
400
  public TraversableResolver getTraversableResolver() {
401
    return validatorFactory().getTraversableResolver();
4✔
402
  }
403

404
  @Override
405
  public ConstraintValidatorFactory getConstraintValidatorFactory() {
406
    return validatorFactory().getConstraintValidatorFactory();
4✔
407
  }
408

409
  @Override
410
  public ParameterNameProvider getParameterNameProvider() {
411
    return validatorFactory().getParameterNameProvider();
4✔
412
  }
413

414
  private ValidatorFactory validatorFactory() {
415
    Assert.state(validatorFactory != null, "No target ValidatorFactory set");
7!
416
    return validatorFactory;
3✔
417
  }
418

419
  @Override
420
  public ClockProvider getClockProvider() {
421
    return validatorFactory().getClockProvider();
4✔
422
  }
423

424
  @Override
425
  @SuppressWarnings("unchecked")
426
  public <T> T unwrap(@Nullable Class<T> type) {
427
    if (type == null || !ValidatorFactory.class.isAssignableFrom(type)) {
6!
428
      try {
429
        return super.unwrap(type);
4✔
430
      }
431
      catch (ValidationException ex) {
1✔
432
        // Ignore - we'll try ValidatorFactory unwrapping next
433
      }
434
    }
435
    if (this.validatorFactory != null) {
3!
436
      try {
437
        return this.validatorFactory.unwrap(type);
5✔
438
      }
439
      catch (ValidationException ex) {
1✔
440
        // Ignore if just being asked for ValidatorFactory
441
        if (ValidatorFactory.class == type) {
3!
442
          return (T) this.validatorFactory;
×
443
        }
444
        throw ex;
2✔
445
      }
446
    }
447
    throw new ValidationException("Cannot unwrap to " + type);
×
448
  }
449

450
  @Override
451
  public void close() {
452
    if (this.validatorFactory != null) {
3!
453
      this.validatorFactory.close();
3✔
454
    }
455
  }
1✔
456

457
  @Override
458
  public void destroy() {
459
    close();
2✔
460
  }
1✔
461

462
  /**
463
   * Inner class to avoid a hard-coded Hibernate Validator dependency.
464
   */
465
  private static final class HibernateValidatorDelegate {
466

467
    public static MessageInterpolator buildMessageInterpolator(MessageSource messageSource) {
468
      return new ResourceBundleMessageInterpolator(new MessageSourceResourceBundleLocator(messageSource));
8✔
469
    }
470
  }
471

472
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc