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

TAKETODAY / today-infrastructure / 20389107108

20 Dec 2025 04:23AM UTC coverage: 84.42% (+0.007%) from 84.413%
20389107108

push

github

TAKETODAY
:art: Expose compiler warnings in CompilationException

This commit improves TestCompiler to expose both errors and warnings
instead of an opaque message. When compilation fails, both errors and
warnings are displayed.

This is particularly useful when combined with the `-Werror` option
that turns the presence of a warning into an error.

61898 of 78371 branches covered (78.98%)

Branch coverage included in aggregate %.

145969 of 167860 relevant lines covered (86.96%)

3.71 hits per line

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

81.66
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 InfraConstraintValidatorFactory}, 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
        eclMethod = ReflectionUtils.getPubliclyAccessibleMethodIfPossible(eclMethod, configuration.getClass());
5✔
280
        ReflectionUtils.invokeMethod(eclMethod, configuration, this.applicationContext.getClassLoader());
12✔
281
      }
282
      catch (NoSuchMethodException ex) {
×
283
        // Ignore - no Hibernate Validator 5.2+ or similar provider
284
      }
1✔
285
    }
286

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

© 2025 Coveralls, Inc