View Javadoc

1   /*
2    * Copyright 2004-2005 Dan Washusen
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *      http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   * See the License for the specific language governing permissions and
14   * limitations under the License.
15   */
16  
17  package org.reactive.beans.factory.config;
18  
19  import org.springframework.beans.factory.config.*;
20  import org.springframework.beans.factory.BeanNameAware;
21  import org.springframework.beans.factory.BeanFactoryAware;
22  import org.springframework.beans.factory.BeanFactory;
23  import org.springframework.beans.factory.BeanDefinitionStoreException;
24  import org.springframework.beans.BeansException;
25  import org.springframework.beans.MutablePropertyValues;
26  import org.springframework.beans.PropertyValue;
27  import org.springframework.core.Ordered;
28  import org.springframework.util.ObjectUtils;
29  
30  import java.util.*;
31  
32  
33  /***
34   * Allows for configuration of individual bean property values from a configuration resource.
35   *
36   * <p>Note: This class is based heavily on the org.springframework.beans.factory.config.PropertyPlaceholderConfigurer by Juergen Hoeller</p>
37   *
38   * @author Dan Washusen
39   * @version $Id: ConfigurationPlaceholderProcessor.java,v 1.1 2004/12/26 23:42:29 dan_washusen Exp $
40   * @since 16.12.2004
41   */
42  public class ConfigurationPlaceholderProcessor
43          implements BeanFactoryPostProcessor, Ordered, BeanNameAware, BeanFactoryAware {
44      /*** The position in the set of BeanFactoryPostProcessor that this will be processed in */
45      private int order = Integer.MAX_VALUE;  // default: same as non-Ordered
46      /*** The beans name as defined in the bean factory */
47      private String beanName;
48      /*** The bean factory itself */
49      private BeanFactory beanFactory;
50      /*** Used to evaluate placeholders with their actual values */
51      protected PlaceholderEvaluator placeholderEvaluator;
52      /*** Used to determine if the value from the bean factory requires evaluation.  By default {@link DefaultPlaceholderMatcher} is used. */
53      protected PlaceholderMatcher placeholderMatcher = new DefaultPlaceholderMatcher();
54  
55      public int getOrder() {
56          return order;
57      }
58  
59      public void setOrder(int order) {
60          this.order = order;
61      }
62  
63      public String getBeanName() {
64          return beanName;
65      }
66  
67      public void setBeanName(String beanName) {
68          this.beanName = beanName;
69      }
70  
71      public BeanFactory getBeanFactory() {
72          return beanFactory;
73      }
74  
75      public void setBeanFactory(BeanFactory beanFactory)
76              throws BeansException {
77          this.beanFactory = beanFactory;
78      }
79  
80      /***
81       * Returns the implementation of {@link PlaceholderEvaluator} used to evaluate placeholder values.
82       * @return The place holder eveluator
83       */
84      public PlaceholderEvaluator getPlaceholderEvaluator() {
85          return placeholderEvaluator;
86      }
87  
88      /***
89       * Sets the implementation of {@link PlaceholderEvaluator} used to evaluate placeholder values.
90       * @param placeholderEvaluator The place holder eveluator
91       */
92      public void setPlaceholderEvaluator(PlaceholderEvaluator placeholderEvaluator) {
93          this.placeholderEvaluator = placeholderEvaluator;
94      }
95  
96      /***
97       * Returns the implementation of {@link PlaceholderMatcher} used to determine if a value requires evalutation.
98       * @return The place holder matcher
99       */
100     public PlaceholderMatcher getPlaceholderMatcher() {
101         return placeholderMatcher;
102     }
103 
104     /***
105      * Sets the implementation of {@link PlaceholderMatcher} used to determine if a value requires evalutation.
106      * @param placeholderEvaluator The place holder matcher
107      */
108     public void setPlaceholderMatcher(PlaceholderMatcher placeholderMatcher) {
109         this.placeholderMatcher = placeholderMatcher;
110     }
111 
112     /***
113      * Processes the bean definitions available in the provided ConfigurableListableBeanFactory.
114      * <p>This method ensures it does not process itself or other bean factories.</p>
115      * @param configurableListableBeanFactory The bean factory to process.
116      * @throws BeansException If an error occurrs while processing a bean definition
117      */
118     public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)
119             throws BeansException {
120         // loop over all the bean definitions processing them as appropriate
121         String[] beanNames = configurableListableBeanFactory.getBeanDefinitionNames();
122 		for (int i = 0; i < beanNames.length; i++) {
123 			// Check that we're not parsing our own bean definition,
124 			// to avoid failing on unresolvable placeholders in properties file locations.
125 			if (!(beanNames[i].equals(getBeanName()) && configurableListableBeanFactory.equals(getBeanFactory()))) {
126 				BeanDefinition beanDefinition = configurableListableBeanFactory.getBeanDefinition(beanNames[i]);
127 
128 				try {
129 					processBeanDefinition(beanDefinition);
130 				}
131 				catch (BeanDefinitionStoreException e) {
132 					throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanNames[i], e.getMessage(), e);
133 				}
134 			}
135 		}
136     }
137 
138     /***
139      * Process a single bean definition, processing the property values, indexed argument and generic argument contrsuctor values if there are any.
140      * @param beanDefinition The bean definition to be processed
141      */
142     protected void processBeanDefinition(BeanDefinition beanDefinition) {
143         // process the property values if there are any
144         MutablePropertyValues mutablePropertyValues = beanDefinition.getPropertyValues();
145         if (mutablePropertyValues != null) {
146             processPropertyValues(beanDefinition.getPropertyValues());
147         }
148 
149         // process the contructor values if there are any
150 		ConstructorArgumentValues constructorArgumentValues = beanDefinition.getConstructorArgumentValues();
151 		if (constructorArgumentValues != null) {
152 			processConstructorArgumentValues(constructorArgumentValues.getIndexedArgumentValues().values());
153 			processConstructorArgumentValues(constructorArgumentValues.getGenericArgumentValues());
154 		}
155     }
156 
157     /***
158      * Process the property values.
159      * @param mutablePropertyValues The mutable property values
160      */
161     protected void processPropertyValues(MutablePropertyValues mutablePropertyValues) {
162 		PropertyValue[] propertyValues = mutablePropertyValues.getPropertyValues();
163 
164         // for each property parse the property value
165 		for (int i = 0; i < propertyValues.length; i++) {
166 			PropertyValue propertyValue = propertyValues[i];
167 
168 			Object parsedValue = processValue(propertyValue.getValue());
169 
170             // if the value isn't the same as the original set the new value
171 			if (!ObjectUtils.nullSafeEquals(parsedValue, propertyValue.getValue())) {
172 				mutablePropertyValues.addPropertyValue(propertyValue.getName(), parsedValue);
173 			}
174 		}
175 	}
176 
177     /***
178      * Process the constructor argument values.
179      * @param genericArgumentValues the constructor argument values
180      */
181     protected void processConstructorArgumentValues(Collection genericArgumentValues) {
182         // for each constructor property parse the value
183         for (Iterator it = genericArgumentValues.iterator(); it.hasNext();) {
184 			ConstructorArgumentValues.ValueHolder valueHolder =	(ConstructorArgumentValues.ValueHolder) it.next();
185 
186 			Object parsedValue = processValue(valueHolder.getValue());
187 
188             // if the value isn't the same as the original set the new value
189 			if (!ObjectUtils.nullSafeEquals(parsedValue, valueHolder.getValue())) {
190 				valueHolder.setValue(parsedValue);
191 			}
192 		}
193     }
194 
195     /***
196      * Parses the value by delegating to a more appropriate parse method.
197      * @param value The value to be parsed
198      * @return The parsed value
199      */
200     protected Object processValue(Object value) {
201         if (value instanceof String) {
202             return processString((String) value);
203         }
204         else if (value instanceof RuntimeBeanReference) {
205             RuntimeBeanReference runtimeBeanReference = (RuntimeBeanReference) value;
206 
207             Object parsedBeanName = processString(runtimeBeanReference.getBeanName());
208 
209             // check that the parsed bean name is an instance of String and panic if it isn't
210             if (!(parsedBeanName instanceof String))
211                 throw new InvalidPlaceholderException("Runtime bean reference of name '" + runtimeBeanReference.getBeanName() + "' has been evaluated and it doesn't appear to be an instance of String.");
212 
213             if (!parsedBeanName.equals(runtimeBeanReference.getBeanName())) {
214                 return new RuntimeBeanReference((String) parsedBeanName);
215             }
216         }
217         else if (value instanceof List) {
218             processList((List) value);
219         }
220         else if (value instanceof Set) {
221             processSet((Set) value);
222         }
223         else if (value instanceof Map) {
224             processMap((Map) value);
225         }
226         else if (value instanceof BeanDefinition) {
227             processBeanDefinition((BeanDefinition) value);
228         }
229         else if (value instanceof BeanDefinitionHolder) {
230             processBeanDefinition(((BeanDefinitionHolder) value).getBeanDefinition());
231         }
232 
233         return value;
234     }
235 
236     /***
237      * This is the root of all parsing.  In the end everything (should) evaluate down to a String.  Here, if the
238      * {@link #getPlaceholderMatcher() placeholder matcher} confirms that value needs evaluating, we look up the
239      * value using the {@link #getPlaceholderEvaluator() evaluator} and return the matching object.
240      * @param value The value to the evaluated
241      * @return The object returned from the evaluator if the value was evaluated, otherwise the original value
242      */
243     protected Object processString(String value) {
244         // init the placeholderMatcher with the new value
245         PlaceholderMatcher placeholderMatcher = getPlaceholderMatcher();
246         placeholderMatcher.init(value);
247 
248         // if no palceholders are present then return the original value
249         if (!placeholderMatcher.isPlaceholderPresent())
250             return value;
251 
252         // if the value contains multiple placeholders then we presume the result is a string
253         // we need to iterate over all the placeholders and concatenate them together with the
254         // surrounding text
255         if (placeholderMatcher.isMultiplePlaceholdersPresent()) {
256             StringBuffer evaluatedValueBuffer = new StringBuffer();
257             while (placeholderMatcher.hasNext()) {
258                 // append the proceeding text
259                 String proceedingText = placeholderMatcher.getProceedingText();
260                 if (proceedingText != null)
261                     evaluatedValueBuffer.append(proceedingText);
262 
263                 // evaluate the current placeholder
264                 Placeholder placeholder = placeholderMatcher.next();
265 
266                 // check to see if the placeholder is a string, if not it's all broken
267                 if (placeholder.getType() != String.class)
268                     throw new InvalidPlaceholderException("A value that contains more than one place holder must contain all strings.");
269 
270                 Object evaluatedValue = getPlaceholderEvaluator().evaluate(placeholder);
271                 evaluatedValueBuffer.append(evaluatedValue);
272             }
273 
274             // append any trailing text that was in the value
275             String trailingText = placeholderMatcher.getTrailingText();
276             if (trailingText != null)
277                 evaluatedValueBuffer.append(trailingText);
278 
279             return evaluatedValueBuffer.toString();
280         }
281         // we have a single placeholder to evalutate so evaluate it
282         else {
283             // move to the first (and only) placeholder
284             placeholderMatcher.hasNext();
285             Placeholder placeholder = placeholderMatcher.next();
286             return getPlaceholderEvaluator().evaluate(placeholder);
287         }
288     }
289 
290     /***
291 	 * Parse the given List, resolving its values if necessary.
292      * @param listValue The list to be evaluated
293 	 */
294 	protected void processList(List listValue) {
295 		for (int i = 0; i < listValue.size(); i++) {
296 			Object element = listValue.get(i);
297 
298 			Object parsedValue = processValue(element);
299 
300             // if the parsed value isn't the same the parsed value then replace the original value
301 			if (!ObjectUtils.nullSafeEquals(parsedValue, element)) {
302 				listValue.set(i, parsedValue);
303 			}
304 		}
305 	}
306 
307     /***
308      * Parse the given Set, resolving its values if necessary.
309      * @param setValue The set to be evaluated
310      */
311 	protected void processSet(Set setValue) {
312 		for (Iterator iterator = new HashSet(setValue).iterator(); iterator.hasNext();) {
313 			Object element = iterator.next();
314 
315 			Object parsedValue = processValue(element);
316 
317             // if the parsed value isn't the same the parsed value then replace the original value
318 			if (!ObjectUtils.nullSafeEquals(parsedValue, element)) {
319 				setValue.remove(element);
320 				setValue.add(parsedValue);
321 			}
322 		}
323 	}
324 
325 	/***
326 	 * Parse the given Map, resolving its values if necessary.
327      * @param mapValue The map to be evaluated
328 	 */
329 	protected void processMap(Map mapValue) {
330 		for (Iterator iterator = mapValue.entrySet().iterator(); iterator.hasNext();) {
331             Map.Entry entry = (Map.Entry) iterator.next();
332 
333 			Object parsedKey = processValue(entry.getKey());
334 
335 			boolean isNewKey = !ObjectUtils.nullSafeEquals(entry.getKey(), parsedKey);
336 
337 			Object parsedValue = processValue(entry.getValue());
338 
339             // if the key changed then remove the old key
340 			if (isNewKey) {
341 				mapValue.remove(entry.getKey());
342 			}
343 
344             // if the key changed or the value is new then replace it
345 			if (isNewKey || !ObjectUtils.nullSafeEquals(parsedValue, entry.getValue())) {
346 				mapValue.put(parsedKey, parsedValue);
347 			}
348 		}
349 	}
350 }