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.kuali.common.util.property.Constants;
39 import org.kuali.common.util.property.GlobalPropertiesMode;
40 import org.kuali.common.util.property.processor.AddPropertiesProcessor;
41 import org.kuali.common.util.property.processor.PropertyProcessor;
42 import org.slf4j.Logger;
43 import org.slf4j.LoggerFactory;
44 import org.springframework.util.PropertyPlaceholderHelper;
45
46
47
48
49
50
51
52 public class PropertyUtils {
53
54 private static final Logger logger = LoggerFactory.getLogger(PropertyUtils.class);
55
56 private static final String XML_EXTENSION = ".xml";
57 private static final String ENV_PREFIX = "env";
58 private static final String DEFAULT_ENCODING = Charset.defaultCharset().name();
59 private static final String DEFAULT_XML_ENCODING = "UTF-8";
60
61 public static final Properties combine(List<Properties> properties) {
62 Properties combined = new Properties();
63 for (Properties p : properties) {
64 combined.putAll(PropertyUtils.toEmpty(p));
65 }
66 return combined;
67 }
68
69 public static final Properties combine(Properties... properties) {
70 return combine(Arrays.asList(properties));
71 }
72
73 public static final void process(Properties properties, PropertyProcessor processor) {
74 process(properties, Collections.singletonList(processor));
75 }
76
77 public static final void process(Properties properties, List<PropertyProcessor> processors) {
78 for (PropertyProcessor processor : CollectionUtils.toEmptyList(processors)) {
79 processor.process(properties);
80 }
81 }
82
83 public static final Properties toEmpty(Properties properties) {
84 return properties == null ? new Properties() : properties;
85 }
86
87 public static final boolean isSingleUnresolvedPlaceholder(String string) {
88 return isSingleUnresolvedPlaceholder(string, Constants.DEFAULT_PLACEHOLDER_PREFIX, Constants.DEFAULT_PLACEHOLDER_SUFFIX);
89 }
90
91 public static final boolean isSingleUnresolvedPlaceholder(String string, String prefix, String suffix) {
92 int prefixMatches = StringUtils.countMatches(string, prefix);
93 int suffixMatches = StringUtils.countMatches(string, suffix);
94 boolean startsWith = StringUtils.startsWith(string, prefix);
95 boolean endsWith = StringUtils.endsWith(string, suffix);
96 return prefixMatches == 1 && suffixMatches == 1 && startsWith && endsWith;
97 }
98
99 public static final boolean containsUnresolvedPlaceholder(String string) {
100 return containsUnresolvedPlaceholder(string, Constants.DEFAULT_PLACEHOLDER_PREFIX, Constants.DEFAULT_PLACEHOLDER_SUFFIX);
101 }
102
103 public static final boolean containsUnresolvedPlaceholder(String string, String prefix, String suffix) {
104 int beginIndex = StringUtils.indexOf(string, prefix);
105 if (beginIndex == -1) {
106 return false;
107 }
108 return StringUtils.indexOf(string, suffix) != -1;
109 }
110
111
112
113
114
115 public static final Properties getResolvedProperties(Properties properties) {
116 return getResolvedProperties(properties, Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE);
117 }
118
119
120
121
122
123 public static final Properties getResolvedProperties(Properties properties, GlobalPropertiesMode globalPropertiesMode) {
124 return getResolvedProperties(properties, Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER, globalPropertiesMode);
125 }
126
127
128
129
130
131 public static final Properties getResolvedProperties(Properties properties, PropertyPlaceholderHelper helper) {
132 return getResolvedProperties(properties, helper, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE);
133 }
134
135
136
137
138
139 public static final Properties getResolvedProperties(Properties properties, PropertyPlaceholderHelper helper, GlobalPropertiesMode globalPropertiesMode) {
140 Properties global = PropertyUtils.getProperties(properties, globalPropertiesMode);
141 List<String> keys = PropertyUtils.getSortedKeys(properties);
142 Properties newProperties = new Properties();
143 for (String key : keys) {
144 String originalValue = properties.getProperty(key);
145 String resolvedValue = helper.replacePlaceholders(originalValue, global);
146 if (!resolvedValue.equals(originalValue)) {
147 logger.debug("Resolved property '" + key + "' [{}] -> [{}]", Str.flatten(originalValue), Str.flatten(resolvedValue));
148 newProperties.setProperty(key, resolvedValue);
149 }
150 }
151 return newProperties;
152 }
153
154
155
156
157 public static final List<String> getValues(Properties properties, List<String> keys) {
158 List<String> values = new ArrayList<String>();
159 for (String key : keys) {
160 values.add(properties.getProperty(key));
161 }
162 return values;
163 }
164
165
166
167
168 public static final List<String> getEndsWithKeys(Properties properties, String suffix) {
169 List<String> keys = getSortedKeys(properties);
170 List<String> matches = new ArrayList<String>();
171 for (String key : keys) {
172 if (StringUtils.endsWith(key, suffix)) {
173 matches.add(key);
174 }
175 }
176 return matches;
177 }
178
179
180
181
182
183 public static final void trim(Properties properties, String includesCSV, String excludesCSV) {
184 List<String> includes = CollectionUtils.getTrimmedListFromCSV(includesCSV);
185 List<String> excludes = CollectionUtils.getTrimmedListFromCSV(excludesCSV);
186 trim(properties, includes, excludes);
187 }
188
189
190
191
192 public static final void trim(Properties properties, List<String> includes, List<String> excludes) {
193 List<String> keys = getSortedKeys(properties);
194 for (String key : keys) {
195 if (!include(key, includes, excludes)) {
196 logger.debug("Removing [{}]", key);
197 properties.remove(key);
198 }
199 }
200 }
201
202
203
204
205
206
207
208
209
210
211 public static final boolean include(String value, List<String> includes, List<String> excludes) {
212 if (isSingleWildcardMatch(value, excludes)) {
213
214 return false;
215 } else {
216
217 return CollectionUtils.isEmpty(includes) || isSingleWildcardMatch(value, includes);
218 }
219 }
220
221 public static final boolean isSingleWildcardMatch(String s, List<String> patterns) {
222 for (String pattern : CollectionUtils.toEmptyList(patterns)) {
223 if (isSingleWildcardMatch(s, pattern)) {
224 return true;
225 }
226 }
227 return false;
228 }
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248 public static final boolean isSingleWildcardMatch(String value, String pattern) {
249 if (value == null && pattern == null) {
250
251 return true;
252 } else if (value != null && pattern == null || value == null && pattern != null) {
253
254 return false;
255 } else if (pattern.equals(Constants.WILDCARD)) {
256
257 return true;
258 } else if (StringUtils.countMatches(pattern, Constants.WILDCARD) > 1) {
259
260 throw new IllegalArgumentException("Pattern [" + pattern + "] is not supported. Only one wildcard is allowed in the pattern");
261 } else if (!StringUtils.contains(pattern, Constants.WILDCARD)) {
262
263 return StringUtils.equals(value, pattern);
264 } else {
265
266
267
268 int pos = StringUtils.indexOf(pattern, Constants.WILDCARD);
269 int suffixPos = pos + Constants.WILDCARD.length();
270 boolean nullPrefix = pos == 0;
271 boolean nullSuffix = suffixPos >= pattern.length();
272 String prefix = nullPrefix ? null : StringUtils.substring(pattern, 0, pos);
273 String suffix = nullSuffix ? null : StringUtils.substring(pattern, suffixPos);
274 boolean prefixMatch = nullPrefix || StringUtils.startsWith(value, prefix);
275 boolean suffixMatch = nullSuffix || StringUtils.endsWith(value, suffix);
276 return prefixMatch && suffixMatch;
277 }
278 }
279
280
281
282
283 public static final Properties getProperties(Properties properties, String include, String exclude) {
284 List<String> keys = getSortedKeys(properties, include, exclude);
285 Properties newProperties = new Properties();
286 for (String key : keys) {
287 String value = properties.getProperty(key);
288 newProperties.setProperty(key, value);
289 }
290 return newProperties;
291 }
292
293
294
295
296 public static final List<String> getSortedKeys(Properties properties, String include, String exclude) {
297 return getSortedKeys(properties, CollectionUtils.toEmptyList(include), CollectionUtils.toEmptyList(exclude));
298 }
299
300
301
302
303 public static final List<String> getSortedKeys(Properties properties, List<String> includes, List<String> excludes) {
304 List<String> keys = getSortedKeys(properties);
305 List<String> includedKeys = new ArrayList<String>();
306 for (String key : keys) {
307 if (include(key, includes, excludes)) {
308 includedKeys.add(key);
309 }
310 }
311 return includedKeys;
312 }
313
314
315
316
317 public static final List<String> getStartsWithKeys(Properties properties, String prefix) {
318 List<String> keys = getSortedKeys(properties);
319 List<String> matches = new ArrayList<String>();
320 for (String key : keys) {
321 if (StringUtils.startsWith(key, prefix)) {
322 matches.add(key);
323 }
324 }
325 return matches;
326 }
327
328
329
330
331 public static final List<String> getSortedKeys(Properties properties) {
332 List<String> keys = new ArrayList<String>(properties.stringPropertyNames());
333 Collections.sort(keys);
334 return keys;
335 }
336
337 public static final String toString(Properties properties) {
338 List<String> keys = getSortedKeys(properties);
339 StringBuilder sb = new StringBuilder();
340 for (String key : keys) {
341 String value = Str.flatten(properties.getProperty(key));
342 sb.append(key + "=" + value + "\n");
343 }
344 return sb.toString();
345 }
346
347 public static final void info(Properties properties) {
348 properties = toEmpty(properties);
349 logger.info("--- Displaying {} properties ---\n\n{}", properties.size(), toString(properties));
350 }
351
352 public static final void debug(Properties properties) {
353 properties = toEmpty(properties);
354 logger.debug("--- Displaying {} properties ---\n\n{}", properties.size(), toString(properties));
355 }
356
357
358
359
360 public static final void store(Properties properties, File file) {
361 store(properties, file, null);
362 }
363
364
365
366
367 public static final void store(Properties properties, File file, String encoding) {
368 store(properties, file, encoding, null);
369 }
370
371
372
373
374 public static final void store(Properties properties, File file, String encoding, String comment) {
375 OutputStream out = null;
376 Writer writer = null;
377 try {
378 out = FileUtils.openOutputStream(file);
379 String path = file.getCanonicalPath();
380 boolean xml = isXml(path);
381 Properties sorted = getSortedProperties(properties);
382 comment = getComment(encoding, comment, xml);
383 if (xml) {
384 logger.info("Storing XML properties - [{}] encoding={}", path, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING));
385 if (encoding == null) {
386 sorted.storeToXML(out, comment);
387 } else {
388 sorted.storeToXML(out, comment, encoding);
389 }
390 } else {
391 writer = LocationUtils.getWriter(out, encoding);
392 logger.info("Storing properties - [{}] encoding={}", path, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING));
393 sorted.store(writer, comment);
394 }
395 } catch (IOException e) {
396 throw new IllegalStateException("Unexpected IO error", e);
397 } finally {
398 IOUtils.closeQuietly(writer);
399 IOUtils.closeQuietly(out);
400 }
401 }
402
403
404
405
406
407
408 public static final Properties getGlobalProperties() {
409 return getProperties(Constants.DEFAULT_GLOBAL_PROPERTIES_MODE);
410 }
411
412
413
414
415
416
417 public static final Properties getGlobalProperties(Properties properties) {
418 return getProperties(properties, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE);
419 }
420
421
422
423
424
425
426
427
428
429 public static final Properties getProperties(Properties properties, GlobalPropertiesMode mode) {
430 Properties newProperties = duplicate(properties);
431 List<PropertyProcessor> modifiers = getPropertyProcessors(mode);
432 for (PropertyProcessor modifier : modifiers) {
433 modifier.process(newProperties);
434 }
435 return newProperties;
436 }
437
438
439
440
441
442
443
444
445
446 public static final Properties getProperties(GlobalPropertiesMode mode) {
447 return getProperties(new Properties(), mode);
448 }
449
450
451
452
453 public static final String getProperty(String key, GlobalPropertiesMode mode) {
454 return getProperty(key, new Properties(), mode);
455 }
456
457
458
459
460
461 public static final String getProperty(String key, Properties properties, GlobalPropertiesMode mode) {
462 return getProperties(properties, mode).getProperty(key);
463 }
464
465
466
467
468 public static final List<PropertyProcessor> getPropertyProcessors(GlobalPropertiesMode mode) {
469 List<PropertyProcessor> processors = new ArrayList<PropertyProcessor>();
470 switch (mode) {
471 case NONE:
472 return processors;
473 case ENVIRONMENT:
474 processors.add(new AddPropertiesProcessor(getEnvAsProperties()));
475 return processors;
476 case SYSTEM:
477 processors.add(new AddPropertiesProcessor(System.getProperties()));
478 return processors;
479 case BOTH:
480 processors.add(new AddPropertiesProcessor(getEnvAsProperties()));
481 processors.add(new AddPropertiesProcessor(System.getProperties()));
482 return processors;
483 default:
484 throw new IllegalStateException(mode + " is unknown");
485 }
486 }
487
488
489
490
491 public static final Properties convert(Map<String, String> map) {
492 Properties props = new Properties();
493 for (String key : map.keySet()) {
494 String value = map.get(key);
495 props.setProperty(key, value);
496 }
497 return props;
498 }
499
500
501
502
503 public static final Properties duplicate(Properties properties) {
504 Properties newProperties = new Properties();
505 newProperties.putAll(properties);
506 return newProperties;
507 }
508
509
510
511
512 public static Properties getEnvAsProperties() {
513 return getEnvAsProperties(ENV_PREFIX);
514 }
515
516
517
518
519 public static Properties getEnvAsProperties(String prefix) {
520 Properties properties = convert(System.getenv());
521 return getPrefixedProperties(properties, prefix);
522 }
523
524
525
526
527 public static final boolean isXml(String location) {
528 return StringUtils.endsWithIgnoreCase(location, XML_EXTENSION);
529 }
530
531
532
533
534 public static final Properties load(File file) {
535 return load(file, null);
536 }
537
538
539
540
541 public static final Properties load(File file, String encoding) {
542 String location = LocationUtils.getCanonicalPath(file);
543 return load(location, encoding);
544 }
545
546
547
548
549 public static final Properties load(String location) {
550 return load(location, null);
551 }
552
553
554
555
556 public static final Properties load(String location, String encoding) {
557 InputStream in = null;
558 Reader reader = null;
559 try {
560 Properties properties = new Properties();
561 boolean xml = isXml(location);
562 if (xml) {
563 in = LocationUtils.getInputStream(location);
564 logger.info("Loading XML properties - [{}]", location);
565 properties.loadFromXML(in);
566 } else {
567 logger.info("Loading properties - [{}] encoding={}", location, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING));
568 reader = LocationUtils.getBufferedReader(location, encoding);
569 properties.load(reader);
570 }
571 return properties;
572 } catch (IOException e) {
573 throw new IllegalStateException("Unexpected IO error", e);
574 } finally {
575 IOUtils.closeQuietly(in);
576 IOUtils.closeQuietly(reader);
577 }
578 }
579
580
581
582
583
584 public static final Properties getPrefixedProperties(Properties properties, String prefix) {
585 if (StringUtils.isBlank(prefix)) {
586 return duplicate(properties);
587 }
588 Properties newProperties = new Properties();
589 for (String key : properties.stringPropertyNames()) {
590 String value = properties.getProperty(key);
591 String newKey = StringUtils.startsWith(key, prefix + ".") ? key : prefix + "." + key;
592 newProperties.setProperty(newKey, value);
593 }
594 return newProperties;
595 }
596
597
598
599
600 public static final Properties reformatKeysAsEnvVars(Properties properties) {
601 Properties newProperties = new Properties();
602 for (String key : properties.stringPropertyNames()) {
603 String value = properties.getProperty(key);
604 String newKey = StringUtils.upperCase(StringUtils.replace(key, ".", "-"));
605 newProperties.setProperty(newKey, value);
606 }
607 return newProperties;
608 }
609
610
611
612
613
614 public static final void addOrOverrideProperty(Properties properties, String key, String newValue, Mode propertyOverwriteMode) {
615 String oldValue = properties.getProperty(key);
616 if (StringUtils.equals(newValue, oldValue)) {
617
618 return;
619 }
620 boolean overwrite = !StringUtils.isBlank(oldValue);
621
622
623 String logNewValue = newValue;
624 String logOldValue = oldValue;
625 if (obscure(key)) {
626 logNewValue = "PROTECTED";
627 logOldValue = "PROTECTED";
628 }
629
630 if (overwrite) {
631
632
633 Object[] args = new Object[] { key, Str.flatten(logNewValue), Str.flatten(logOldValue) };
634 ModeUtils.validate(propertyOverwriteMode, "Overriding [{}={}] was [{}]", args, "Override of existing property [" + key + "] is not allowed.");
635 } else {
636
637 logger.info("Adding [{}={}]", key, Str.flatten(logNewValue));
638 }
639 properties.setProperty(key, newValue);
640 }
641
642 protected static boolean obscure(String key) {
643 if (StringUtils.containsIgnoreCase(key, ".password")) {
644 return true;
645 }
646 if (StringUtils.containsIgnoreCase(key, ".secret")) {
647 return true;
648 }
649 if (StringUtils.containsIgnoreCase(key, ".private")) {
650 return true;
651 }
652 return false;
653 }
654
655 private static final String getDefaultComment(String encoding, boolean xml) {
656 if (encoding == null) {
657 if (xml) {
658
659 return "encoding.default=" + DEFAULT_XML_ENCODING;
660 } else {
661
662 return "encoding.default=" + DEFAULT_ENCODING;
663 }
664 } else {
665 return "encoding.specified=" + encoding;
666 }
667 }
668
669 private static final String getComment(String encoding, String comment, boolean xml) {
670 if (StringUtils.isBlank(comment)) {
671 return getDefaultComment(encoding, xml);
672 } else {
673 return comment + "\n#" + getDefaultComment(encoding, xml);
674 }
675 }
676
677
678
679
680 private static final SortedProperties getSortedProperties(Properties properties) {
681 SortedProperties sp = new PropertyUtils().new SortedProperties();
682 sp.putAll(properties);
683 return sp;
684 }
685
686
687
688
689
690 private class SortedProperties extends Properties {
691
692 private static final long serialVersionUID = 1330825236411537386L;
693
694
695
696
697 @Override
698 public Set<Object> keySet() {
699 return Collections.unmodifiableSet(new TreeSet<Object>(super.keySet()));
700 }
701
702
703
704
705 @Override
706 public synchronized Enumeration<Object> keys() {
707 return Collections.enumeration(new TreeSet<Object>(super.keySet()));
708 }
709 }
710
711 }