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.Collections;
27 import java.util.Enumeration;
28 import java.util.List;
29 import java.util.Map;
30 import java.util.Properties;
31 import java.util.Set;
32 import java.util.TreeSet;
33
34 import org.apache.commons.io.FileUtils;
35 import org.apache.commons.io.IOUtils;
36 import org.apache.commons.lang3.StringUtils;
37 import org.kuali.common.util.property.Constants;
38 import org.kuali.common.util.property.GlobalPropertiesMode;
39 import org.kuali.common.util.property.processor.AddPropertiesProcessor;
40 import org.kuali.common.util.property.processor.PropertyProcessor;
41 import org.slf4j.Logger;
42 import org.slf4j.LoggerFactory;
43 import org.springframework.util.PropertyPlaceholderHelper;
44
45
46
47
48
49
50
51 public class PropertyUtils {
52
53 private static final Logger logger = LoggerFactory.getLogger(PropertyUtils.class);
54
55 private static final String XML_EXTENSION = ".xml";
56 private static final String ENV_PREFIX = "env";
57 private static final String DEFAULT_ENCODING = Charset.defaultCharset().name();
58 private static final String DEFAULT_XML_ENCODING = "UTF-8";
59
60 public static final Properties toEmpty(Properties properties) {
61 return properties == null ? new Properties() : properties;
62 }
63
64 public static final boolean isSingleUnresolvedPlaceholder(String string) {
65 return isSingleUnresolvedPlaceholder(string, Constants.DEFAULT_PLACEHOLDER_PREFIX, Constants.DEFAULT_PLACEHOLDER_SUFFIX);
66 }
67
68 public static final boolean isSingleUnresolvedPlaceholder(String string, String prefix, String suffix) {
69 int prefixMatches = StringUtils.countMatches(string, prefix);
70 int suffixMatches = StringUtils.countMatches(string, suffix);
71 boolean startsWith = StringUtils.startsWith(string, prefix);
72 boolean endsWith = StringUtils.endsWith(string, suffix);
73 return prefixMatches == 1 && suffixMatches == 1 && startsWith && endsWith;
74 }
75
76 public static final boolean containsUnresolvedPlaceholder2(String string) {
77 int beginIndex = StringUtils.indexOf(string, Constants.DEFAULT_PLACEHOLDER_PREFIX);
78 if (beginIndex == -1) {
79 return false;
80 }
81 int endIndex = StringUtils.indexOf(string, Constants.DEFAULT_PLACEHOLDER_SUFFIX, beginIndex);
82 if (endIndex == -1) {
83 return false;
84 }
85 String substring = StringUtils.substring(string, beginIndex, endIndex);
86 return StringUtils.indexOf(substring, Constants.DEFAULT_VALUE_SEPARATOR) == -1;
87 }
88
89 public static final boolean containsUnresolvedPlaceholder(String string) {
90 return containsUnresolvedPlaceholder(string, Constants.DEFAULT_PLACEHOLDER_PREFIX, Constants.DEFAULT_PLACEHOLDER_SUFFIX);
91 }
92
93 public static final boolean containsUnresolvedPlaceholder(String string, String prefix, String suffix) {
94 int beginIndex = StringUtils.indexOf(string, prefix);
95 if (beginIndex == -1) {
96 return false;
97 }
98 return StringUtils.indexOf(string, suffix) != -1;
99 }
100
101
102
103
104
105 public static final Properties getResolvedProperties(Properties properties) {
106 return getResolvedProperties(properties, Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE);
107 }
108
109
110
111
112
113 public static final Properties getResolvedProperties(Properties properties, GlobalPropertiesMode globalPropertiesMode) {
114 return getResolvedProperties(properties, Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER, globalPropertiesMode);
115 }
116
117
118
119
120
121 public static final Properties getResolvedProperties(Properties properties, PropertyPlaceholderHelper helper) {
122 return getResolvedProperties(properties, helper, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE);
123 }
124
125
126
127
128
129 public static final Properties getResolvedProperties(Properties properties, PropertyPlaceholderHelper helper, GlobalPropertiesMode globalPropertiesMode) {
130 Properties global = PropertyUtils.getProperties(properties, globalPropertiesMode);
131 List<String> keys = PropertyUtils.getSortedKeys(properties);
132 Properties newProperties = new Properties();
133 for (String key : keys) {
134 String originalValue = properties.getProperty(key);
135 String resolvedValue = helper.replacePlaceholders(originalValue, global);
136 if (!resolvedValue.equals(originalValue)) {
137 logger.debug("Resolved property '" + key + "' [{}] -> [{}]", Str.flatten(originalValue), Str.flatten(resolvedValue));
138 newProperties.setProperty(key, resolvedValue);
139 }
140 }
141 return newProperties;
142 }
143
144
145
146
147 public static final List<String> getValues(Properties properties, List<String> keys) {
148 List<String> values = new ArrayList<String>();
149 for (String key : keys) {
150 values.add(properties.getProperty(key));
151 }
152 return values;
153 }
154
155
156
157
158 public static final List<String> getEndsWithKeys(Properties properties, String suffix) {
159 List<String> keys = getSortedKeys(properties);
160 List<String> matches = new ArrayList<String>();
161 for (String key : keys) {
162 if (StringUtils.endsWith(key, suffix)) {
163 matches.add(key);
164 }
165 }
166 return matches;
167 }
168
169
170
171
172
173 public static final void trim(Properties properties, String includesCSV, String excludesCSV) {
174 List<String> includes = CollectionUtils.getTrimmedListFromCSV(includesCSV);
175 List<String> excludes = CollectionUtils.getTrimmedListFromCSV(excludesCSV);
176 trim(properties, includes, excludes);
177 }
178
179
180
181
182 public static final void trim(Properties properties, List<String> includes, List<String> excludes) {
183 List<String> keys = getSortedKeys(properties);
184 for (String key : keys) {
185 boolean include = include(key, includes, excludes);
186 if (!include) {
187 logger.debug("Removing [{}]", key);
188 properties.remove(key);
189 }
190 }
191 }
192
193
194
195
196
197
198
199
200 public static final boolean include(String value, List<String> includes, List<String> excludes) {
201 if (!CollectionUtils.isEmpty(excludes) && excludes.contains(value)) {
202 return false;
203 } else {
204 return CollectionUtils.isEmpty(includes) || includes.contains(value);
205 }
206 }
207
208
209
210
211 public static final List<String> getSortedKeys(Properties properties, List<String> includes, List<String> excludes) {
212 List<String> keys = getSortedKeys(properties);
213 List<String> includedKeys = new ArrayList<String>();
214 for (String key : keys) {
215 if (include(key, includes, excludes)) {
216 includedKeys.add(key);
217 }
218 }
219 return includedKeys;
220 }
221
222
223
224
225 public static final List<String> getStartsWithKeys(Properties properties, String prefix) {
226 List<String> keys = getSortedKeys(properties);
227 List<String> matches = new ArrayList<String>();
228 for (String key : keys) {
229 if (StringUtils.startsWith(key, prefix)) {
230 matches.add(key);
231 }
232 }
233 return matches;
234 }
235
236
237
238
239 public static final List<String> getSortedKeys(Properties properties) {
240 List<String> keys = new ArrayList<String>(properties.stringPropertyNames());
241 Collections.sort(keys);
242 return keys;
243 }
244
245 public static final void show(Properties properties) {
246 List<String> keys = getSortedKeys(properties);
247 for (String key : keys) {
248 String value = Str.flatten(properties.getProperty(key));
249 logger.info(key + "=" + value);
250 }
251 }
252
253
254
255
256 public static final void store(Properties properties, File file) {
257 store(properties, file, null);
258 }
259
260
261
262
263 public static final void store(Properties properties, File file, String encoding) {
264 store(properties, file, encoding, null);
265 }
266
267
268
269
270 public static final void store(Properties properties, File file, String encoding, String comment) {
271 OutputStream out = null;
272 Writer writer = null;
273 try {
274 out = FileUtils.openOutputStream(file);
275 String path = file.getCanonicalPath();
276 boolean xml = isXml(path);
277 Properties sorted = getSortedProperties(properties);
278 comment = getComment(encoding, comment, xml);
279 if (xml) {
280 logger.info("Storing XML properties - [{}] encoding={}", path, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING));
281 if (encoding == null) {
282 sorted.storeToXML(out, comment);
283 } else {
284 sorted.storeToXML(out, comment, encoding);
285 }
286 } else {
287 writer = LocationUtils.getWriter(out, encoding);
288 logger.info("Storing properties - [{}] encoding={}", path, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING));
289 sorted.store(writer, comment);
290 }
291 } catch (IOException e) {
292 throw new IllegalStateException("Unexpected IO error", e);
293 } finally {
294 IOUtils.closeQuietly(writer);
295 IOUtils.closeQuietly(out);
296 }
297 }
298
299
300
301
302
303
304 public static final Properties getGlobalProperties() {
305 return getProperties(Constants.DEFAULT_GLOBAL_PROPERTIES_MODE);
306 }
307
308
309
310
311
312
313 public static final Properties getGlobalProperties(Properties properties) {
314 return getProperties(properties, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE);
315 }
316
317
318
319
320
321
322
323
324
325 public static final Properties getProperties(Properties properties, GlobalPropertiesMode mode) {
326 Properties newProperties = duplicate(properties);
327 List<PropertyProcessor> modifiers = getPropertyProcessors(mode);
328 for (PropertyProcessor modifier : modifiers) {
329 modifier.process(newProperties);
330 }
331 return newProperties;
332 }
333
334
335
336
337
338
339
340
341
342 public static final Properties getProperties(GlobalPropertiesMode mode) {
343 return getProperties(new Properties(), mode);
344 }
345
346
347
348
349 public static final String getProperty(String key, GlobalPropertiesMode mode) {
350 return getProperty(key, new Properties(), mode);
351 }
352
353
354
355
356
357 public static final String getProperty(String key, Properties properties, GlobalPropertiesMode mode) {
358 return getProperties(properties, mode).getProperty(key);
359 }
360
361
362
363
364 public static final List<PropertyProcessor> getPropertyProcessors(GlobalPropertiesMode mode) {
365 List<PropertyProcessor> processors = new ArrayList<PropertyProcessor>();
366 switch (mode) {
367 case NONE:
368 return processors;
369 case ENVIRONMENT:
370 processors.add(new AddPropertiesProcessor(getEnvAsProperties()));
371 return processors;
372 case SYSTEM:
373 processors.add(new AddPropertiesProcessor(System.getProperties()));
374 return processors;
375 case BOTH:
376 processors.add(new AddPropertiesProcessor(getEnvAsProperties()));
377 processors.add(new AddPropertiesProcessor(System.getProperties()));
378 return processors;
379 default:
380 throw new IllegalStateException(mode + " is unknown");
381 }
382 }
383
384
385
386
387 public static final Properties convert(Map<String, String> map) {
388 Properties props = new Properties();
389 for (String key : map.keySet()) {
390 String value = map.get(key);
391 props.setProperty(key, value);
392 }
393 return props;
394 }
395
396
397
398
399 public static final Properties duplicate(Properties properties) {
400 Properties newProperties = new Properties();
401 newProperties.putAll(properties);
402 return newProperties;
403 }
404
405
406
407
408 public static Properties getEnvAsProperties() {
409 return getEnvAsProperties(ENV_PREFIX);
410 }
411
412
413
414
415 public static Properties getEnvAsProperties(String prefix) {
416 Properties properties = convert(System.getenv());
417 return getPrefixedProperties(properties, prefix);
418 }
419
420
421
422
423 public static final boolean isXml(String location) {
424 return StringUtils.endsWithIgnoreCase(location, XML_EXTENSION);
425 }
426
427
428
429
430 public static final Properties load(String location) {
431 return load(location, null);
432 }
433
434
435
436
437 public static final Properties load(String location, String encoding) {
438 InputStream in = null;
439 Reader reader = null;
440 try {
441 Properties properties = new Properties();
442 boolean xml = isXml(location);
443 if (xml) {
444 in = LocationUtils.getInputStream(location);
445 logger.info("Loading XML properties - [{}]", location);
446 properties.loadFromXML(in);
447 } else {
448 logger.info("Loading properties - [{}] encoding={}", location, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING));
449 reader = LocationUtils.getBufferedReader(location, encoding);
450 properties.load(reader);
451 }
452 return properties;
453 } catch (IOException e) {
454 throw new IllegalStateException("Unexpected IO error", e);
455 } finally {
456 IOUtils.closeQuietly(in);
457 IOUtils.closeQuietly(reader);
458 }
459 }
460
461
462
463
464
465 public static final Properties getPrefixedProperties(Properties properties, String prefix) {
466 if (StringUtils.isBlank(prefix)) {
467 return duplicate(properties);
468 }
469 Properties newProperties = new Properties();
470 for (String key : properties.stringPropertyNames()) {
471 String value = properties.getProperty(key);
472 String newKey = StringUtils.startsWith(key, prefix + ".") ? key : prefix + "." + key;
473 newProperties.setProperty(newKey, value);
474 }
475 return newProperties;
476 }
477
478
479
480
481 public static final Properties reformatKeysAsEnvVars(Properties properties) {
482 Properties newProperties = new Properties();
483 for (String key : properties.stringPropertyNames()) {
484 String value = properties.getProperty(key);
485 String newKey = StringUtils.upperCase(StringUtils.replace(key, ".", "-"));
486 newProperties.setProperty(newKey, value);
487 }
488 return newProperties;
489 }
490
491
492
493
494
495 public static final void addOrOverrideProperty(Properties properties, String key, String newValue, Mode propertyOverwriteMode) {
496 String oldValue = properties.getProperty(key);
497 boolean newEqualsOld = StringUtils.equals(newValue, oldValue);
498 if (newEqualsOld) {
499
500 return;
501 }
502 boolean overwrite = !StringUtils.isBlank(oldValue);
503 if (overwrite) {
504
505
506 ModeUtils.validate(propertyOverwriteMode, "Overriding [{}]", key, "Override of existing property [" + key + "] is not allowed.");
507 } else {
508
509 logger.debug("Adding property {}=[{}]", key, Str.flatten(newValue));
510 }
511 properties.setProperty(key, newValue);
512 }
513
514 private static final String getDefaultComment(String encoding, boolean xml) {
515 if (encoding == null) {
516 if (xml) {
517
518 return "encoding.default=" + DEFAULT_XML_ENCODING;
519 } else {
520
521 return "encoding.default=" + DEFAULT_ENCODING;
522 }
523 } else {
524 return "encoding.specified=" + encoding;
525 }
526 }
527
528 private static final String getComment(String comment, String encoding, boolean xml) {
529 if (StringUtils.isBlank(comment)) {
530 return getDefaultComment(encoding, xml);
531 } else {
532 return comment + "\n#" + getDefaultComment(encoding, xml);
533 }
534 }
535
536
537
538
539 private static final SortedProperties getSortedProperties(Properties properties) {
540 SortedProperties sp = new PropertyUtils().new SortedProperties();
541 sp.putAll(properties);
542 return sp;
543 }
544
545
546
547
548
549 private class SortedProperties extends Properties {
550
551 private static final long serialVersionUID = 1330825236411537386L;
552
553
554
555
556 @Override
557 public Set<Object> keySet() {
558 return Collections.unmodifiableSet(new TreeSet<Object>(super.keySet()));
559 }
560
561
562
563
564 @Override
565 public synchronized Enumeration<Object> keys() {
566 return Collections.enumeration(new TreeSet<Object>(super.keySet()));
567 }
568 }
569
570 }