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