View Javadoc

1   package org.kuali.spring.util;
2   
3   import java.io.IOException;
4   import java.io.InputStream;
5   import java.io.InputStreamReader;
6   import java.util.ArrayList;
7   import java.util.Collections;
8   import java.util.List;
9   import java.util.Map;
10  import java.util.Properties;
11  
12  import org.slf4j.Logger;
13  import org.slf4j.LoggerFactory;
14  import org.springframework.core.io.Resource;
15  import org.springframework.core.io.support.PropertiesLoaderSupport;
16  import org.springframework.util.CollectionUtils;
17  import org.springframework.util.DefaultPropertiesPersister;
18  import org.springframework.util.ObjectUtils;
19  import org.springframework.util.PropertiesPersister;
20  
21  public class PropertiesLoader {
22  	private static final Logger logger = LoggerFactory.getLogger(PropertiesLoader.class);
23  
24  	public static final String DEFAULT_ENVIRONMENT_PROPERTY_PREFIX = "env.";
25  	public static final boolean DEFAULT_IS_USE_ENVIRONMENT_PROPERTY_PREFIX = false;
26  	public static final boolean DEFAULT_IS_LOCAL_OVERRIDE = false;
27  	public static final boolean DEFAULT_IS_IGNORE_RESOURCE_NOT_FOUND = false;
28  	public static final boolean DEFAULT_IS_SEARCH_SYSTEM_ENVIRONMENT = true;
29  	public static final SystemPropertiesMode DEFAULT_SYSTEM_PROPERTIES_MODE = SystemPropertiesMode.SYSTEM_PROPERTIES_MODE_OVERRIDE;
30  
31  	// Properties with defaults
32  	String environmentPropertyPrefix = DEFAULT_ENVIRONMENT_PROPERTY_PREFIX;
33  	boolean useEnvironmentPropertyPrefix = DEFAULT_IS_USE_ENVIRONMENT_PROPERTY_PREFIX;
34  	SystemPropertiesMode systemPropertiesMode = DEFAULT_SYSTEM_PROPERTIES_MODE;
35  	boolean localOverride = DEFAULT_IS_LOCAL_OVERRIDE;
36  	boolean ignoreResourceNotFound = DEFAULT_IS_IGNORE_RESOURCE_NOT_FOUND;
37  	boolean searchSystemEnvironment = DEFAULT_IS_SEARCH_SYSTEM_ENVIRONMENT;
38  
39  	// Properties without defaults
40  	String fileEncoding;
41  	Properties[] localProperties;
42  	Resource[] locations;
43  
44  	// Default component beans
45  	PropertiesPersister propertiesPersister = new DefaultPropertiesPersister();
46  	PropertyLogger plogger = new PropertyLogger();
47  
48  	// Instance variables filled in during loading
49  	Properties systemProperties;
50  	Properties environmentProperties;
51  	Properties resourceProperties;
52  	Properties mergedLocalProperties;
53  	Properties properties;
54  
55  	/**
56  	 * Get a handle to our environment properties. Prefix is optional
57  	 */
58  	protected Properties getEnvironmentProperties(String prefix) {
59  		Map<String, String> environmentMap = SystemUtils.getEnvironmentIgnoreExceptions();
60  		Properties envProps = new Properties();
61  		for (Map.Entry<String, String> entry : environmentMap.entrySet()) {
62  			String key = (prefix == null) ? entry.getKey() : prefix + entry.getKey();
63  			envProps.setProperty(key, entry.getValue());
64  		}
65  		return envProps;
66  	}
67  
68  	/**
69  	 * Given a resource and an InputStream, load and return a Properties object. Supports regular as well as XML style
70  	 * properties files
71  	 * 
72  	 * @param location
73  	 * @param is
74  	 * @return
75  	 * @throws IOException
76  	 */
77  	protected Properties getProperties(Resource location, InputStream is) throws IOException {
78  		Properties properties = new Properties();
79  		// Use XML style loading if it is an XML file
80  		if (isXMLFile(location)) {
81  			getPropertiesPersister().loadFromXml(properties, is);
82  			return properties;
83  		}
84  
85  		if (getFileEncoding() != null) {
86  			// Use a Reader if they've specified a fileEncoding
87  			getPropertiesPersister().load(properties, new InputStreamReader(is, getFileEncoding()));
88  		} else {
89  			// Otherwise use an InputStream
90  			getPropertiesPersister().load(properties, is);
91  		}
92  		return properties;
93  	}
94  
95  	/**
96  	 * Load properties from a Resource object. Returns an empty Properties object if ignoreResourceNotFound is true and
97  	 * the resource could not be located
98  	 * 
99  	 * @param location
100 	 * @return
101 	 * @throws IOException
102 	 */
103 	protected Properties loadProperties(Resource location) throws IOException {
104 		// Handle locations that don't exist
105 		if (!location.exists()) {
106 			return handleResourceNotFound(location);
107 		}
108 
109 		// Proceed with loading
110 		logger.info("Loading properties from {}", location);
111 		InputStream is = null;
112 		try {
113 			is = location.getInputStream();
114 			return getProperties(location, is);
115 		} catch (IOException e) {
116 			throw e;
117 		} finally {
118 			nullSafeClose(is);
119 		}
120 	}
121 
122 	/**
123 	 * True if this location represents an XML file, false otherwise
124 	 * 
125 	 * @param location
126 	 * @return
127 	 */
128 	protected boolean isXMLFile(Resource location) {
129 		String filename = null;
130 		try {
131 			filename = location.getFilename();
132 		} catch (IllegalStateException ex) {
133 			// resource is not file-based. See SPR-7552.
134 			return false;
135 		}
136 		// May not have thrown an exception, but might still be null
137 		if (filename == null) {
138 			return false;
139 		}
140 		// It's an XML file
141 		if (filename.endsWith(PropertiesLoaderSupport.XML_FILE_EXTENSION)) {
142 			return true;
143 		} else {
144 			return false;
145 		}
146 	}
147 
148 	/**
149 	 * Throw an exception unless ignoreResourceNotFound is true
150 	 * 
151 	 * @param location
152 	 * @return
153 	 */
154 	protected Properties handleResourceNotFound(Resource location) {
155 		if (isIgnoreResourceNotFound()) {
156 			logger.info("Ignoring properties from {}.  Resource not found", location);
157 			return new Properties();
158 		} else {
159 			throw new PropertiesLoadException("Resource not found: " + location);
160 		}
161 	}
162 
163 	/**
164 	 * Close the InputStream
165 	 * 
166 	 * @param is
167 	 * @throws IOException
168 	 */
169 	protected void nullSafeClose(InputStream is) throws IOException {
170 		if (is == null) {
171 			return;
172 		}
173 		is.close();
174 	}
175 
176 	/**
177 	 * Merge the Properties[] into a single Properties object
178 	 */
179 	protected Properties mergeLocalProperties() {
180 		if (getLocalProperties() == null || getLocalProperties().length == 0) {
181 			// Nothing to do, return an empty Properties object
182 			return new Properties();
183 		}
184 		// Merge the Properties[] into a single Properties object
185 		Properties result = new Properties();
186 		for (Properties localProp : getLocalProperties()) {
187 			CollectionUtils.mergePropertiesIntoMap(localProp, result);
188 		}
189 		return result;
190 	}
191 
192 	/**
193 	 * Get properties from the environment
194 	 */
195 	protected Properties loadEnvironmentProperties() {
196 		if (isUseEnvironmentPropertyPrefix()) {
197 			return getEnvironmentProperties(getEnvironmentPropertyPrefix());
198 		} else {
199 			return getEnvironmentProperties(null);
200 		}
201 	}
202 
203 	/**
204 	 * Get system properties
205 	 */
206 	protected Properties loadSystemProperties() {
207 		return SystemUtils.getSystemPropertiesIgnoreExceptions();
208 	}
209 
210 	/**
211 	 * Merge local, resource, system, and environment properties into a single Properties object. User supplied settings
212 	 * control which property "wins" if a property is defined in multiple areas
213 	 */
214 	public Properties mergeProperties(Properties local, Properties resource, Properties sys, Properties env) {
215 		// Storage for our merged properties
216 		Properties result = new Properties();
217 
218 		// Merge in local properties (nothing to actually merge here, but this also logs them when DEBUG is on)
219 		PropertiesMergeContext context = new PropertiesMergeContext(result, local, PropertySource.LOCAL);
220 		mergeProperties(context);
221 
222 		// Merge in resource properties. localOverride controls what property "wins" if the same
223 		// property is declared both locally and in a resource
224 		boolean resourcePropertyWins = !isLocalOverride();
225 		context = new PropertiesMergeContext(result, resource, resourcePropertyWins, PropertySource.RESOURCE);
226 		mergeProperties(context);
227 
228 		// Merge in system properties according to the SystemPropertiesMode being used
229 		if (!getSystemPropertiesMode().equals(SystemPropertiesMode.SYSTEM_PROPERTIES_MODE_NEVER)) {
230 			boolean override = getSystemPropertiesMode().equals(SystemPropertiesMode.SYSTEM_PROPERTIES_MODE_OVERRIDE);
231 			context = new PropertiesMergeContext(result, sys, override, PropertySource.SYSTEM);
232 			mergeProperties(context);
233 		}
234 
235 		// Merge in environment properties. Environment properties never override properties from another source
236 		if (isSearchSystemEnvironment()) {
237 			context = new PropertiesMergeContext(result, env, false, PropertySource.ENVIRONMENT);
238 			mergeProperties(context);
239 		}
240 
241 		// Return the merged properties
242 		return result;
243 	}
244 
245 	/**
246 	 * Fill in a Properties object
247 	 */
248 	public Properties loadProperties() {
249 		try {
250 			// Populate properties from the default set of locations
251 			Properties local = mergeLocalProperties();
252 			Properties resource = loadResourceProperties();
253 			Properties sys = loadSystemProperties();
254 			Properties env = loadEnvironmentProperties();
255 
256 			// Store the properties locally
257 			setMergedLocalProperties(local);
258 			setResourceProperties(resource);
259 			setSystemProperties(sys);
260 			setEnvironmentProperties(env);
261 
262 			// Merge them into a single properties object
263 			return mergeProperties(local, resource, sys, env);
264 		} catch (IOException e) {
265 			throw new PropertiesLoadException("Unexpected error loading properties", e);
266 		}
267 	}
268 
269 	/**
270 	 * Merge a property under 'key' from newProps into currentProps using the settings from PropertiesMergeContext
271 	 * 
272 	 * @param context
273 	 * @param key
274 	 */
275 	public PropertyMergeResult mergeProperty(PropertiesMergeContext context, String key) {
276 		Properties newProps = context.getNewProperties();
277 		Properties currentProps = context.getCurrentProperties();
278 		boolean override = context.isOverride();
279 
280 		// Extract the new value
281 		String newValue = newProps.getProperty(key);
282 
283 		// Extract the existing value
284 		String currentValue = currentProps.getProperty(key);
285 
286 		// If the new value is null, there is nothing further to do
287 		if (newValue == null) {
288 			PropertyMergeType type = PropertyMergeType.NULL_NEW_VALUE;
289 			return new PropertyMergeResult(context, key, currentValue, newValue, type);
290 		}
291 
292 		// The newValue is not null, and there is no existing value for this key
293 		if (currentValue == null) {
294 			currentProps.setProperty(key, newValue);
295 			PropertyMergeType type = PropertyMergeType.ADD;
296 			return new PropertyMergeResult(context, key, currentValue, newValue, type);
297 		}
298 
299 		// Neither value is null and the values are the same, nothing further to do
300 		if (ObjectUtils.nullSafeEquals(newValue, currentValue)) {
301 			PropertyMergeType type = PropertyMergeType.IDENTICAL_VALUES;
302 			return new PropertyMergeResult(context, key, currentValue, newValue, type);
303 		}
304 
305 		if (override) {
306 			// There is an existing property, but the new property wins
307 			currentProps.setProperty(key, newValue);
308 			PropertyMergeType type = PropertyMergeType.OVERRIDE;
309 			return new PropertyMergeResult(context, key, currentValue, newValue, type);
310 		} else {
311 			// There is already an existing property, and the existing property wins
312 			PropertyMergeType type = PropertyMergeType.EXISTING_PROPERTY_WINS;
313 			return new PropertyMergeResult(context, key, currentValue, newValue, type);
314 		}
315 	}
316 
317 	public void mergeProperties(PropertiesMergeContext context) {
318 		List<String> keys = new ArrayList<String>(context.getNewProperties().stringPropertyNames());
319 		if (context.isSort()) {
320 			Collections.sort(keys);
321 		}
322 		for (String key : keys) {
323 			logResult(mergeProperty(context, key));
324 		}
325 	}
326 
327 	protected void logResult(PropertyMergeResult result) {
328 		PropertySource source = result.getContext().getSource();
329 		String key = result.getKey();
330 		String newValue = result.getNewValue();
331 		String currentValue = result.getOldValue();
332 
333 		switch (result.getType()) {
334 		case ADD:
335 			logger.debug("Add " + source + " property {}=[{}]", key, plogger.getLogValue(key, newValue));
336 			return;
337 		case OVERRIDE:
338 			logger.info(source + " property override for '" + key + "' [{}]->[{}]",
339 					plogger.getLogValue(key, currentValue), plogger.getLogValue(key, newValue));
340 			return;
341 		case EXISTING_PROPERTY_WINS:
342 			logger.debug("The existing value for '" + key + "' is not being overridden by the " + source
343 					+ " value. Existing:[{}] New:[{}]", plogger.getLogValue(key, currentValue),
344 					plogger.getLogValue(key, newValue));
345 			return;
346 		default:
347 			logger.trace("Merge property result: {} for {}", result.getType(), key);
348 			return;
349 
350 		}
351 
352 	}
353 
354 	public Properties loadResourceProperties() throws IOException {
355 		if (getLocations() == null || getLocations().length == 0) {
356 			logger.info("No resource property locations to load from");
357 			return new Properties();
358 		}
359 		Properties result = new Properties();
360 		for (Resource location : getLocations()) {
361 			Properties newProps = loadProperties(location);
362 			PropertySource source = PropertySource.RESOURCE;
363 			// If a property is declared in more than one resource location, the last resource location "wins"
364 			boolean override = true;
365 			PropertiesMergeContext context = new PropertiesMergeContext(result, newProps, override, source);
366 			mergeProperties(context);
367 		}
368 		return result;
369 	}
370 
371 	public boolean isIgnoreResourceNotFound() {
372 		return ignoreResourceNotFound;
373 	}
374 
375 	public String getFileEncoding() {
376 		return fileEncoding;
377 	}
378 
379 	public Properties[] getLocalProperties() {
380 		return localProperties;
381 	}
382 
383 	public void setLocalProperties(Properties[] localProperties) {
384 		this.localProperties = localProperties;
385 	}
386 
387 	public Resource[] getLocations() {
388 		return locations;
389 	}
390 
391 	public boolean isLocalOverride() {
392 		return localOverride;
393 	}
394 
395 	public PropertiesPersister getPropertiesPersister() {
396 		return propertiesPersister;
397 	}
398 
399 	public String getEnvironmentPropertyPrefix() {
400 		return environmentPropertyPrefix;
401 	}
402 
403 	public void setEnvironmentPropertyPrefix(String environmentPropertyPrefix) {
404 		this.environmentPropertyPrefix = environmentPropertyPrefix;
405 	}
406 
407 	public Logger getLogger() {
408 		return logger;
409 	}
410 
411 	public void setPropertiesPersister(PropertiesPersister propertiesPersister) {
412 		this.propertiesPersister = propertiesPersister;
413 	}
414 
415 	public void setLocations(Resource[] locations) {
416 		this.locations = locations;
417 	}
418 
419 	public void setLocalOverride(boolean localOverride) {
420 		this.localOverride = localOverride;
421 	}
422 
423 	public void setIgnoreResourceNotFound(boolean ignoreResourceNotFound) {
424 		this.ignoreResourceNotFound = ignoreResourceNotFound;
425 	}
426 
427 	public void setFileEncoding(String fileEncoding) {
428 		this.fileEncoding = fileEncoding;
429 	}
430 
431 	public SystemPropertiesMode getSystemPropertiesMode() {
432 		return systemPropertiesMode;
433 	}
434 
435 	public void setSystemPropertiesMode(SystemPropertiesMode systemPropertiesMode) {
436 		this.systemPropertiesMode = systemPropertiesMode;
437 	}
438 
439 	public boolean isSearchSystemEnvironment() {
440 		return searchSystemEnvironment;
441 	}
442 
443 	public void setSearchSystemEnvironment(boolean searchSystemEnvironment) {
444 		this.searchSystemEnvironment = searchSystemEnvironment;
445 	}
446 
447 	public void setResourceProperties(Properties resourceProperties) {
448 		this.resourceProperties = resourceProperties;
449 	}
450 
451 	public void setSystemProperties(Properties systemProperties) {
452 		this.systemProperties = systemProperties;
453 	}
454 
455 	public void setEnvironmentProperties(Properties environmentProperties) {
456 		this.environmentProperties = environmentProperties;
457 	}
458 
459 	public void setMergedLocalProperties(Properties mergedLocalProperties) {
460 		this.mergedLocalProperties = mergedLocalProperties;
461 	}
462 
463 	public boolean isUseEnvironmentPropertyPrefix() {
464 		return useEnvironmentPropertyPrefix;
465 	}
466 
467 	public void setUseEnvironmentPropertyPrefix(boolean useEnvironmentPropertyPrefix) {
468 		this.useEnvironmentPropertyPrefix = useEnvironmentPropertyPrefix;
469 	}
470 
471 	public PropertyLogger getPlogger() {
472 		return plogger;
473 	}
474 
475 	public void setPlogger(PropertyLogger propertiesLogger) {
476 		this.plogger = propertiesLogger;
477 	}
478 
479 	public Properties getProperties() {
480 		return properties;
481 	}
482 
483 	public void setProperties(Properties properties) {
484 		this.properties = properties;
485 	}
486 
487 	public Properties getSystemProperties() {
488 		return systemProperties;
489 	}
490 
491 	public Properties getEnvironmentProperties() {
492 		return environmentProperties;
493 	}
494 
495 	public Properties getResourceProperties() {
496 		return resourceProperties;
497 	}
498 
499 	public Properties getMergedLocalProperties() {
500 		return mergedLocalProperties;
501 	}
502 
503 }