001/* 002 * Copyright 2012-2018 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.context.properties.bind; 018 019import java.util.ArrayDeque; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.Deque; 025import java.util.HashSet; 026import java.util.List; 027import java.util.Map; 028import java.util.Objects; 029import java.util.Set; 030import java.util.function.Consumer; 031import java.util.function.Supplier; 032import java.util.stream.Stream; 033 034import org.springframework.beans.PropertyEditorRegistry; 035import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; 036import org.springframework.boot.context.properties.source.ConfigurationProperty; 037import org.springframework.boot.context.properties.source.ConfigurationPropertyName; 038import org.springframework.boot.context.properties.source.ConfigurationPropertySource; 039import org.springframework.boot.context.properties.source.ConfigurationPropertySources; 040import org.springframework.boot.context.properties.source.ConfigurationPropertyState; 041import org.springframework.boot.convert.ApplicationConversionService; 042import org.springframework.core.convert.ConversionService; 043import org.springframework.core.convert.ConverterNotFoundException; 044import org.springframework.core.env.Environment; 045import org.springframework.format.support.DefaultFormattingConversionService; 046import org.springframework.util.Assert; 047 048/** 049 * A container object which Binds objects from one or more 050 * {@link ConfigurationPropertySource ConfigurationPropertySources}. 051 * 052 * @author Phillip Webb 053 * @author Madhura Bhave 054 * @since 2.0.0 055 */ 056public class Binder { 057 058 private static final Set<Class<?>> NON_BEAN_CLASSES = Collections 059 .unmodifiableSet(new HashSet<>(Arrays.asList(Object.class, Class.class))); 060 061 private static final List<BeanBinder> BEAN_BINDERS; 062 063 static { 064 List<BeanBinder> binders = new ArrayList<>(); 065 binders.add(new JavaBeanBinder()); 066 BEAN_BINDERS = Collections.unmodifiableList(binders); 067 } 068 069 private final Iterable<ConfigurationPropertySource> sources; 070 071 private final PlaceholdersResolver placeholdersResolver; 072 073 private final ConversionService conversionService; 074 075 private final Consumer<PropertyEditorRegistry> propertyEditorInitializer; 076 077 /** 078 * Create a new {@link Binder} instance for the specified sources. A 079 * {@link DefaultFormattingConversionService} will be used for all conversion. 080 * @param sources the sources used for binding 081 */ 082 public Binder(ConfigurationPropertySource... sources) { 083 this(Arrays.asList(sources), null, null, null); 084 } 085 086 /** 087 * Create a new {@link Binder} instance for the specified sources. A 088 * {@link DefaultFormattingConversionService} will be used for all conversion. 089 * @param sources the sources used for binding 090 */ 091 public Binder(Iterable<ConfigurationPropertySource> sources) { 092 this(sources, null, null, null); 093 } 094 095 /** 096 * Create a new {@link Binder} instance for the specified sources. 097 * @param sources the sources used for binding 098 * @param placeholdersResolver strategy to resolve any property placeholders 099 */ 100 public Binder(Iterable<ConfigurationPropertySource> sources, 101 PlaceholdersResolver placeholdersResolver) { 102 this(sources, placeholdersResolver, null, null); 103 } 104 105 /** 106 * Create a new {@link Binder} instance for the specified sources. 107 * @param sources the sources used for binding 108 * @param placeholdersResolver strategy to resolve any property placeholders 109 * @param conversionService the conversion service to convert values (or {@code null} 110 * to use {@link ApplicationConversionService}) 111 */ 112 public Binder(Iterable<ConfigurationPropertySource> sources, 113 PlaceholdersResolver placeholdersResolver, 114 ConversionService conversionService) { 115 this(sources, placeholdersResolver, conversionService, null); 116 } 117 118 /** 119 * Create a new {@link Binder} instance for the specified sources. 120 * @param sources the sources used for binding 121 * @param placeholdersResolver strategy to resolve any property placeholders 122 * @param conversionService the conversion service to convert values (or {@code null} 123 * to use {@link ApplicationConversionService}) 124 * @param propertyEditorInitializer initializer used to configure the property editors 125 * that can convert values (or {@code null} if no initialization is required). Often 126 * used to call {@link ConfigurableListableBeanFactory#copyRegisteredEditorsTo}. 127 */ 128 public Binder(Iterable<ConfigurationPropertySource> sources, 129 PlaceholdersResolver placeholdersResolver, 130 ConversionService conversionService, 131 Consumer<PropertyEditorRegistry> propertyEditorInitializer) { 132 Assert.notNull(sources, "Sources must not be null"); 133 this.sources = sources; 134 this.placeholdersResolver = (placeholdersResolver != null) ? placeholdersResolver 135 : PlaceholdersResolver.NONE; 136 this.conversionService = (conversionService != null) ? conversionService 137 : ApplicationConversionService.getSharedInstance(); 138 this.propertyEditorInitializer = propertyEditorInitializer; 139 } 140 141 /** 142 * Bind the specified target {@link Class} using this binder's 143 * {@link ConfigurationPropertySource property sources}. 144 * @param name the configuration property name to bind 145 * @param target the target class 146 * @param <T> the bound type 147 * @return the binding result (never {@code null}) 148 * @see #bind(ConfigurationPropertyName, Bindable, BindHandler) 149 */ 150 public <T> BindResult<T> bind(String name, Class<T> target) { 151 return bind(name, Bindable.of(target)); 152 } 153 154 /** 155 * Bind the specified target {@link Bindable} using this binder's 156 * {@link ConfigurationPropertySource property sources}. 157 * @param name the configuration property name to bind 158 * @param target the target bindable 159 * @param <T> the bound type 160 * @return the binding result (never {@code null}) 161 * @see #bind(ConfigurationPropertyName, Bindable, BindHandler) 162 */ 163 public <T> BindResult<T> bind(String name, Bindable<T> target) { 164 return bind(ConfigurationPropertyName.of(name), target, null); 165 } 166 167 /** 168 * Bind the specified target {@link Bindable} using this binder's 169 * {@link ConfigurationPropertySource property sources}. 170 * @param name the configuration property name to bind 171 * @param target the target bindable 172 * @param <T> the bound type 173 * @return the binding result (never {@code null}) 174 * @see #bind(ConfigurationPropertyName, Bindable, BindHandler) 175 */ 176 public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target) { 177 return bind(name, target, null); 178 } 179 180 /** 181 * Bind the specified target {@link Bindable} using this binder's 182 * {@link ConfigurationPropertySource property sources}. 183 * @param name the configuration property name to bind 184 * @param target the target bindable 185 * @param handler the bind handler (may be {@code null}) 186 * @param <T> the bound type 187 * @return the binding result (never {@code null}) 188 */ 189 public <T> BindResult<T> bind(String name, Bindable<T> target, BindHandler handler) { 190 return bind(ConfigurationPropertyName.of(name), target, handler); 191 } 192 193 /** 194 * Bind the specified target {@link Bindable} using this binder's 195 * {@link ConfigurationPropertySource property sources}. 196 * @param name the configuration property name to bind 197 * @param target the target bindable 198 * @param handler the bind handler (may be {@code null}) 199 * @param <T> the bound type 200 * @return the binding result (never {@code null}) 201 */ 202 public <T> BindResult<T> bind(ConfigurationPropertyName name, Bindable<T> target, 203 BindHandler handler) { 204 Assert.notNull(name, "Name must not be null"); 205 Assert.notNull(target, "Target must not be null"); 206 handler = (handler != null) ? handler : BindHandler.DEFAULT; 207 Context context = new Context(); 208 T bound = bind(name, target, handler, context, false); 209 return BindResult.of(bound); 210 } 211 212 protected final <T> T bind(ConfigurationPropertyName name, Bindable<T> target, 213 BindHandler handler, Context context, boolean allowRecursiveBinding) { 214 context.clearConfigurationProperty(); 215 try { 216 target = handler.onStart(name, target, context); 217 if (target == null) { 218 return null; 219 } 220 Object bound = bindObject(name, target, handler, context, 221 allowRecursiveBinding); 222 return handleBindResult(name, target, handler, context, bound); 223 } 224 catch (Exception ex) { 225 return handleBindError(name, target, handler, context, ex); 226 } 227 } 228 229 private <T> T handleBindResult(ConfigurationPropertyName name, Bindable<T> target, 230 BindHandler handler, Context context, Object result) throws Exception { 231 if (result != null) { 232 result = handler.onSuccess(name, target, context, result); 233 result = context.getConverter().convert(result, target); 234 } 235 handler.onFinish(name, target, context, result); 236 return context.getConverter().convert(result, target); 237 } 238 239 private <T> T handleBindError(ConfigurationPropertyName name, Bindable<T> target, 240 BindHandler handler, Context context, Exception error) { 241 try { 242 Object result = handler.onFailure(name, target, context, error); 243 return context.getConverter().convert(result, target); 244 } 245 catch (Exception ex) { 246 if (ex instanceof BindException) { 247 throw (BindException) ex; 248 } 249 throw new BindException(name, target, context.getConfigurationProperty(), ex); 250 } 251 } 252 253 private <T> Object bindObject(ConfigurationPropertyName name, Bindable<T> target, 254 BindHandler handler, Context context, boolean allowRecursiveBinding) { 255 ConfigurationProperty property = findProperty(name, context); 256 if (property == null && containsNoDescendantOf(context.getSources(), name)) { 257 return null; 258 } 259 AggregateBinder<?> aggregateBinder = getAggregateBinder(target, context); 260 if (aggregateBinder != null) { 261 return bindAggregate(name, target, handler, context, aggregateBinder); 262 } 263 if (property != null) { 264 try { 265 return bindProperty(target, context, property); 266 } 267 catch (ConverterNotFoundException ex) { 268 // We might still be able to bind it as a bean 269 Object bean = bindBean(name, target, handler, context, 270 allowRecursiveBinding); 271 if (bean != null) { 272 return bean; 273 } 274 throw ex; 275 } 276 } 277 return bindBean(name, target, handler, context, allowRecursiveBinding); 278 } 279 280 private AggregateBinder<?> getAggregateBinder(Bindable<?> target, Context context) { 281 Class<?> resolvedType = target.getType().resolve(Object.class); 282 if (Map.class.isAssignableFrom(resolvedType)) { 283 return new MapBinder(context); 284 } 285 if (Collection.class.isAssignableFrom(resolvedType)) { 286 return new CollectionBinder(context); 287 } 288 if (target.getType().isArray()) { 289 return new ArrayBinder(context); 290 } 291 return null; 292 } 293 294 private <T> Object bindAggregate(ConfigurationPropertyName name, Bindable<T> target, 295 BindHandler handler, Context context, AggregateBinder<?> aggregateBinder) { 296 AggregateElementBinder elementBinder = (itemName, itemTarget, source) -> { 297 boolean allowRecursiveBinding = aggregateBinder 298 .isAllowRecursiveBinding(source); 299 Supplier<?> supplier = () -> bind(itemName, itemTarget, handler, context, 300 allowRecursiveBinding); 301 return context.withSource(source, supplier); 302 }; 303 return context.withIncreasedDepth( 304 () -> aggregateBinder.bind(name, target, elementBinder)); 305 } 306 307 private ConfigurationProperty findProperty(ConfigurationPropertyName name, 308 Context context) { 309 if (name.isEmpty()) { 310 return null; 311 } 312 for (ConfigurationPropertySource source : context.getSources()) { 313 ConfigurationProperty property = source.getConfigurationProperty(name); 314 if (property != null) { 315 return property; 316 } 317 } 318 return null; 319 } 320 321 private <T> Object bindProperty(Bindable<T> target, Context context, 322 ConfigurationProperty property) { 323 context.setConfigurationProperty(property); 324 Object result = property.getValue(); 325 result = this.placeholdersResolver.resolvePlaceholders(result); 326 result = context.getConverter().convert(result, target); 327 return result; 328 } 329 330 private Object bindBean(ConfigurationPropertyName name, Bindable<?> target, 331 BindHandler handler, Context context, boolean allowRecursiveBinding) { 332 if (containsNoDescendantOf(context.getSources(), name) 333 || isUnbindableBean(name, target, context)) { 334 return null; 335 } 336 BeanPropertyBinder propertyBinder = (propertyName, propertyTarget) -> bind( 337 name.append(propertyName), propertyTarget, handler, context, false); 338 Class<?> type = target.getType().resolve(Object.class); 339 if (!allowRecursiveBinding && context.hasBoundBean(type)) { 340 return null; 341 } 342 return context.withBean(type, () -> { 343 Stream<?> boundBeans = BEAN_BINDERS.stream() 344 .map((b) -> b.bind(name, target, context, propertyBinder)); 345 return boundBeans.filter(Objects::nonNull).findFirst().orElse(null); 346 }); 347 } 348 349 private boolean isUnbindableBean(ConfigurationPropertyName name, Bindable<?> target, 350 Context context) { 351 for (ConfigurationPropertySource source : context.getSources()) { 352 if (source.containsDescendantOf(name) == ConfigurationPropertyState.PRESENT) { 353 // We know there are properties to bind so we can't bypass anything 354 return false; 355 } 356 } 357 Class<?> resolved = target.getType().resolve(Object.class); 358 if (resolved.isPrimitive() || NON_BEAN_CLASSES.contains(resolved)) { 359 return true; 360 } 361 return resolved.getName().startsWith("java."); 362 } 363 364 private boolean containsNoDescendantOf(Iterable<ConfigurationPropertySource> sources, 365 ConfigurationPropertyName name) { 366 for (ConfigurationPropertySource source : sources) { 367 if (source.containsDescendantOf(name) != ConfigurationPropertyState.ABSENT) { 368 return false; 369 } 370 } 371 return true; 372 } 373 374 /** 375 * Create a new {@link Binder} instance from the specified environment. 376 * @param environment the environment source (must have attached 377 * {@link ConfigurationPropertySources}) 378 * @return a {@link Binder} instance 379 */ 380 public static Binder get(Environment environment) { 381 return new Binder(ConfigurationPropertySources.get(environment), 382 new PropertySourcesPlaceholdersResolver(environment)); 383 } 384 385 /** 386 * Context used when binding and the {@link BindContext} implementation. 387 */ 388 final class Context implements BindContext { 389 390 private final BindConverter converter; 391 392 private int depth; 393 394 private final List<ConfigurationPropertySource> source = Arrays 395 .asList((ConfigurationPropertySource) null); 396 397 private int sourcePushCount; 398 399 private final Deque<Class<?>> beans = new ArrayDeque<>(); 400 401 private ConfigurationProperty configurationProperty; 402 403 Context() { 404 this.converter = BindConverter.get(Binder.this.conversionService, 405 Binder.this.propertyEditorInitializer); 406 } 407 408 private void increaseDepth() { 409 this.depth++; 410 } 411 412 private void decreaseDepth() { 413 this.depth--; 414 } 415 416 private <T> T withSource(ConfigurationPropertySource source, 417 Supplier<T> supplier) { 418 if (source == null) { 419 return supplier.get(); 420 } 421 this.source.set(0, source); 422 this.sourcePushCount++; 423 try { 424 return supplier.get(); 425 } 426 finally { 427 this.sourcePushCount--; 428 } 429 } 430 431 private <T> T withBean(Class<?> bean, Supplier<T> supplier) { 432 this.beans.push(bean); 433 try { 434 return withIncreasedDepth(supplier); 435 } 436 finally { 437 this.beans.pop(); 438 } 439 } 440 441 private boolean hasBoundBean(Class<?> bean) { 442 return this.beans.contains(bean); 443 } 444 445 private <T> T withIncreasedDepth(Supplier<T> supplier) { 446 increaseDepth(); 447 try { 448 return supplier.get(); 449 } 450 finally { 451 decreaseDepth(); 452 } 453 } 454 455 private void setConfigurationProperty( 456 ConfigurationProperty configurationProperty) { 457 this.configurationProperty = configurationProperty; 458 } 459 460 private void clearConfigurationProperty() { 461 this.configurationProperty = null; 462 } 463 464 public PlaceholdersResolver getPlaceholdersResolver() { 465 return Binder.this.placeholdersResolver; 466 } 467 468 public BindConverter getConverter() { 469 return this.converter; 470 } 471 472 @Override 473 public Binder getBinder() { 474 return Binder.this; 475 } 476 477 @Override 478 public int getDepth() { 479 return this.depth; 480 } 481 482 @Override 483 public Iterable<ConfigurationPropertySource> getSources() { 484 if (this.sourcePushCount > 0) { 485 return this.source; 486 } 487 return Binder.this.sources; 488 } 489 490 @Override 491 public ConfigurationProperty getConfigurationProperty() { 492 return this.configurationProperty; 493 } 494 495 } 496 497}