001/*
002 * Copyright 2002-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 *      https://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.beans.factory.config;
018
019import java.util.Collections;
020import java.util.Enumeration;
021import java.util.Properties;
022import java.util.Set;
023import java.util.concurrent.ConcurrentHashMap;
024
025import org.springframework.beans.BeansException;
026import org.springframework.beans.PropertyValue;
027import org.springframework.beans.factory.BeanInitializationException;
028
029/**
030 * Property resource configurer that overrides bean property values in an application
031 * context definition. It <i>pushes</i> values from a properties file into bean definitions.
032 *
033 * <p>Configuration lines are expected to be of the following form:
034 *
035 * <pre class="code">beanName.property=value</pre>
036 *
037 * Example properties file:
038 *
039 * <pre class="code">dataSource.driverClassName=com.mysql.jdbc.Driver
040 * dataSource.url=jdbc:mysql:mydb</pre>
041 *
042 * In contrast to PropertyPlaceholderConfigurer, the original definition can have default
043 * values or no values at all for such bean properties. If an overriding properties file does
044 * not have an entry for a certain bean property, the default context definition is used.
045 *
046 * <p>Note that the context definition <i>is not</i> aware of being overridden;
047 * so this is not immediately obvious when looking at the XML definition file.
048 * Furthermore, note that specified override values are always <i>literal</i> values;
049 * they are not translated into bean references. This also applies when the original
050 * value in the XML bean definition specifies a bean reference.
051 *
052 * <p>In case of multiple PropertyOverrideConfigurers that define different values for
053 * the same bean property, the <i>last</i> one will win (due to the overriding mechanism).
054 *
055 * <p>Property values can be converted after reading them in, through overriding
056 * the {@code convertPropertyValue} method. For example, encrypted values
057 * can be detected and decrypted accordingly before processing them.
058 *
059 * @author Juergen Hoeller
060 * @author Rod Johnson
061 * @since 12.03.2003
062 * @see #convertPropertyValue
063 * @see PropertyPlaceholderConfigurer
064 */
065public class PropertyOverrideConfigurer extends PropertyResourceConfigurer {
066
067        /**
068         * The default bean name separator.
069         */
070        public static final String DEFAULT_BEAN_NAME_SEPARATOR = ".";
071
072
073        private String beanNameSeparator = DEFAULT_BEAN_NAME_SEPARATOR;
074
075        private boolean ignoreInvalidKeys = false;
076
077        /**
078         * Contains names of beans that have overrides.
079         */
080        private final Set<String> beanNames = Collections.newSetFromMap(new ConcurrentHashMap<String, Boolean>(16));
081
082
083        /**
084         * Set the separator to expect between bean name and property path.
085         * Default is a dot (".").
086         */
087        public void setBeanNameSeparator(String beanNameSeparator) {
088                this.beanNameSeparator = beanNameSeparator;
089        }
090
091        /**
092         * Set whether to ignore invalid keys. Default is "false".
093         * <p>If you ignore invalid keys, keys that do not follow the 'beanName.property' format
094         * (or refer to invalid bean names or properties) will just be logged at debug level.
095         * This allows one to have arbitrary other keys in a properties file.
096         */
097        public void setIgnoreInvalidKeys(boolean ignoreInvalidKeys) {
098                this.ignoreInvalidKeys = ignoreInvalidKeys;
099        }
100
101
102        @Override
103        protected void processProperties(ConfigurableListableBeanFactory beanFactory, Properties props)
104                        throws BeansException {
105
106                for (Enumeration<?> names = props.propertyNames(); names.hasMoreElements();) {
107                        String key = (String) names.nextElement();
108                        try {
109                                processKey(beanFactory, key, props.getProperty(key));
110                        }
111                        catch (BeansException ex) {
112                                String msg = "Could not process key '" + key + "' in PropertyOverrideConfigurer";
113                                if (!this.ignoreInvalidKeys) {
114                                        throw new BeanInitializationException(msg, ex);
115                                }
116                                if (logger.isDebugEnabled()) {
117                                        logger.debug(msg, ex);
118                                }
119                        }
120                }
121        }
122
123        /**
124         * Process the given key as 'beanName.property' entry.
125         */
126        protected void processKey(ConfigurableListableBeanFactory factory, String key, String value)
127                        throws BeansException {
128
129                int separatorIndex = key.indexOf(this.beanNameSeparator);
130                if (separatorIndex == -1) {
131                        throw new BeanInitializationException("Invalid key '" + key +
132                                        "': expected 'beanName" + this.beanNameSeparator + "property'");
133                }
134                String beanName = key.substring(0, separatorIndex);
135                String beanProperty = key.substring(separatorIndex + 1);
136                this.beanNames.add(beanName);
137                applyPropertyValue(factory, beanName, beanProperty, value);
138                if (logger.isDebugEnabled()) {
139                        logger.debug("Property '" + key + "' set to value [" + value + "]");
140                }
141        }
142
143        /**
144         * Apply the given property value to the corresponding bean.
145         */
146        protected void applyPropertyValue(
147                        ConfigurableListableBeanFactory factory, String beanName, String property, String value) {
148
149                BeanDefinition bd = factory.getBeanDefinition(beanName);
150                BeanDefinition bdToUse = bd;
151                while (bd != null) {
152                        bdToUse = bd;
153                        bd = bd.getOriginatingBeanDefinition();
154                }
155                PropertyValue pv = new PropertyValue(property, value);
156                pv.setOptional(this.ignoreInvalidKeys);
157                bdToUse.getPropertyValues().addPropertyValue(pv);
158        }
159
160
161        /**
162         * Were there overrides for this bean?
163         * Only valid after processing has occurred at least once.
164         * @param beanName name of the bean to query status for
165         * @return whether there were property overrides for the named bean
166         */
167        public boolean hasPropertyOverridesFor(String beanName) {
168                return this.beanNames.contains(beanName);
169        }
170
171}