View Javadoc

1   /*
2    * EL4J, the Extension Library for the J2EE, adds incremental enhancements to
3    * the spring framework, http://el4j.sf.net
4    * Copyright (C) 2005 by ELCA Informatique SA, Av. de la Harpe 22-24,
5    * 1000 Lausanne, Switzerland, http://www.elca.ch
6    *
7    * EL4J is published under the GNU Lesser General Public License (LGPL)
8    * Version 2.1. See http://www.gnu.org/licenses/
9    *
10   * This program is distributed in the hope that it will be useful,
11   * but WITHOUT ANY WARRANTY; without even the implied warranty of
12   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13   * GNU Lesser General Public License for more details.
14   *
15   * For alternative licensing, please contact info@elca.ch
16   */
17  package ch.elca.el4j.core.config;
18  
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.InputStreamReader;
22  import java.util.ArrayList;
23  import java.util.Collection;
24  import java.util.Enumeration;
25  import java.util.List;
26  import java.util.Properties;
27  
28  import org.slf4j.Logger;
29  import org.slf4j.LoggerFactory;
30  import org.springframework.beans.BeanWrapperImpl;
31  import org.springframework.beans.MutablePropertyValues;
32  import org.springframework.beans.PropertyValue;
33  import org.springframework.beans.factory.config.BeanDefinition;
34  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
35  import org.springframework.beans.factory.config.PropertyOverrideConfigurer;
36  import org.springframework.beans.factory.config.TypedStringValue;
37  import org.springframework.beans.factory.support.ManagedList;
38  import org.springframework.beans.propertyeditors.StringArrayPropertyEditor;
39  import org.springframework.core.io.Resource;
40  import org.springframework.util.DefaultPropertiesPersister;
41  import org.springframework.util.PropertiesPersister;
42  
43  import ch.elca.el4j.services.monitoring.notification.CoreNotificationHelper;
44  
45  /**
46   * Adds an entry to a list property of a Spring bean. A file, e.g.
47   * "listmerge.properties", whose values are of type
48   * "beanName"."propertyName"="value 1", "value 2", ... can be referenced in the
49   * bean definition of this class under the property name "location". The defined
50   * values will be added to the list with name "propertyName" of the bean with
51   * name "beanName".
52   *
53   * <p>
54   * The postProcessBeanFactory method from abstract class
55   * PropertyResourceConfigurer is overridden in order to prevent the properties
56   * from being overridden. Another possibility would be to suggest a refactoring
57   * of this method in abstract class PropertyResourceConfigurer.
58   *
59   * <p>
60   * TODO This class handles only lists. Extensions could also manage Maps. A
61   * possible pattern for defining map entries in a properties file is (tbd):
62   * "bean_name.property_name = key1=value1, key2=value2".
63   *
64   * @svnLink $Revision: 4010 $;$Date: 2009-12-01 10:59:54 +0100 (Di, 01. Dez 2009) $;$Author: jonasha $;$URL: https://el4j.svn.sourceforge.net/svnroot/el4j/branches/el4j_3_1/el4j/framework/modules/core/src/main/java/ch/elca/el4j/core/config/ListPropertyMergeConfigurer.java $
65   *
66   * @author Raphael Boog (RBO)
67   * @author Martin Zeltner (MZE)
68   */
69  public class ListPropertyMergeConfigurer extends PropertyOverrideConfigurer {
70  	/**
71  	 * Private logger.
72  	 */
73  	private static Logger s_logger
74  		= LoggerFactory.getLogger(ListPropertyMergeConfigurer.class);
75  
76  	/**
77  	 * Properties.
78  	 */
79  	private Properties m_properties;
80  
81  	/**
82  	 * Resource locations.
83  	 */
84  	private Resource[] m_locations;
85  
86  	/**
87  	 * File encoding.
88  	 */
89  	private String m_fileEncoding;
90  
91  	/**
92  	 * Persister of properties.
93  	 */
94  	private PropertiesPersister m_propertiesPersister
95  		= new DefaultPropertiesPersister();
96  
97  	/**
98  	 * Ignore if a resource could not be found.
99  	 */
100 	private boolean m_ignoreResourceNotFound = false;
101 	
102 	/**
103 	 * Marks if new items should be pre- or appended to the old values.
104 	 */
105 	private boolean m_insertNewItemsAfter = true;
106 
107 	/**
108 	 * {@inheritDoc}
109 	 */
110 	public final void setProperties(Properties properties) {
111 		m_properties = properties;
112 	}
113 
114 	/**
115 	 * {@inheritDoc}
116 	 */
117 	public final void setLocation(Resource location) {
118 		m_locations = new Resource[] {location};
119 		super.setLocation(location);
120 	}
121 
122 	/**
123 	 * {@inheritDoc}
124 	 */
125 	public final void setLocations(Resource[] locations) {
126 		super.setLocations(locations);
127 		m_locations = locations;
128 	}
129 
130 	/**
131 	 * {@inheritDoc}
132 	 */
133 	public final void setFileEncoding(String encoding) {
134 		super.setFileEncoding(encoding);
135 		m_fileEncoding = encoding;
136 	}
137 
138 	/**
139 	 * {@inheritDoc}
140 	 */
141 	public final void setPropertiesPersister(
142 		PropertiesPersister propertiesPersister) {
143 		super.setPropertiesPersister(propertiesPersister);
144 		m_propertiesPersister = propertiesPersister;
145 	}
146 
147 	/**
148 	 * {@inheritDoc}
149 	 */
150 	public final void setIgnoreResourceNotFound(
151 		boolean ignoreResourceNotFound) {
152 		super.setIgnoreResourceNotFound(ignoreResourceNotFound);
153 		m_ignoreResourceNotFound = ignoreResourceNotFound;
154 	}
155 
156 	/**
157 	 * @return Returns the fileEncoding.
158 	 */
159 	public final String getFileEncoding() {
160 		return m_fileEncoding;
161 	}
162 
163 	/**
164 	 * @return Returns the ignoreResourceNotFound.
165 	 */
166 	public final boolean isIgnoreResourceNotFound() {
167 		return m_ignoreResourceNotFound;
168 	}
169 
170 	/**
171 	 * @return Returns the locations.
172 	 */
173 	public final Resource[] getLocations() {
174 		return m_locations.clone();
175 	}
176 
177 	/**
178 	 * @return Returns the properties.
179 	 */
180 	public final Properties getProperties() {
181 		return m_properties;
182 	}
183 
184 	/**
185 	 * @return Returns the propertiesPersister.
186 	 */
187 	public final PropertiesPersister getPropertiesPersister() {
188 		return m_propertiesPersister;
189 	}
190 
191 	/**
192 	 * Marks if new items should be pre- or appended to the old values.
193 	 *
194 	 * @return Returns the insertNewItemsAfter.
195 	 */
196 	public final boolean isInsertNewItemsAfter() {
197 		return m_insertNewItemsAfter;
198 	}
199 
200 	/**
201 	 * Marks if new items should be pre- or appended to the old values.
202 	 *
203 	 * @param insertNewItemsAfter The insertNewItemsAfter to set.
204 	 */
205 	public final void setInsertNewItemsAfter(boolean insertNewItemsAfter) {
206 		m_insertNewItemsAfter = insertNewItemsAfter;
207 	}
208 
209 	/**
210 	 * Marks if new items should be pre- or appended to the old values.
211 	 * Is the opposite of method <code>isInsertNewItemsAfter</code>.
212 	 *
213 	 * @return Returns the insertNewItemsBefore.
214 	 * @see #isInsertNewItemsAfter()
215 	 */
216 	public final boolean isInsertNewItemsBefore() {
217 		return !isInsertNewItemsAfter();
218 	}
219 
220 	/**
221 	 * Marks if new items should be pre- or appended to the old values.
222 	 * Is the opposite of method <code>setInsertNewItemsAfter</code>.
223 	 *
224 	 * @param insertNewItemsBefore The insertNewItemsBefore to set.
225 	 * @see #setInsertNewItemsAfter(boolean)
226 	 */
227 	public final void setInsertNewItemsBefore(boolean insertNewItemsBefore) {
228 		setInsertNewItemsAfter(!insertNewItemsBefore);
229 	}
230 
231 	/**
232 	 * {@inheritDoc}
233 	 */
234 	@Override
235 	public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
236 		Properties mergedProps = new Properties();
237 
238 		addDefaultProperties(mergedProps);
239 
240 		Resource[] locations = getLocations();
241 		if (locations != null) {
242 			for (int i = 0; i < locations.length; i++) {
243 				Resource location = locations[i];
244 				loadProperties(mergedProps, location);
245 				processProperties(beanFactory, mergedProps);
246 				mergedProps = new Properties();
247 			}
248 		} else {
249 			processProperties(beanFactory, mergedProps);
250 		}
251 	}
252 
253 	/**
254 	 * Adds default properties to the properties container.
255 	 *
256 	 * @param mergedProps Is the properties container to write into.
257 	 */
258 	protected void addDefaultProperties(Properties mergedProps) {
259 		Properties defaultProperties = getProperties();
260 		if (defaultProperties != null) {
261 			// use propertyNames enumeration to also catch default properties
262 			Enumeration en = defaultProperties.propertyNames();
263 			while (en.hasMoreElements()) {
264 				String key = (String) en.nextElement();
265 				mergedProps.setProperty(
266 					key, defaultProperties.getProperty(key));
267 			}
268 		}
269 	}
270 
271 	/**
272 	 * Loads the properties from the given location.
273 	 *
274 	 * @param mergedProps Is the properties container to write into.
275 	 * @param location Is the resource location to read properties from.
276 	 */
277 	protected void loadProperties(Properties mergedProps, Resource location) {
278 		if (logger.isInfoEnabled()) {
279 			logger.info("Loading properties file from " + location);
280 		}
281 		InputStream is = null;
282 		try {
283 			is = location.getInputStream();
284 			if (location.getFilename().endsWith(XML_FILE_EXTENSION)) {
285 				getPropertiesPersister().loadFromXml(mergedProps, is);
286 			} else {
287 				String fileEncoding = getFileEncoding();
288 				if (fileEncoding != null) {
289 					getPropertiesPersister().load(mergedProps,
290 						new InputStreamReader(is, fileEncoding));
291 				} else {
292 					getPropertiesPersister().load(mergedProps, is);
293 				}
294 			}
295 		} catch (IOException ex) {
296 			String msg = "Could not load properties from " + location;
297 			if (isIgnoreResourceNotFound()) {
298 				if (s_logger.isWarnEnabled()) {
299 					s_logger.warn(msg + ": " + ex.getMessage());
300 				}
301 			} else {
302 				CoreNotificationHelper.notifyMisconfiguration(msg, ex);
303 			}
304 		} finally {
305 			if (is != null) {
306 				try {
307 					is.close();
308 				} catch (IOException e) {
309 					if (s_logger.isWarnEnabled()) {
310 						s_logger.warn("Inputstream could not be "
311 							+ "properly closed.", e);
312 					}
313 				}
314 			}
315 		}
316 	}
317 
318 	/**
319 	 * {@inheritDoc}
320 	 */
321 	@Override
322 	protected void applyPropertyValue(ConfigurableListableBeanFactory factory,
323 		String beanName, String property, String value) {
324 		/**
325 		 * Read the existing property value.
326 		 */
327 		BeanWrapperImpl beanWrapper = new BeanWrapperImpl();
328 		beanWrapper.registerCustomEditor(String[].class, new
329 		StringArrayPropertyEditor());
330 		BeanDefinition beanDefinition = factory.getBeanDefinition(beanName);
331 		MutablePropertyValues mutablePropertyValues = beanDefinition
332 			.getPropertyValues();
333 		PropertyValue propertyValue = mutablePropertyValues
334 			.getPropertyValue(property);
335 
336 		/**
337 		 * Fill the old values from property into list.
338 		 */
339 		List oldValueList = new ArrayList();
340 		if (propertyValue != null) {
341 			Object valueObject = propertyValue.getValue();
342 			if (valueObject instanceof Collection) {
343 				oldValueList.addAll((Collection) valueObject);
344 			} else {
345 				
346 				String[] oldValues = (String[]) beanWrapper.convertIfNecessary(
347 					valueObject, String[].class);
348 				
349 				/*String[] oldValues = (String[]) beanWrapper
350 					.doTypeConversionIfNecessary(valueObject, String[].class);*/
351 				for (int i = 0; i < oldValues.length; i++) {
352 					String oldValue = oldValues[i];
353 					oldValueList.add(oldValue != null ? oldValue.trim()
354 						: oldValue);
355 				}
356 			}
357 		}
358 		
359 		/**
360 		 * Fill the new values into list.
361 		 */
362 		List newValueList = new ArrayList();
363 		String[] newValues = (String[]) beanWrapper.convertIfNecessary(value,
364 			String[].class);
365 		
366 
367 		for (int i = 0; i < newValues.length; i++) {
368 			String newValue = newValues[i];
369 			TypedStringValue typedValue = null;
370 			if (newValue != null) {
371 				newValue = newValue.trim();
372 				typedValue = new TypedStringValue(newValue);
373 			}
374 			newValueList.add(typedValue);
375 		}
376 		
377 		/**
378 		 * Mix the old and new values and set the new property value.
379 		 */
380 		ManagedList mixedValueList = new ManagedList();
381 		if (isInsertNewItemsAfter()) {
382 			mixedValueList.addAll(oldValueList);
383 			mixedValueList.addAll(newValueList);
384 		} else {
385 			mixedValueList.addAll(newValueList);
386 			mixedValueList.addAll(oldValueList);
387 		}
388 		mutablePropertyValues.addPropertyValue(property, mixedValueList);
389 	}
390 }