• 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

93.79
today-context/src/main/java/infra/context/properties/ConfigurationPropertiesBinder.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.context.properties;
19

20
import org.jspecify.annotations.Nullable;
21

22
import java.util.ArrayList;
23
import java.util.List;
24
import java.util.function.Consumer;
25

26
import infra.beans.BeansException;
27
import infra.beans.PropertyEditorRegistry;
28
import infra.beans.factory.BeanFactory;
29
import infra.beans.factory.FactoryBean;
30
import infra.beans.factory.config.BeanDefinition;
31
import infra.beans.factory.support.BeanDefinitionBuilder;
32
import infra.beans.factory.support.BeanDefinitionRegistry;
33
import infra.context.ApplicationContext;
34
import infra.context.ApplicationContextAware;
35
import infra.context.ConfigurableApplicationContext;
36
import infra.context.properties.bind.AbstractBindHandler;
37
import infra.context.properties.bind.BindContext;
38
import infra.context.properties.bind.BindHandler;
39
import infra.context.properties.bind.BindResult;
40
import infra.context.properties.bind.Bindable;
41
import infra.context.properties.bind.Binder;
42
import infra.context.properties.bind.BoundPropertiesTrackingBindHandler;
43
import infra.context.properties.bind.PropertySourcesPlaceholdersResolver;
44
import infra.context.properties.bind.handler.IgnoreErrorsBindHandler;
45
import infra.context.properties.bind.handler.IgnoreTopLevelConverterNotFoundBindHandler;
46
import infra.context.properties.bind.handler.NoUnboundElementsBindHandler;
47
import infra.context.properties.bind.validation.ValidationBindHandler;
48
import infra.context.properties.source.ConfigurationPropertyName;
49
import infra.context.properties.source.ConfigurationPropertySource;
50
import infra.context.properties.source.ConfigurationPropertySources;
51
import infra.context.properties.source.UnboundElementsSourceFilter;
52
import infra.core.annotation.MergedAnnotations;
53
import infra.core.conversion.ConversionService;
54
import infra.core.env.PropertySources;
55
import infra.lang.Assert;
56
import infra.validation.Errors;
57
import infra.validation.Validator;
58
import infra.validation.annotation.Validated;
59

60
/**
61
 * Internal class used by the {@link ConfigurationPropertiesBindingPostProcessor} to
62
 * handle the actual {@link ConfigurationProperties @ConfigurationProperties} binding.
63
 *
64
 * @author Stephane Nicoll
65
 * @author Phillip Webb
66
 * @author <a href="https://github.com/TAKETODAY">Harry Yang</a>
67
 * @since 4.0
68
 */
