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}