View Javadoc

1   /**
2    * Copyright 2010-2013 The Kuali Foundation
3    *
4    * Licensed under the Educational Community 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.opensource.org/licenses/ecl2.php
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  package org.kuali.common.util.spring;
17  
18  import java.io.File;
19  import java.util.ArrayList;
20  import java.util.Arrays;
21  import java.util.Collections;
22  import java.util.Comparator;
23  import java.util.Iterator;
24  import java.util.List;
25  import java.util.Map;
26  import java.util.Properties;
27  
28  import org.codehaus.plexus.util.StringUtils;
29  import org.jasypt.util.text.TextEncryptor;
30  import org.kuali.common.util.Assert;
31  import org.kuali.common.util.CollectionUtils;
32  import org.kuali.common.util.EncUtils;
33  import org.kuali.common.util.EncryptionStrength;
34  import org.kuali.common.util.FormatUtils;
35  import org.kuali.common.util.LocationUtils;
36  import org.kuali.common.util.LoggerLevel;
37  import org.kuali.common.util.LoggerUtils;
38  import org.kuali.common.util.Project;
39  import org.kuali.common.util.ProjectUtils;
40  import org.kuali.common.util.PropertyUtils;
41  import org.kuali.common.util.ReflectionUtils;
42  import org.kuali.common.util.Str;
43  import org.kuali.common.util.execute.Executable;
44  import org.kuali.common.util.execute.SpringExecutable;
45  import org.kuali.common.util.property.Constants;
46  import org.kuali.common.util.property.ProjectProperties;
47  import org.kuali.common.util.property.processor.ResolvePlaceholdersProcessor;
48  import org.kuali.common.util.service.DefaultSpringService;
49  import org.kuali.common.util.service.PropertySourceContext;
50  import org.kuali.common.util.service.SpringContext;
51  import org.kuali.common.util.service.SpringService;
52  import org.slf4j.Logger;
53  import org.slf4j.LoggerFactory;
54  import org.springframework.beans.factory.BeanFactoryUtils;
55  import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
56  import org.springframework.context.ApplicationContext;
57  import org.springframework.context.ConfigurableApplicationContext;
58  import org.springframework.context.annotation.AnnotationConfigApplicationContext;
59  import org.springframework.context.support.ClassPathXmlApplicationContext;
60  import org.springframework.context.support.GenericXmlApplicationContext;
61  import org.springframework.core.env.ConfigurableEnvironment;
62  import org.springframework.core.env.EnumerablePropertySource;
63  import org.springframework.core.env.Environment;
64  import org.springframework.core.env.MutablePropertySources;
65  import org.springframework.core.env.PropertiesPropertySource;
66  import org.springframework.core.env.PropertySource;
67  import org.springframework.util.PropertyPlaceholderHelper;
68  
69  public class SpringUtils {
70  
71  	private static final Logger logger = LoggerFactory.getLogger(SpringUtils.class);
72  
73  	// Configure a helper that fails on unresolved placeholders
74  	private static final PropertyPlaceholderHelper HELPER = new PropertyPlaceholderHelper("${", "}", ":", false);
75  
76  	/**
77  	 * Given a property holding the name of a class, return an instance of that class
78  	 */
79  	public static <T> T getInstance(Environment env, String key, String defaultValue) {
80  		String className = getProperty(env, key, defaultValue);
81  		return ReflectionUtils.newInstance(className);
82  	}
83  
84  	public static List<String> getListFromCSV(Environment env, String key, String defaultValue) {
85  		String csv = SpringUtils.getProperty(env, key, defaultValue);
86  		return CollectionUtils.getTrimmedListFromCSV(csv);
87  	}
88  
89  	public static List<PropertySource<?>> getPropertySources(SpringService service, Class<?> annotatedClass, String mavenPropertiesBeanName, Properties mavenProperties) {
90  		ConfigurableApplicationContext parent = getContextWithPreRegisteredBean(mavenPropertiesBeanName, mavenProperties);
91  		AnnotationConfigApplicationContext child = new AnnotationConfigApplicationContext();
92  		child.setParent(parent);
93  		child.register(annotatedClass);
94  		child.refresh();
95  		return SpringUtils.getPropertySources(child);
96  	}
97  
98  	public static List<PropertySource<?>> getPropertySources(SpringService service, String location, String mavenPropertiesBeanName, Properties mavenProperties) {
99  		String[] locationsArray = { location };
100 		ConfigurableApplicationContext parent = getContextWithPreRegisteredBean(mavenPropertiesBeanName, mavenProperties);
101 		ConfigurableApplicationContext child = new ClassPathXmlApplicationContext(locationsArray, parent);
102 		return SpringUtils.getPropertySources(child);
103 	}
104 
105 	public static Executable getSpringExecutable(Environment env, boolean skip, PropertySource<?> ps, List<Class<?>> annotatedClasses) {
106 		/**
107 		 * This line creates a property source containing 100% of the properties needed by Spring to resolve any/all placeholders. It will be the only property source available to
108 		 * Spring so it needs to include system properties and environment variables
109 		 */
110 		PropertySourceContext psc = new PropertySourceContext(ps, true);
111 
112 		// Setup the Spring context
113 		SpringContext context = new SpringContext();
114 		context.setAnnotatedClasses(annotatedClasses);
115 		context.setPropertySourceContext(psc);
116 
117 		// Load the context
118 		SpringExecutable se = new SpringExecutable();
119 		se.setService(new DefaultSpringService());
120 		se.setContext(context);
121 		se.setSkip(skip);
122 		return se;
123 	}
124 
125 	public static int getInteger(Environment env, String key) {
126 		String value = getProperty(env, key);
127 		return Integer.parseInt(value);
128 	}
129 
130 	public static int getInteger(Environment env, String key, int defaultValue) {
131 		String value = getProperty(env, key, Integer.toString(defaultValue));
132 		return Integer.parseInt(value);
133 	}
134 
135 	public static long getLong(Environment env, String key) {
136 		String value = getProperty(env, key);
137 		return Long.parseLong(value);
138 	}
139 
140 	public static long getLong(Environment env, String key, long defaultValue) {
141 		String value = getProperty(env, key, Long.toString(defaultValue));
142 		return Long.parseLong(value);
143 	}
144 
145 	public static double getDouble(Environment env, String key) {
146 		String value = getProperty(env, key);
147 		return Double.parseDouble(value);
148 	}
149 
150 	public static double getDouble(Environment env, String key, double defaultValue) {
151 		String value = getProperty(env, key, Double.toString(defaultValue));
152 		return Double.parseDouble(value);
153 	}
154 
155 	/**
156 	 * Parse milliseconds from a time string that ends with a unit of measure. If no unit of measure is provided, milliseconds is assumed. Unit of measure is case insensitive.
157 	 * 
158 	 * @see FormatUtils.getMillis(String time)
159 	 */
160 	public static long getMillis(Environment env, String key, String defaultValue) {
161 		String value = getProperty(env, key, defaultValue);
162 		return FormatUtils.getMillis(value);
163 	}
164 
165 	/**
166 	 * Parse bytes from a size string that ends with a unit of measure. If no unit of measure is provided, bytes is assumed. Unit of measure is case insensitive.
167 	 * 
168 	 * @see FormatUtils.getBytes(String size)
169 	 */
170 	public static long getBytes(Environment env, String key, String defaultValue) {
171 		String value = getProperty(env, key, defaultValue);
172 		return FormatUtils.getBytes(value);
173 	}
174 
175 	/**
176 	 * Parse bytes from a size string that ends with a unit of measure. If no unit of measure is provided, bytes is assumed. Unit of measure is case insensitive.
177 	 * 
178 	 * @see FormatUtils.getBytes(String size)
179 	 */
180 	public static long getBytes(Environment env, String key) {
181 		String value = getProperty(env, key);
182 		return FormatUtils.getBytes(value);
183 	}
184 
185 	public static File getFile(Environment env, String key) {
186 		String value = getProperty(env, key);
187 		return new File(value);
188 	}
189 
190 	public static boolean getBoolean(Environment env, String key, boolean defaultValue) {
191 		String value = getProperty(env, key, Boolean.toString(defaultValue));
192 		return Boolean.parseBoolean(value);
193 	}
194 
195 	public static boolean getBoolean(Environment env, String key) {
196 		String value = getProperty(env, key);
197 		return Boolean.parseBoolean(value);
198 	}
199 
200 	public static PropertySource<?> getGlobalPropertySource(String name, List<ProjectProperties> pps) {
201 		// Load them from disk
202 		Properties source = PropertyUtils.load(pps);
203 
204 		// Add in system/environment properties
205 		Properties globalSource = PropertyUtils.getGlobalProperties(source);
206 
207 		// Prepare them so they are ready for use
208 		prepareContextProperties(globalSource);
209 
210 		// Return a PropertySource backed by the properties
211 		return new PropertiesPropertySource(name, globalSource);
212 	}
213 
214 	public static PropertySource<?> getPropertySource(String name, List<ProjectProperties> pps) {
215 		// Load them from disk
216 		Properties source = PropertyUtils.load(pps);
217 
218 		// Prepare them so they are ready for use
219 		prepareContextProperties(source);
220 
221 		// Return a PropertySource backed by the properties
222 		return new PropertiesPropertySource(name, source);
223 	}
224 
225 	public static String getRequiredResolvedProperty(Properties properties, String key) {
226 		return getRequiredResolvedProperty(properties, key, null);
227 	}
228 
229 	public static String getRequiredResolvedProperty(Properties properties, String key, String defaultValue) {
230 		String value = properties.getProperty(key);
231 		value = StringUtils.isBlank(value) ? defaultValue : value;
232 		if (StringUtils.isBlank(value)) {
233 			throw new IllegalArgumentException("[" + key + "] is not set");
234 		} else {
235 			return HELPER.replacePlaceholders(value, properties);
236 		}
237 	}
238 
239 	/**
240 	 * Process the properties passed in so they are ready for use by a Spring context.<br>
241 	 * 
242 	 * 1 - Override with system/environment properties<br>
243 	 * 2 - Decrypt any ENC(...) values<br>
244 	 * 3 - Resolve all property values throwing an exception if any are unresolvable.<br>
245 	 */
246 	public static void prepareContextProperties(Properties properties) {
247 
248 		// Override with system/environment properties
249 		properties.putAll(PropertyUtils.getGlobalProperties());
250 
251 		// Are we decrypting property values?
252 		decrypt(properties);
253 
254 		// Are we resolving placeholders
255 		resolve(properties);
256 	}
257 
258 	public static void resolve(Properties properties) {
259 		// Are we resolving placeholders?
260 		boolean resolve = new Boolean(getRequiredResolvedProperty(properties, "properties.resolve", "true"));
261 		if (resolve) {
262 			ResolvePlaceholdersProcessor rpp = new ResolvePlaceholdersProcessor();
263 			rpp.setHelper(HELPER);
264 			rpp.process(properties);
265 		}
266 	}
267 
268 	public static void decrypt(Properties properties) {
269 		// Are we decrypting property values?
270 		boolean decrypt = new Boolean(getRequiredResolvedProperty(properties, "properties.decrypt", "false"));
271 		if (decrypt) {
272 			// If they asked to decrypt, a password is required
273 			String password = getRequiredResolvedProperty(properties, "properties.enc.password");
274 
275 			// Strength is optional (defaults to BASIC)
276 			String defaultStrength = EncryptionStrength.BASIC.name();
277 			String strength = getRequiredResolvedProperty(properties, "properties.enc.strength", defaultStrength);
278 			EncryptionStrength es = EncryptionStrength.valueOf(strength);
279 			TextEncryptor decryptor = EncUtils.getTextEncryptor(es, password);
280 			PropertyUtils.decrypt(properties, decryptor);
281 		}
282 	}
283 
284 	/**
285 	 * Converts a GAV into Spring's classpath style notation for the default project properties context.
286 	 * 
287 	 * <pre>
288 	 *  org.kuali.common:kuali-jdbc -> classpath:org/kuali/common/kuali-jdbc-properties-context.xml
289 	 * </pre>
290 	 */
291 	public static String getDefaultPropertyContextLocation(String gav) {
292 		Assert.hasText(gav, "gav has no text");
293 		Project p = ProjectUtils.getProject(gav);
294 		return "classpath:" + Str.getPath(p.getGroupId()) + "/" + p.getArtifactId() + "-properties-context.xml";
295 	}
296 
297 	/**
298 	 * Make sure all of the locations actually exist
299 	 */
300 	public static void validateExists(List<String> locations) {
301 		StringBuilder sb = new StringBuilder();
302 		for (String location : locations) {
303 			if (!LocationUtils.exists(location)) {
304 				sb.append("Location [" + location + "] does not exist\n");
305 			}
306 		}
307 		if (sb.length() > 0) {
308 			throw new IllegalArgumentException(sb.toString());
309 		}
310 	}
311 
312 	public static ConfigurableApplicationContext getContextWithPreRegisteredBeans(String id, String displayName, List<String> beanNames, List<Object> beans) {
313 		Assert.isTrue(beanNames.size() == beans.size());
314 		GenericXmlApplicationContext appContext = new GenericXmlApplicationContext();
315 		if (!StringUtils.isBlank(id)) {
316 			appContext.setId(id);
317 		}
318 		if (!StringUtils.isBlank(displayName)) {
319 			appContext.setDisplayName(displayName);
320 		}
321 		appContext.refresh();
322 		ConfigurableListableBeanFactory factory = appContext.getBeanFactory();
323 		for (int i = 0; i < beanNames.size(); i++) {
324 			String beanName = beanNames.get(i);
325 			Object bean = beans.get(i);
326 			logger.debug("Registering bean - [{}] -> [{}]", beanName, bean.getClass().getName());
327 			factory.registerSingleton(beanName, bean);
328 		}
329 		return appContext;
330 	}
331 
332 	public static ConfigurableApplicationContext getContextWithPreRegisteredBeans(List<String> beanNames, List<Object> beans) {
333 		return getContextWithPreRegisteredBeans(null, null, beanNames, beans);
334 	}
335 
336 	/**
337 	 * Null safe refresh for a context
338 	 */
339 	public static void refreshQuietly(ConfigurableApplicationContext context) {
340 		if (context != null) {
341 			context.refresh();
342 		}
343 	}
344 
345 	/**
346 	 * Null safe close for a context
347 	 */
348 	public static void closeQuietly(ConfigurableApplicationContext context) {
349 		if (context != null) {
350 			context.close();
351 		}
352 	}
353 
354 	public static ConfigurableApplicationContext getContextWithPreRegisteredBean(String beanName, Object bean) {
355 		return getContextWithPreRegisteredBeans(Arrays.asList(beanName), Arrays.asList(bean));
356 	}
357 
358 	public static List<PropertySource<?>> getPropertySources(Class<?> annotatedClass) {
359 		ConfigurableApplicationContext context = new AnnotationConfigApplicationContext(annotatedClass);
360 		return extractPropertySourcesAndClose(context);
361 	}
362 
363 	public static List<PropertySource<?>> extractPropertySourcesAndClose(ConfigurableApplicationContext context) {
364 		// Extract PropertySources (if any)
365 		List<PropertySource<?>> sources = getPropertySources(context);
366 
367 		// Close the context
368 		closeQuietly(context);
369 
370 		// Return the list
371 		return sources;
372 	}
373 
374 	/**
375 	 * Scan the XML Spring context for any beans that implement <code>PropertySource</code>
376 	 */
377 	public static List<PropertySource<?>> getPropertySources(String location) {
378 		ConfigurableApplicationContext context = new GenericXmlApplicationContext(location);
379 		return extractPropertySourcesAndClose(context);
380 	}
381 
382 	/**
383 	 * This method returns a list of any PropertySource objects registered in the indicated context. They are sorted by property source name.
384 	 */
385 	public static List<PropertySource<?>> getPropertySources(ConfigurableApplicationContext context) {
386 		// Sort them by name
387 		return getPropertySources(context, new PropertySourceNameComparator());
388 	}
389 
390 	public static <T> Map<String, T> getAllBeans(List<String> locations, Class<T> type) {
391 		String[] locationsArray = locations.toArray(new String[locations.size()]);
392 		ConfigurableApplicationContext ctx = new GenericXmlApplicationContext(locationsArray);
393 		Map<String, T> map = BeanFactoryUtils.beansOfTypeIncludingAncestors(ctx, type);
394 		ctx.close();
395 		return map;
396 	}
397 
398 	public static <T> Map<String, T> getAllBeans(String location, Class<T> type) {
399 		ConfigurableApplicationContext ctx = new GenericXmlApplicationContext(location);
400 		Map<String, T> map = BeanFactoryUtils.beansOfTypeIncludingAncestors(ctx, type);
401 		ctx.close();
402 		return map;
403 	}
404 
405 	public static <T> Map<String, T> getAllBeans(ConfigurableApplicationContext ctx, Class<T> type) {
406 		return BeanFactoryUtils.beansOfTypeIncludingAncestors(ctx, type);
407 	}
408 
409 	/**
410 	 * This method returns a list of any PropertySource objects registered in the indicated context. The comparator is responsible for putting them in correct order.
411 	 */
412 	public static List<PropertySource<?>> getPropertySources(ConfigurableApplicationContext context, Comparator<PropertySource<?>> comparator) {
413 		// Extract all beans that implement the PropertySource interface
414 		@SuppressWarnings("rawtypes")
415 		Map<String, PropertySource> map = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, PropertySource.class);
416 
417 		// Extract the PropertySource beans into a list
418 		List<PropertySource<?>> list = new ArrayList<PropertySource<?>>();
419 		for (PropertySource<?> source : map.values()) {
420 			list.add(source);
421 		}
422 
423 		// Sort them using the provided comparator
424 		Collections.sort(list, comparator);
425 
426 		// Return the list
427 		return list;
428 	}
429 
430 	/**
431 	 * Null safe method for converting an untyped array of property sources into a list. Never returns null.
432 	 */
433 	public static List<PropertySource<?>> asList(PropertySource<?>... sources) {
434 		List<PropertySource<?>> list = new ArrayList<PropertySource<?>>();
435 		if (sources == null) {
436 			return list;
437 		}
438 		for (PropertySource<?> element : sources) {
439 			if (element != null) {
440 				list.add(element);
441 			}
442 		}
443 		return list;
444 	}
445 
446 	public static void debug(ApplicationContext ctx) {
447 		logger.debug("------------------------ Spring Context ------------------------------");
448 		logger.debug("Id: [{}]", ctx.getId());
449 		logger.debug("Display Name: [{}]", ctx.getDisplayName());
450 		logger.debug("Application Name: [{}]", ctx.getApplicationName());
451 		logger.debug("----------------------------------------------------------------------");
452 		List<String> names = Arrays.asList(BeanFactoryUtils.beanNamesIncludingAncestors(ctx));
453 		List<String> columns = Arrays.asList("Name", "Type", "Hashcode");
454 		List<Object[]> rows = new ArrayList<Object[]>();
455 		Collections.sort(names);
456 		for (String name : names) {
457 			Object bean = ctx.getBean(name);
458 			String instance = (bean == null) ? Constants.NULL : bean.getClass().getSimpleName();
459 			String hashcode = (bean == null) ? Constants.NULL : Integer.toHexString(bean.hashCode());
460 			Object[] row = { name, instance, hashcode };
461 			rows.add(row);
462 		}
463 		LoggerUtils.logTable(columns, rows, LoggerLevel.DEBUG, logger, true);
464 		logger.debug("----------------------------------------------------------------------");
465 	}
466 
467 	public static void showPropertySources(List<PropertySource<?>> propertySources) {
468 		List<String> columns = Arrays.asList("Name", "Impl", "Source");
469 		List<Object[]> rows = new ArrayList<Object[]>();
470 		for (PropertySource<?> propertySource : propertySources) {
471 			String name = propertySource.getName();
472 			String impl = propertySource.getClass().getName();
473 			String source = propertySource.getSource().getClass().getName();
474 			Object[] row = { name, impl, source };
475 			rows.add(row);
476 		}
477 		LoggerUtils.logTable(columns, rows, LoggerLevel.INFO, logger, true);
478 	}
479 
480 	public static void showPropertySources(ConfigurableEnvironment env) {
481 		showPropertySources(getPropertySources(env));
482 	}
483 
484 	/**
485 	 * Get a fully resolved property value from the environment. If the property is not found or contains unresolvable placeholders an exception is thrown.
486 	 */
487 	public static String getProperty(Environment env, String key) {
488 		String value = env.getRequiredProperty(key);
489 		return env.resolveRequiredPlaceholders(value);
490 	}
491 
492 	/**
493 	 * Return true if the environment value for key is not null.
494 	 */
495 	public static boolean exists(Environment env, String key) {
496 		return env.getProperty(key) != null;
497 	}
498 
499 	/**
500 	 * Always return a fully resolved value. Use <code>defaultValue</code> if a value cannot be located in the environment. Throw an exception if the return value contains
501 	 * unresolvable placeholders.
502 	 */
503 	public static String getProperty(Environment env, String key, String defaultValue) {
504 		if (defaultValue == null) {
505 			// No default value supplied, we must be able to locate this property in the environment
506 			return getProperty(env, key);
507 		} else {
508 			// Look up a value from the environment
509 			String value = env.getProperty(key);
510 			if (value == null) {
511 				// Resolve the default value against the environment
512 				return env.resolveRequiredPlaceholders(defaultValue);
513 			} else {
514 				// Resolve the located value against the environment
515 				return env.resolveRequiredPlaceholders(value);
516 			}
517 		}
518 	}
519 
520 	/**
521 	 * Examine <code>ConfigurableEnvironment</code> for <code>PropertySource</code>'s that extend <code>EnumerablePropertySource</code> and aggregate them into a single
522 	 * <code>Properties</code> object
523 	 */
524 	public static Properties getAllEnumerableProperties(ConfigurableEnvironment env) {
525 
526 		// Extract the list of PropertySources from the environment
527 		List<PropertySource<?>> sources = getPropertySources(env);
528 
529 		// Spring provides PropertySource objects ordered from highest priority to lowest priority
530 		// We reverse the order here so things follow the typical "last one in wins" strategy
531 		Collections.reverse(sources);
532 
533 		// Convert the list of PropertySource's to a list of Properties objects
534 		PropertySourceConversionResult result = convertEnumerablePropertySources(sources);
535 
536 		// Combine them into a single Properties object
537 		return PropertyUtils.combine(result.getPropertiesList());
538 	}
539 
540 	/**
541 	 * Remove any existing property sources and add one property source backed by the properties passed in
542 	 */
543 	public static void reconfigurePropertySources(ConfigurableEnvironment env, String name, Properties properties) {
544 		// Remove all existing property sources
545 		removeAllPropertySources(env);
546 
547 		// MutablePropertySources allow us to manipulate the list of property sources
548 		MutablePropertySources mps = env.getPropertySources();
549 
550 		// Make sure there are no existing property sources
551 		Assert.isTrue(mps.size() == 0);
552 
553 		// Create a property source backed by the properties object passed in
554 		PropertiesPropertySource pps = new PropertiesPropertySource(name, properties);
555 
556 		// Add it to the environment
557 		mps.addFirst(pps);
558 	}
559 
560 	/**
561 	 * Remove any existing property sources
562 	 */
563 	public static void removeAllPropertySources(ConfigurableEnvironment env) {
564 		MutablePropertySources mps = env.getPropertySources();
565 		List<PropertySource<?>> sources = getPropertySources(env);
566 		for (PropertySource<?> source : sources) {
567 			String name = source.getName();
568 			mps.remove(name);
569 		}
570 	}
571 
572 	/**
573 	 * Get all PropertySource objects from the environment as a List.
574 	 */
575 	public static List<PropertySource<?>> getPropertySources(ConfigurableEnvironment env) {
576 		MutablePropertySources mps = env.getPropertySources();
577 		List<PropertySource<?>> sources = new ArrayList<PropertySource<?>>();
578 		Iterator<PropertySource<?>> itr = mps.iterator();
579 		while (itr.hasNext()) {
580 			PropertySource<?> source = itr.next();
581 			sources.add(source);
582 		}
583 		return sources;
584 	}
585 
586 	/**
587 	 * Convert any PropertySources that extend EnumerablePropertySource into Properties object's
588 	 */
589 	public static PropertySourceConversionResult convertEnumerablePropertySources(List<PropertySource<?>> sources) {
590 		PropertySourceConversionResult result = new PropertySourceConversionResult();
591 		List<Properties> list = new ArrayList<Properties>();
592 		List<PropertySource<?>> converted = new ArrayList<PropertySource<?>>();
593 		List<PropertySource<?>> skipped = new ArrayList<PropertySource<?>>();
594 		// Extract property values from the sources and place them in a Properties object
595 		for (PropertySource<?> source : sources) {
596 			logger.debug("Adding [{}]", source.getName());
597 			if (source instanceof EnumerablePropertySource) {
598 				EnumerablePropertySource<?> eps = (EnumerablePropertySource<?>) source;
599 				Properties sourceProperties = convert(eps);
600 				list.add(sourceProperties);
601 				converted.add(source);
602 			} else {
603 				logger.debug("Unable to obtain properties from property source [{}] -> [{}]", source.getName(), source.getClass().getName());
604 				skipped.add(source);
605 			}
606 		}
607 		result.setConverted(converted);
608 		result.setSkipped(skipped);
609 		result.setPropertiesList(list);
610 		return result;
611 	}
612 
613 	/**
614 	 * Convert an EnumerablePropertySource into a Properties object.
615 	 */
616 	public static Properties convert(EnumerablePropertySource<?> source) {
617 		Properties properties = new Properties();
618 		String[] names = source.getPropertyNames();
619 		for (String name : names) {
620 			Object object = source.getProperty(name);
621 			if (object != null) {
622 				String value = object.toString();
623 				properties.setProperty(name, value);
624 			} else {
625 				logger.warn("Property [{}] is null", name);
626 			}
627 		}
628 		return properties;
629 	}
630 
631 	/**
632 	 * Return true if, and only if, <code>property</code> is set in the environment and evaluates to true.
633 	 */
634 	public static boolean isTrue(Environment env, String property) {
635 		String value = env.getProperty(property);
636 		if (StringUtils.isBlank(value)) {
637 			return false;
638 		} else {
639 			return new Boolean(value);
640 		}
641 	}
642 
643 }