1   
2   
3   
4   
5   
6   
7   
8   
9   
10  
11  
12  
13  
14  
15  
16  package org.kuali.common.util;
17  
18  import java.io.File;
19  import java.io.IOException;
20  import java.io.InputStream;
21  import java.io.OutputStream;
22  import java.io.Reader;
23  import java.io.Writer;
24  import java.nio.charset.Charset;
25  import java.util.ArrayList;
26  import java.util.Arrays;
27  import java.util.Collections;
28  import java.util.Enumeration;
29  import java.util.List;
30  import java.util.Map;
31  import java.util.Properties;
32  import java.util.Set;
33  import java.util.TreeSet;
34  
35  import org.apache.commons.io.FileUtils;
36  import org.apache.commons.io.IOUtils;
37  import org.apache.commons.lang3.StringUtils;
38  import org.jasypt.util.text.TextEncryptor;
39  import org.kuali.common.util.property.Constants;
40  import org.kuali.common.util.property.GlobalPropertiesMode;
41  import org.kuali.common.util.property.ProjectProperties;
42  import org.kuali.common.util.property.PropertiesContext;
43  import org.kuali.common.util.property.processor.AddPropertiesProcessor;
44  import org.kuali.common.util.property.processor.PropertyProcessor;
45  import org.slf4j.Logger;
46  import org.slf4j.LoggerFactory;
47  import org.springframework.util.Assert;
48  import org.springframework.util.PropertyPlaceholderHelper;
49  
50  
51  
52  
53  
54  
55  public class PropertyUtils {
56  
57  	private static final Logger logger = LoggerFactory.getLogger(PropertyUtils.class);
58  
59  	private static final String XML_EXTENSION = ".xml";
60  	private static final String ENV_PREFIX = "env";
61  	private static final String DEFAULT_ENCODING = Charset.defaultCharset().name();
62  	private static final String DEFAULT_XML_ENCODING = "UTF-8";
63  
64  	public static void appendToOrSetProperty(Properties properties, String key, String value) {
65  		Assert.hasText(value);
66  		String existingValue = properties.getProperty(key);
67  		if (existingValue == null) {
68  			existingValue = "";
69  		}
70  		String newValue = existingValue + value;
71  		properties.setProperty(key, newValue);
72  	}
73  
74  	public static Properties load(List<ProjectProperties> pps) {
75  
76  		
77  		Properties properties = new Properties();
78  
79  		
80  		for (ProjectProperties pp : pps) {
81  
82  			
83  			PropertiesContext ctx = pp.getPropertiesContext();
84  
85  			
86  			Properties combined = PropertyUtils.combine(properties, ctx.getProperties());
87  
88  			
89  			ctx.setProperties(combined);
90  
91  			
92  			Properties loaded = load(ctx);
93  
94  			
95  			properties.putAll(loaded);
96  		}
97  
98  		
99  		return properties;
100 	}
101 
102 	public static Properties load(PropertiesContext context) {
103 		
104 		if (CollectionUtils.isEmpty(context.getLocations())) {
105 			return PropertyUtils.toEmpty(context.getProperties());
106 		}
107 
108 		
109 		Assert.notNull(context.getHelper(), "helper is null");
110 		Assert.notNull(context.getLocations(), "locations are null");
111 		Assert.notNull(context.getEncoding(), "encoding is null");
112 		Assert.notNull(context.getMissingLocationsMode(), "missingLocationsMode is null");
113 
114 		
115 		Properties global = PropertyUtils.getGlobalProperties();
116 
117 		
118 		context.setProperties(PropertyUtils.toEmpty(context.getProperties()));
119 
120 		
121 		Properties result = new Properties();
122 
123 		
124 		result.putAll(PropertyUtils.toEmpty(context.getProperties()));
125 
126 		
127 		for (String location : context.getLocations()) {
128 
129 			
130 			Properties resolverProperties = PropertyUtils.combine(context.getProperties(), result, global);
131 
132 			
133 			String resolvedLocation = context.getHelper().replacePlaceholders(location, resolverProperties);
134 
135 			
136 			if (LocationUtils.exists(resolvedLocation)) {
137 
138 				
139 				Properties properties = PropertyUtils.load(resolvedLocation, context.getEncoding());
140 
141 				
142 				result.putAll(properties);
143 			} else {
144 
145 				
146 				ModeUtils.validate(context.getMissingLocationsMode(), "Non-existent location [" + resolvedLocation + "]");
147 			}
148 		}
149 
150 		
151 		return result;
152 	}
153 
154 	
155 
156 
157 
158 
159 
160 
161 	public static void decrypt(Properties properties, TextEncryptor encryptor) {
162 		decrypt(properties, encryptor, null, null);
163 	}
164 
165 	
166 
167 
168 
169 
170 
171 
172 	public static Properties getEncryptedProperties(Properties properties) {
173 		List<String> keys = getSortedKeys(properties);
174 		Properties encrypted = new Properties();
175 		for (String key : keys) {
176 			String value = properties.getProperty(key);
177 			if (isEncryptedPropertyValue(value)) {
178 				encrypted.setProperty(key, value);
179 			}
180 		}
181 		return encrypted;
182 	}
183 
184 	
185 
186 
187 
188 
189 
190 
191 	public static void decrypt(Properties properties, TextEncryptor encryptor, List<String> includes, List<String> excludes) {
192 		List<String> keys = getSortedKeys(properties, includes, excludes);
193 		for (String key : keys) {
194 			String value = properties.getProperty(key);
195 			if (isEncryptedPropertyValue(value)) {
196 				String decryptedValue = decryptPropertyValue(encryptor, value);
197 				properties.setProperty(key, decryptedValue);
198 			}
199 		}
200 	}
201 
202 	
203 
204 
205 	public static boolean isEncryptedPropertyValue(String value) {
206 		return StringUtils.startsWith(value, Constants.ENCRYPTION_PREFIX) && StringUtils.endsWith(value, Constants.ENCRYPTION_SUFFIX);
207 	}
208 
209 	
210 
211 
212 
213 
214 
215 
216 	public static void encrypt(Properties properties, TextEncryptor encryptor) {
217 		encrypt(properties, encryptor, null, null);
218 	}
219 
220 	
221 
222 
223 
224 
225 
226 
227 	public static void encrypt(Properties properties, TextEncryptor encryptor, List<String> includes, List<String> excludes) {
228 		List<String> keys = getSortedKeys(properties, includes, excludes);
229 		for (String key : keys) {
230 			String originalValue = properties.getProperty(key);
231 			String encryptedValue = encryptPropertyValue(encryptor, originalValue);
232 			properties.setProperty(key, encryptedValue);
233 		}
234 	}
235 
236 	
237 
238 
239 
240 
241 
242 
243 	public static String decryptPropertyValue(TextEncryptor encryptor, String value) {
244 		
245 		Assert.isTrue(StringUtils.startsWith(value, Constants.ENCRYPTION_PREFIX), "value does not start with " + Constants.ENCRYPTION_PREFIX);
246 		Assert.isTrue(StringUtils.endsWith(value, Constants.ENCRYPTION_SUFFIX), "value does not end with " + Constants.ENCRYPTION_SUFFIX);
247 
248 		
249 		int start = Constants.ENCRYPTION_PREFIX.length();
250 		int end = StringUtils.length(value) - Constants.ENCRYPTION_SUFFIX.length();
251 		String unwrapped = StringUtils.substring(value, start, end);
252 
253 		
254 		return encryptor.decrypt(unwrapped);
255 	}
256 
257 	
258 
259 
260 
261 
262 
263 
264 	public static String encryptPropertyValue(TextEncryptor encryptor, String value) {
265 		String encryptedValue = encryptor.encrypt(value);
266 		StringBuilder sb = new StringBuilder();
267 		sb.append(Constants.ENCRYPTION_PREFIX);
268 		sb.append(encryptedValue);
269 		sb.append(Constants.ENCRYPTION_SUFFIX);
270 		return sb.toString();
271 	}
272 
273 	public static void overrideWithGlobalValues(Properties properties, GlobalPropertiesMode mode) {
274 		List<String> keys = PropertyUtils.getSortedKeys(properties);
275 		Properties global = PropertyUtils.getProperties(mode);
276 		for (String key : keys) {
277 			String globalValue = global.getProperty(key);
278 			if (!StringUtils.isBlank(globalValue)) {
279 				properties.setProperty(key, globalValue);
280 			}
281 		}
282 	}
283 
284 	public static final Properties combine(List<Properties> properties) {
285 		Properties combined = new Properties();
286 		for (Properties p : properties) {
287 			combined.putAll(PropertyUtils.toEmpty(p));
288 		}
289 		return combined;
290 	}
291 
292 	public static final Properties combine(Properties... properties) {
293 		return combine(Arrays.asList(properties));
294 	}
295 
296 	public static final void process(Properties properties, PropertyProcessor processor) {
297 		process(properties, Collections.singletonList(processor));
298 	}
299 
300 	public static final void process(Properties properties, List<PropertyProcessor> processors) {
301 		for (PropertyProcessor processor : CollectionUtils.toEmptyList(processors)) {
302 			processor.process(properties);
303 		}
304 	}
305 
306 	public static final Properties toEmpty(Properties properties) {
307 		return properties == null ? new Properties() : properties;
308 	}
309 
310 	public static final boolean isSingleUnresolvedPlaceholder(String string) {
311 		return isSingleUnresolvedPlaceholder(string, Constants.DEFAULT_PLACEHOLDER_PREFIX, Constants.DEFAULT_PLACEHOLDER_SUFFIX);
312 	}
313 
314 	public static final boolean isSingleUnresolvedPlaceholder(String string, String prefix, String suffix) {
315 		int prefixMatches = StringUtils.countMatches(string, prefix);
316 		int suffixMatches = StringUtils.countMatches(string, suffix);
317 		boolean startsWith = StringUtils.startsWith(string, prefix);
318 		boolean endsWith = StringUtils.endsWith(string, suffix);
319 		return prefixMatches == 1 && suffixMatches == 1 && startsWith && endsWith;
320 	}
321 
322 	public static final boolean containsUnresolvedPlaceholder(String string) {
323 		return containsUnresolvedPlaceholder(string, Constants.DEFAULT_PLACEHOLDER_PREFIX, Constants.DEFAULT_PLACEHOLDER_SUFFIX);
324 	}
325 
326 	public static final boolean containsUnresolvedPlaceholder(String string, String prefix, String suffix) {
327 		int beginIndex = StringUtils.indexOf(string, prefix);
328 		if (beginIndex == -1) {
329 			return false;
330 		}
331 		return StringUtils.indexOf(string, suffix) != -1;
332 	}
333 
334 	
335 
336 
337 
338 	public static final Properties getResolvedProperties(Properties properties) {
339 		return getResolvedProperties(properties, Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE);
340 	}
341 
342 	
343 
344 
345 
346 	public static final Properties getResolvedProperties(Properties properties, GlobalPropertiesMode globalPropertiesMode) {
347 		return getResolvedProperties(properties, Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER, globalPropertiesMode);
348 	}
349 
350 	
351 
352 
353 
354 	public static final Properties getResolvedProperties(Properties properties, PropertyPlaceholderHelper helper) {
355 		return getResolvedProperties(properties, helper, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE);
356 	}
357 
358 	
359 
360 
361 
362 	public static final Properties getResolvedProperties(Properties properties, PropertyPlaceholderHelper helper, GlobalPropertiesMode globalPropertiesMode) {
363 		Properties global = PropertyUtils.getProperties(properties, globalPropertiesMode);
364 		List<String> keys = PropertyUtils.getSortedKeys(properties);
365 		Properties newProperties = new Properties();
366 		for (String key : keys) {
367 			String originalValue = properties.getProperty(key);
368 			String resolvedValue = helper.replacePlaceholders(originalValue, global);
369 			if (!resolvedValue.equals(originalValue)) {
370 				logger.debug("Resolved property '" + key + "' [{}] -> [{}]", Str.flatten(originalValue), Str.flatten(resolvedValue));
371 				newProperties.setProperty(key, resolvedValue);
372 			}
373 		}
374 		return newProperties;
375 	}
376 
377 	
378 
379 
380 	public static final List<String> getValues(Properties properties, List<String> keys) {
381 		List<String> values = new ArrayList<String>();
382 		for (String key : keys) {
383 			values.add(properties.getProperty(key));
384 		}
385 		return values;
386 	}
387 
388 	
389 
390 
391 	public static final List<String> getEndsWithKeys(Properties properties, String suffix) {
392 		List<String> keys = getSortedKeys(properties);
393 		List<String> matches = new ArrayList<String>();
394 		for (String key : keys) {
395 			if (StringUtils.endsWith(key, suffix)) {
396 				matches.add(key);
397 			}
398 		}
399 		return matches;
400 	}
401 
402 	
403 
404 
405 	public static final void trim(Properties properties, String includesCSV, String excludesCSV) {
406 		List<String> includes = CollectionUtils.getTrimmedListFromCSV(includesCSV);
407 		List<String> excludes = CollectionUtils.getTrimmedListFromCSV(excludesCSV);
408 		trim(properties, includes, excludes);
409 	}
410 
411 	
412 
413 
414 	public static final void trim(Properties properties, List<String> includes, List<String> excludes) {
415 		List<String> keys = getSortedKeys(properties);
416 		for (String key : keys) {
417 			if (!include(key, includes, excludes)) {
418 				logger.debug("Removing [{}]", key);
419 				properties.remove(key);
420 			}
421 		}
422 	}
423 
424 	
425 
426 
427 
428 
429 
430 
431 
432 	public static final boolean include(String value, List<String> includes, List<String> excludes) {
433 		if (isSingleWildcardMatch(value, excludes)) {
434 			
435 			return false;
436 		} else {
437 			
438 			return CollectionUtils.isEmpty(includes) || isSingleWildcardMatch(value, includes);
439 		}
440 	}
441 
442 	public static final boolean isSingleWildcardMatch(String s, List<String> patterns) {
443 		for (String pattern : CollectionUtils.toEmptyList(patterns)) {
444 			if (isSingleWildcardMatch(s, pattern)) {
445 				return true;
446 			}
447 		}
448 		return false;
449 	}
450 
451 	
452 
453 
454 
455 
456 
457 
458 
459 
460 
461 
462 
463 
464 
465 
466 
467 
468 
469 	public static final boolean isSingleWildcardMatch(String value, String pattern) {
470 		if (value == null && pattern == null) {
471 			
472 			return true;
473 		} else if (value != null && pattern == null || value == null && pattern != null) {
474 			
475 			return false;
476 		} else if (pattern.equals(Constants.WILDCARD)) {
477 			
478 			return true;
479 		} else if (StringUtils.countMatches(pattern, Constants.WILDCARD) > 1) {
480 			
481 			throw new IllegalArgumentException("Pattern [" + pattern + "] is not supported.  Only one wildcard is allowed in the pattern");
482 		} else if (!StringUtils.contains(pattern, Constants.WILDCARD)) {
483 			
484 			return StringUtils.equals(value, pattern);
485 		} else {
486 			
487 			
488 			
489 			int pos = StringUtils.indexOf(pattern, Constants.WILDCARD);
490 			int suffixPos = pos + Constants.WILDCARD.length();
491 			boolean nullPrefix = pos == 0;
492 			boolean nullSuffix = suffixPos >= pattern.length();
493 			String prefix = nullPrefix ? null : StringUtils.substring(pattern, 0, pos);
494 			String suffix = nullSuffix ? null : StringUtils.substring(pattern, suffixPos);
495 			boolean prefixMatch = nullPrefix || StringUtils.startsWith(value, prefix);
496 			boolean suffixMatch = nullSuffix || StringUtils.endsWith(value, suffix);
497 			return prefixMatch && suffixMatch;
498 		}
499 	}
500 
501 	
502 
503 
504 	public static final Properties getProperties(Properties properties, String include, String exclude) {
505 		List<String> keys = getSortedKeys(properties, include, exclude);
506 		Properties newProperties = new Properties();
507 		for (String key : keys) {
508 			String value = properties.getProperty(key);
509 			newProperties.setProperty(key, value);
510 		}
511 		return newProperties;
512 	}
513 
514 	
515 
516 
517 	public static final List<String> getSortedKeys(Properties properties, String include, String exclude) {
518 		return getSortedKeys(properties, CollectionUtils.toEmptyList(include), CollectionUtils.toEmptyList(exclude));
519 	}
520 
521 	
522 
523 
524 	public static final List<String> getSortedKeys(Properties properties, List<String> includes, List<String> excludes) {
525 		List<String> keys = getSortedKeys(properties);
526 		List<String> includedKeys = new ArrayList<String>();
527 		for (String key : keys) {
528 			if (include(key, includes, excludes)) {
529 				includedKeys.add(key);
530 			}
531 		}
532 		return includedKeys;
533 	}
534 
535 	
536 
537 
538 	public static final List<String> getStartsWithKeys(Properties properties, String prefix) {
539 		List<String> keys = getSortedKeys(properties);
540 		List<String> matches = new ArrayList<String>();
541 		for (String key : keys) {
542 			if (StringUtils.startsWith(key, prefix)) {
543 				matches.add(key);
544 			}
545 		}
546 		return matches;
547 	}
548 
549 	
550 
551 
552 	public static final List<String> getSortedKeys(Properties properties) {
553 		List<String> keys = new ArrayList<String>(properties.stringPropertyNames());
554 		Collections.sort(keys);
555 		return keys;
556 	}
557 
558 	public static final String toString(Properties properties) {
559 		List<String> keys = getSortedKeys(properties);
560 		StringBuilder sb = new StringBuilder();
561 		for (String key : keys) {
562 			String value = Str.flatten(properties.getProperty(key));
563 			sb.append(key + "=" + value + "\n");
564 		}
565 		return sb.toString();
566 	}
567 
568 	public static final void info(Properties properties) {
569 		properties = toEmpty(properties);
570 		logger.info("--- Displaying {} properties ---\n\n{}", properties.size(), toString(properties));
571 	}
572 
573 	public static final void debug(Properties properties) {
574 		properties = toEmpty(properties);
575 		logger.debug("--- Displaying {} properties ---\n\n{}", properties.size(), toString(properties));
576 	}
577 
578 	
579 
580 
581 	public static final void store(Properties properties, File file) {
582 		store(properties, file, null);
583 	}
584 
585 	
586 
587 
588 	public static final void store(Properties properties, File file, String encoding) {
589 		store(properties, file, encoding, null);
590 	}
591 
592 	
593 
594 
595 	public static final void store(Properties properties, File file, String encoding, String comment) {
596 		OutputStream out = null;
597 		Writer writer = null;
598 		try {
599 			out = FileUtils.openOutputStream(file);
600 			String path = file.getCanonicalPath();
601 			boolean xml = isXml(path);
602 			Properties sorted = getSortedProperties(properties);
603 			comment = getComment(encoding, comment, xml);
604 			if (xml) {
605 				logger.info("Storing XML properties - [{}] encoding={}", path, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING));
606 				if (encoding == null) {
607 					sorted.storeToXML(out, comment);
608 				} else {
609 					sorted.storeToXML(out, comment, encoding);
610 				}
611 			} else {
612 				writer = LocationUtils.getWriter(out, encoding);
613 				logger.info("Storing properties - [{}] encoding={}", path, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING));
614 				sorted.store(writer, comment);
615 			}
616 		} catch (IOException e) {
617 			throw new IllegalStateException("Unexpected IO error", e);
618 		} finally {
619 			IOUtils.closeQuietly(writer);
620 			IOUtils.closeQuietly(out);
621 		}
622 	}
623 
624 	
625 
626 
627 
628 	public static final Properties getGlobalProperties() {
629 		return getProperties(Constants.DEFAULT_GLOBAL_PROPERTIES_MODE);
630 	}
631 
632 	
633 
634 
635 
636 	public static final Properties getGlobalProperties(Properties properties) {
637 		return getProperties(properties, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE);
638 	}
639 
640 	
641 
642 
643 
644 
645 
646 	public static final Properties getProperties(Properties properties, GlobalPropertiesMode mode) {
647 		Properties newProperties = duplicate(properties);
648 		List<PropertyProcessor> modifiers = getPropertyProcessors(mode);
649 		for (PropertyProcessor modifier : modifiers) {
650 			modifier.process(newProperties);
651 		}
652 		return newProperties;
653 	}
654 
655 	
656 
657 
658 
659 
660 
661 	public static final Properties getProperties(GlobalPropertiesMode mode) {
662 		return getProperties(new Properties(), mode);
663 	}
664 
665 	
666 
667 
668 	public static final String getProperty(String key, GlobalPropertiesMode mode) {
669 		return getProperty(key, new Properties(), mode);
670 	}
671 
672 	
673 
674 
675 
676 	public static final String getProperty(String key, Properties properties, GlobalPropertiesMode mode) {
677 		return getProperties(properties, mode).getProperty(key);
678 	}
679 
680 	
681 
682 
683 	public static final List<PropertyProcessor> getPropertyProcessors(GlobalPropertiesMode mode) {
684 		List<PropertyProcessor> processors = new ArrayList<PropertyProcessor>();
685 		switch (mode) {
686 		case NONE:
687 			return processors;
688 		case ENVIRONMENT:
689 			processors.add(new AddPropertiesProcessor(getEnvAsProperties()));
690 			return processors;
691 		case SYSTEM:
692 			processors.add(new AddPropertiesProcessor(System.getProperties()));
693 			return processors;
694 		case BOTH:
695 			processors.add(new AddPropertiesProcessor(getEnvAsProperties()));
696 			processors.add(new AddPropertiesProcessor(System.getProperties()));
697 			return processors;
698 		default:
699 			throw new IllegalStateException(mode + " is unknown");
700 		}
701 	}
702 
703 	
704 
705 
706 	public static final Properties convert(Map<String, String> map) {
707 		Properties props = new Properties();
708 		for (String key : map.keySet()) {
709 			String value = map.get(key);
710 			props.setProperty(key, value);
711 		}
712 		return props;
713 	}
714 
715 	
716 
717 
718 	public static final Properties duplicate(Properties properties) {
719 		Properties newProperties = new Properties();
720 		newProperties.putAll(properties);
721 		return newProperties;
722 	}
723 
724 	
725 
726 
727 	public static Properties getEnvAsProperties() {
728 		return getEnvAsProperties(ENV_PREFIX);
729 	}
730 
731 	
732 
733 
734 	public static Properties getEnvAsProperties(String prefix) {
735 		Properties properties = convert(System.getenv());
736 		return getPrefixedProperties(properties, prefix);
737 	}
738 
739 	
740 
741 
742 	public static final boolean isXml(String location) {
743 		return StringUtils.endsWithIgnoreCase(location, XML_EXTENSION);
744 	}
745 
746 	
747 
748 
749 	public static final Properties load(File file) {
750 		return load(file, null);
751 	}
752 
753 	
754 
755 
756 	public static final Properties load(File file, String encoding) {
757 		String location = LocationUtils.getCanonicalPath(file);
758 		return load(location, encoding);
759 	}
760 
761 	
762 
763 
764 	public static final Properties load(String location) {
765 		return load(location, null);
766 	}
767 
768 	
769 
770 
771 	public static final Properties load(String location, String encoding) {
772 		InputStream in = null;
773 		Reader reader = null;
774 		try {
775 			Properties properties = new Properties();
776 			boolean xml = isXml(location);
777 			location = getCanonicalLocation(location);
778 			if (xml) {
779 				in = LocationUtils.getInputStream(location);
780 				logger.info("Loading XML properties - [{}]", location);
781 				properties.loadFromXML(in);
782 			} else {
783 				logger.info("Loading properties - [{}] encoding={}", location, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING));
784 				reader = LocationUtils.getBufferedReader(location, encoding);
785 				properties.load(reader);
786 			}
787 			return properties;
788 		} catch (IOException e) {
789 			throw new IllegalStateException("Unexpected IO error", e);
790 		} finally {
791 			IOUtils.closeQuietly(in);
792 			IOUtils.closeQuietly(reader);
793 		}
794 	}
795 
796 	protected static String getCanonicalLocation(String location) {
797 		if (LocationUtils.isExistingFile(location)) {
798 			return LocationUtils.getCanonicalPath(new File(location));
799 		} else {
800 			return location;
801 		}
802 	}
803 
804 	
805 
806 
807 
808 	public static final Properties getPrefixedProperties(Properties properties, String prefix) {
809 		if (StringUtils.isBlank(prefix)) {
810 			return duplicate(properties);
811 		}
812 		Properties newProperties = new Properties();
813 		for (String key : properties.stringPropertyNames()) {
814 			String value = properties.getProperty(key);
815 			String newKey = StringUtils.startsWith(key, prefix + ".") ? key : prefix + "." + key;
816 			newProperties.setProperty(newKey, value);
817 		}
818 		return newProperties;
819 	}
820 
821 	
822 
823 
824 	public static final Properties reformatKeysAsEnvVars(Properties properties) {
825 		Properties newProperties = new Properties();
826 		for (String key : properties.stringPropertyNames()) {
827 			String value = properties.getProperty(key);
828 			String newKey = StringUtils.upperCase(StringUtils.replace(key, ".", "-"));
829 			newProperties.setProperty(newKey, value);
830 		}
831 		return newProperties;
832 	}
833 
834 	
835 
836 
837 
838 	public static final void addOrOverrideProperty(Properties properties, String key, String newValue, Mode propertyOverwriteMode) {
839 		String oldValue = properties.getProperty(key);
840 		if (StringUtils.equals(newValue, oldValue)) {
841 			
842 			return;
843 		}
844 		boolean overwrite = !StringUtils.isBlank(oldValue);
845 
846 		
847 		String logNewValue = newValue;
848 		String logOldValue = oldValue;
849 		if (obscure(key)) {
850 			logNewValue = "PROTECTED";
851 			logOldValue = "PROTECTED";
852 		}
853 
854 		if (overwrite) {
855 			
856 			
857 			Object[] args = new Object[] { key, Str.flatten(logNewValue), Str.flatten(logOldValue) };
858 			ModeUtils.validate(propertyOverwriteMode, "Overriding [{}={}] was [{}]", args, "Override of existing property [" + key + "] is not allowed.");
859 		} else {
860 			
861 			logger.info("Adding [{}={}]", key, Str.flatten(logNewValue));
862 		}
863 		properties.setProperty(key, newValue);
864 	}
865 
866 	protected static boolean obscure(String key) {
867 		if (StringUtils.containsIgnoreCase(key, ".password")) {
868 			return true;
869 		}
870 		if (StringUtils.containsIgnoreCase(key, ".secret")) {
871 			return true;
872 		}
873 		if (StringUtils.containsIgnoreCase(key, ".private")) {
874 			return true;
875 		}
876 		return false;
877 	}
878 
879 	private static final String getDefaultComment(String encoding, boolean xml) {
880 		if (encoding == null) {
881 			if (xml) {
882 				
883 				return "encoding.default=" + DEFAULT_XML_ENCODING;
884 			} else {
885 				
886 				return "encoding.default=" + DEFAULT_ENCODING;
887 			}
888 		} else {
889 			return "encoding.specified=" + encoding;
890 		}
891 	}
892 
893 	private static final String getComment(String encoding, String comment, boolean xml) {
894 		if (StringUtils.isBlank(comment)) {
895 			return getDefaultComment(encoding, xml);
896 		} else {
897 			return comment + "\n#" + getDefaultComment(encoding, xml);
898 		}
899 	}
900 
901 	
902 
903 
904 	private static final SortedProperties getSortedProperties(Properties properties) {
905 		SortedProperties sp = new PropertyUtils().new SortedProperties();
906 		sp.putAll(properties);
907 		return sp;
908 	}
909 
910 	
911 
912 
913 	private class SortedProperties extends Properties {
914 
915 		private static final long serialVersionUID = 1330825236411537386L;
916 
917 		
918 
919 
920 		@Override
921 		public Set<Object> keySet() {
922 			return Collections.unmodifiableSet(new TreeSet<Object>(super.keySet()));
923 		}
924 
925 		
926 
927 
928 		@Override
929 		public synchronized Enumeration<Object> keys() {
930 			return Collections.enumeration(new TreeSet<Object>(super.keySet()));
931 		}
932 	}
933 
934 	
935 
936 
937 
938 
939 
940 
941 
942 
943 
944 
945 	public static final void addListComparisonProperties(Properties properties, ComparisonResults listComparison, List<String> propertyNames) {
946 		
947 		Assert.isTrue(propertyNames.size() == 3);
948 
949 		properties.setProperty(propertyNames.get(0), CollectionUtils.getCSV(listComparison.getAdded()));
950 		properties.setProperty(propertyNames.get(1), CollectionUtils.getCSV(listComparison.getSame()));
951 		properties.setProperty(propertyNames.get(2), CollectionUtils.getCSV(listComparison.getDeleted()));
952 	}
953 
954 }