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;
018
019import java.lang.annotation.Annotation;
020import java.lang.reflect.Method;
021
022import org.springframework.beans.BeansException;
023import org.springframework.beans.factory.InitializingBean;
024import org.springframework.beans.factory.config.BeanPostProcessor;
025import org.springframework.boot.context.properties.bind.Bindable;
026import org.springframework.context.ApplicationContext;
027import org.springframework.context.ApplicationContextAware;
028import org.springframework.core.Ordered;
029import org.springframework.core.PriorityOrdered;
030import org.springframework.core.ResolvableType;
031import org.springframework.core.annotation.AnnotationUtils;
032import org.springframework.core.env.PropertySources;
033import org.springframework.validation.annotation.Validated;
034
035/**
036 * {@link BeanPostProcessor} to bind {@link PropertySources} to beans annotated with
037 * {@link ConfigurationProperties}.
038 *
039 * @author Dave Syer
040 * @author Phillip Webb
041 * @author Christian Dupuis
042 * @author Stephane Nicoll
043 * @author Madhura Bhave
044 */
045public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor,
046                PriorityOrdered, ApplicationContextAware, InitializingBean {
047
048        /**
049         * The bean name that this post-processor is registered with.
050         */
051        public static final String BEAN_NAME = ConfigurationPropertiesBindingPostProcessor.class
052                        .getName();
053
054        /**
055         * The bean name of the configuration properties validator.
056         */
057        public static final String VALIDATOR_BEAN_NAME = "configurationPropertiesValidator";
058
059        private ConfigurationBeanFactoryMetadata beanFactoryMetadata;
060
061        private ApplicationContext applicationContext;
062
063        private ConfigurationPropertiesBinder configurationPropertiesBinder;
064
065        @Override
066        public void setApplicationContext(ApplicationContext applicationContext)
067                        throws BeansException {
068                this.applicationContext = applicationContext;
069        }
070
071        @Override
072        public void afterPropertiesSet() throws Exception {
073                // We can't use constructor injection of the application context because
074                // it causes eager factory bean initialization
075                this.beanFactoryMetadata = this.applicationContext.getBean(
076                                ConfigurationBeanFactoryMetadata.BEAN_NAME,
077                                ConfigurationBeanFactoryMetadata.class);
078                this.configurationPropertiesBinder = new ConfigurationPropertiesBinder(
079                                this.applicationContext, VALIDATOR_BEAN_NAME);
080        }
081
082        @Override
083        public int getOrder() {
084                return Ordered.HIGHEST_PRECEDENCE + 1;
085        }
086
087        @Override
088        public Object postProcessBeforeInitialization(Object bean, String beanName)
089                        throws BeansException {
090                ConfigurationProperties annotation = getAnnotation(bean, beanName,
091                                ConfigurationProperties.class);
092                if (annotation != null) {
093                        bind(bean, beanName, annotation);
094                }
095                return bean;
096        }
097
098        private void bind(Object bean, String beanName, ConfigurationProperties annotation) {
099                ResolvableType type = getBeanType(bean, beanName);
100                Validated validated = getAnnotation(bean, beanName, Validated.class);
101                Annotation[] annotations = (validated != null)
102                                ? new Annotation[] { annotation, validated }
103                                : new Annotation[] { annotation };
104                Bindable<?> target = Bindable.of(type).withExistingValue(bean)
105                                .withAnnotations(annotations);
106                try {
107                        this.configurationPropertiesBinder.bind(target);
108                }
109                catch (Exception ex) {
110                        throw new ConfigurationPropertiesBindException(beanName, bean, annotation,
111                                        ex);
112                }
113        }
114
115        private ResolvableType getBeanType(Object bean, String beanName) {
116                Method factoryMethod = this.beanFactoryMetadata.findFactoryMethod(beanName);
117                if (factoryMethod != null) {
118                        return ResolvableType.forMethodReturnType(factoryMethod);
119                }
120                return ResolvableType.forClass(bean.getClass());
121        }
122
123        private <A extends Annotation> A getAnnotation(Object bean, String beanName,
124                        Class<A> type) {
125                A annotation = this.beanFactoryMetadata.findFactoryAnnotation(beanName, type);
126                if (annotation == null) {
127                        annotation = AnnotationUtils.findAnnotation(bean.getClass(), type);
128                }
129                return annotation;
130        }
131
132}