69
class ConfigurationPropertiesBinder {
70

71
  private static final String BEAN_NAME = "infra.context.internalConfigurationPropertiesBinder";
72

73
  private static final String VALIDATOR_BEAN_NAME = EnableConfigurationProperties.VALIDATOR_BEAN_NAME;
74

75
  private final ApplicationContext applicationContext;
76

77
  private final PropertySources propertySources;
78

79
  @Nullable
80
  private final Validator configurationPropertiesValidator;
81

82
  private final boolean jsr303Present;
83

84
  @Nullable
85
  private volatile Binder binder;
86

87
  @Nullable
88
  private volatile List<ConfigurationPropertiesBindHandlerAdvisor> bindHandlerAdvisors;
89

90
  ConfigurationPropertiesBinder(ApplicationContext context) {
2✔
91
    this.applicationContext = context;
3✔
92
    this.propertySources = new PropertySourcesDeducer(context).getPropertySources();
7✔
93
    this.configurationPropertiesValidator = getConfigurationPropertiesValidator(context);
5✔
94
    this.jsr303Present = ConfigurationPropertiesJsr303Validator.isJsr303Present(context);
4✔
95
  }
1✔
96

97
  BindResult<?> bind(ConfigurationPropertiesBean propertiesBean) {
98
    Bindable<?> target = propertiesBean.asBindTarget();
3✔
99
    ConfigurationProperties annotation = propertiesBean.getAnnotation();
3✔
100
    BindHandler bindHandler = getBindHandler(target, annotation);
5✔
101
    return getBinder().bind(annotation.prefix(), target, bindHandler);
8✔
102
  }
103

104
  Object bindOrCreate(ConfigurationPropertiesBean propertiesBean) {
105
    Bindable<?> target = propertiesBean.asBindTarget();
3✔
106
    ConfigurationProperties annotation = propertiesBean.getAnnotation();
3✔
107
    BindHandler bindHandler = getBindHandler(target, annotation);
5✔
108
    return getBinder().bindOrCreate(annotation.prefix(), target, bindHandler);
8✔
109
  }
110

111
  @Nullable
112
  private Validator getConfigurationPropertiesValidator(ApplicationContext applicationContext) {
113
    if (applicationContext.containsBean(VALIDATOR_BEAN_NAME)) {
4✔
114
      return applicationContext.getBean(VALIDATOR_BEAN_NAME, Validator.class);
6✔
115
    }
116
    return null;
2✔
117
  }
118

119
  private <T> BindHandler getBindHandler(Bindable<T> target, ConfigurationProperties annotation) {
120
    var validators = getValidators(target);
4✔
121
    BindHandler handler = getHandler();
3✔
122
    handler = new ConfigurationPropertiesBindHandler(handler);
5✔
123
    if (annotation.ignoreInvalidFields()) {
3✔
124
      handler = new IgnoreErrorsBindHandler(handler);
5✔
125
    }
126
    if (!annotation.ignoreUnknownFields()) {
3✔
127
      UnboundElementsSourceFilter filter = new UnboundElementsSourceFilter();
4✔
128
      handler = new NoUnboundElementsBindHandler(handler, filter);
6✔
129
    }
130
    if (!validators.isEmpty()) {
3✔
131
      handler = new ValidationBindHandler(handler, validators.toArray(new Validator[0]));
10✔
132
    }
133
    for (ConfigurationPropertiesBindHandlerAdvisor advisor : getBindHandlerAdvisors()) {
11✔
134
      handler = advisor.apply(handler);
4✔
135
    }
1✔
136
    return handler;
2✔
137
  }
138

139
  private IgnoreTopLevelConverterNotFoundBindHandler getHandler() {
140
    BoundConfigurationProperties bound = BoundConfigurationProperties.get(applicationContext);
4✔
141
    return bound != null
3!
142
            ? new IgnoreTopLevelConverterNotFoundBindHandler(new BoundPropertiesTrackingBindHandler(bound::add))
12✔
143
            : new IgnoreTopLevelConverterNotFoundBindHandler();
×
144
  }
145

146
  @SuppressWarnings("NullAway")
147
  private ArrayList<Validator> getValidators(Bindable<?> target) {
148
    ArrayList<Validator> validators = new ArrayList<>(3);
5✔
149
    if (configurationPropertiesValidator != null) {
3✔
150
      validators.add(configurationPropertiesValidator);
5✔
151
    }
152
    if (jsr303Present && target.getAnnotation(Validated.class) != null) {
7!
153
      validators.add(getJsr303Validator(target.getType().resolve()));
8✔
154
    }
155
    Validator selfValidator = getSelfValidator(target);
4✔
156
    if (selfValidator != null) {
2✔
157
      validators.add(selfValidator);
4✔
158
    }
159
    return validators;
2✔
160
  }
161

162
  @Nullable
163
  private Validator getSelfValidator(Bindable<?> target) {
164
    if (target.getValue() != null) {
3✔
165
      Object value = target.getValue().get();
4✔
166
      return (value instanceof Validator validator) ? validator : null;
10✔
167
    }
168
    Class<?> type = target.getType().resolve();
4✔
169
    if (type != null && Validator.class.isAssignableFrom(type)) {
6!
170
      return new SelfValidatingConstructorBoundBindableValidator(type);
5✔
171
    }
172
    return null;
2✔
173
  }
174

175
  private Validator getJsr303Validator(Class<?> type) {
176
    return new ConfigurationPropertiesJsr303Validator(this.applicationContext, type);
7✔
177
  }
178

179
  private List<ConfigurationPropertiesBindHandlerAdvisor> getBindHandlerAdvisors() {
180
    List<ConfigurationPropertiesBindHandlerAdvisor> bindHandlerAdvisors = this.bindHandlerAdvisors;
3✔
181
    if (bindHandlerAdvisors == null) {
2✔
182
      bindHandlerAdvisors = this.applicationContext
3✔
183
              .getBeanProvider(ConfigurationPropertiesBindHandlerAdvisor.class)
1✔
184
              .orderedStream()
1✔
185
              .toList();
2✔
186
      this.bindHandlerAdvisors = bindHandlerAdvisors;
3✔
187
    }
188
    return bindHandlerAdvisors;
2✔
189
  }
190

191
  private Binder getBinder() {
192
    Binder binder = this.binder;
3✔
193
    if (binder == null) {
2✔
194
      binder = new Binder(getConfigurationPropertySources(), getPropertySourcesPlaceholdersResolver(),
7✔
195
              getConversionServices(), getPropertyEditorInitializer(), null,
7✔
196
              null);
197
      this.binder = binder;
3✔
198
    }
199
    return binder;
2✔
200
  }
201

202
  private Iterable<ConfigurationPropertySource> getConfigurationPropertySources() {
203
    return ConfigurationPropertySources.from(this.propertySources);
4✔
204
  }
205

206
  private PropertySourcesPlaceholdersResolver getPropertySourcesPlaceholdersResolver() {
207
    return new PropertySourcesPlaceholdersResolver(this.propertySources);
6✔
208
  }
209

210
  @Nullable
211
  private List<ConversionService> getConversionServices() {
212
    return new ConversionServiceDeducer(applicationContext).getConversionServices();
7✔
213
  }
214

215
  @Nullable
216
  private Consumer<PropertyEditorRegistry> getPropertyEditorInitializer() {
217
    if (applicationContext instanceof ConfigurableApplicationContext configurableContext) {
9!
218
      return configurableContext.getBeanFactory()::copyRegisteredEditorsTo;
7✔
219
    }
220
    return null;
×
221
  }
222

223
  static void register(BeanDefinitionRegistry registry) {
224
    if (!registry.containsBeanDefinition(BEAN_NAME)) {
4✔
225
      BeanDefinition definition = BeanDefinitionBuilder
1✔
226
              .rootBeanDefinition(ConfigurationPropertiesBinderFactory.class)
1✔
227
              .getBeanDefinition();
2✔
228
      definition.setEnableDependencyInjection(false);
3✔
229
      definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
3✔
230
      registry.registerBeanDefinition(ConfigurationPropertiesBinder.BEAN_NAME, definition);
4✔
231
    }
232
  }
1✔
233

234
  static ConfigurationPropertiesBinder get(BeanFactory beanFactory) {
235
    return beanFactory.getBean(BEAN_NAME, ConfigurationPropertiesBinder.class);
6✔
236
  }
237

238
  /**
239
   * {@link BindHandler} to deal with
240
   * {@link ConfigurationProperties @ConfigurationProperties} concerns.
241
   */
242
  private static class ConfigurationPropertiesBindHandler extends AbstractBindHandler {
243

244
    ConfigurationPropertiesBindHandler(BindHandler handler) {
245
      super(handler);
3✔
246
    }
1✔
247

248
    @Override
249
    public <T> Bindable<T> onStart(ConfigurationPropertyName name, Bindable<T> target, BindContext context) {
250
      return isConfigurationProperties(target.getType().resolve())
7✔
251
              ? target.withBindRestrictions(Bindable.BindRestriction.NO_DIRECT_PROPERTY) : target;
10✔
252
    }
253

254
    private boolean isConfigurationProperties(@Nullable Class<?> target) {
255
      return target != null && MergedAnnotations.from(target).isPresent(ConfigurationProperties.class);
11!
256
    }
257

258
  }
259

260
  /**
261
   * {@link FactoryBean} to create the {@link ConfigurationPropertiesBinder}.
262
   */
263
  static class ConfigurationPropertiesBinderFactory
3✔
264
          implements FactoryBean<ConfigurationPropertiesBinder>, ApplicationContextAware {
265

266
    @Nullable
267
    private ConfigurationPropertiesBinder binder;
268

269
    @Override
270
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
271
      if (binder == null) {
3!
272
        binder = new ConfigurationPropertiesBinder(applicationContext);
6✔
273
      }
274
    }
1✔
275

276
    @Override
277
    public Class<?> getObjectType() {
278
      return ConfigurationPropertiesBinder.class;
2✔
279
    }
280

281
    @Override
282
    public ConfigurationPropertiesBinder getObject() throws Exception {
283
      Assert.state(this.binder != null, "Binder was not created due to missing setApplicationContext call");
7!
284
      return this.binder;
3✔
285
    }
286

287
  }
288

289
  /**
290
   * A {@code Validator} for a constructor-bound {@code Bindable} where the type being
291
   * bound is itself a {@code Validator} implementation.
292
   */
293
  static class SelfValidatingConstructorBoundBindableValidator implements Validator {
294

295
    private final Class<?> type;
296

297
    SelfValidatingConstructorBoundBindableValidator(Class<?> type) {
2✔
298
      this.type = type;
3✔
299
    }
1✔
300

301
    @Override
302
    public boolean supports(Class<?> candidate) {
303
      return candidate.isAssignableFrom(this.type);
5✔
304
    }
305

306
    @Override
307
    public void validate(Object target, Errors errors) {
308
      ((Validator) target).validate(target, errors);
5✔
309
    }
1✔
310

311
  }
312

313
}
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