001/** 002 * Copyright 2010-2014 The Kuali Foundation 003 * 004 * Licensed under the Educational Community License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.opensource.org/licenses/ecl2.php 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.kuali.common.util; 017 018import java.io.File; 019import java.io.IOException; 020import java.io.InputStream; 021import java.io.OutputStream; 022import java.io.Reader; 023import java.io.Writer; 024import java.nio.charset.Charset; 025import java.util.ArrayList; 026import java.util.Arrays; 027import java.util.Collections; 028import java.util.Enumeration; 029import java.util.List; 030import java.util.Map; 031import java.util.Properties; 032import java.util.Set; 033import java.util.SortedMap; 034import java.util.TreeSet; 035 036import org.apache.commons.io.FileUtils; 037import org.apache.commons.io.IOUtils; 038import org.apache.commons.lang3.StringUtils; 039import org.jasypt.util.text.TextEncryptor; 040import org.kuali.common.util.properties.rice.RiceLoader; 041import org.kuali.common.util.property.Constants; 042import org.kuali.common.util.property.GlobalPropertiesMode; 043import org.kuali.common.util.property.ImmutableProperties; 044import org.kuali.common.util.property.PropertiesContext; 045import org.kuali.common.util.property.PropertyFormat; 046import org.kuali.common.util.property.processor.AddPropertiesProcessor; 047import org.kuali.common.util.property.processor.PropertyProcessor; 048import org.slf4j.Logger; 049import org.slf4j.LoggerFactory; 050import org.springframework.util.PropertyPlaceholderHelper; 051 052import com.google.common.base.Optional; 053import com.google.common.collect.Maps; 054 055/** 056 * Simplify handling of <code>Properties</code> especially as it relates to storing and loading. <code>Properties</code> can be loaded from any url Spring resource loading can 057 * understand. When storing and loading, locations ending in <code>.xml</code> are automatically handled using <code>storeToXML()</code> and <code>loadFromXML()</code>, 058 * respectively. <code>Properties</code> are always stored in sorted order with the <code>encoding</code> indicated via a comment. 059 */ 060public class PropertyUtils { 061 062 private static final Logger logger = LoggerFactory.getLogger(PropertyUtils.class); 063 064 public static final String ADDITIONAL_LOCATIONS = "properties.additional.locations"; 065 public static final String ADDITIONAL_LOCATIONS_ENCODING = ADDITIONAL_LOCATIONS + ".encoding"; 066 public static final Properties EMPTY = new ImmutableProperties(new Properties()); 067 068 private static final String XML_EXTENSION = ".xml"; 069 private static final PropertyPlaceholderHelper HELPER = new PropertyPlaceholderHelper("${", "}", ":", false); 070 public static final String ENV_PREFIX = "env"; 071 private static final String DEFAULT_ENCODING = Charset.defaultCharset().name(); 072 private static final String DEFAULT_XML_ENCODING = Encodings.UTF8; 073 074 /** 075 * If there is no value for <code>key</code> or the value is NULL or NONE, return Optional.absent(), otherwise return Optional.of(value) 076 */ 077 public static Optional<String> getOptionalString(Properties properties, String key) { 078 if (properties.getProperty(key) == null) { 079 return Optional.absent(); 080 } else { 081 return Optional.of(properties.getProperty(key)); 082 } 083 } 084 085 public static Optional<String> getString(Properties properties, String key, Optional<String> provided) { 086 Optional<String> value = getOptionalString(properties, key); 087 if (value.isPresent()) { 088 return value; 089 } else { 090 return provided; 091 } 092 } 093 094 /** 095 * If the properties passed in are already immutable, just return them, otherwise, return a new ImmutableProperties object 096 */ 097 public static Properties toImmutable(Properties properties) { 098 return (properties instanceof ImmutableProperties) ? properties : new ImmutableProperties(properties); 099 } 100 101 /** 102 * The list returned by this method is unmodifiable and contains only <code>ImmutableProperties</code> 103 */ 104 public static List<Properties> toImmutable(List<Properties> properties) { 105 List<Properties> immutables = new ArrayList<Properties>(); 106 for (Properties p : properties) { 107 immutables.add(toImmutable(p)); 108 } 109 return Collections.unmodifiableList(immutables); 110 } 111 112 /** 113 * Return true if the value for <code>key</code> evaluates to the string <code>true</code> (ignoring case). The properties passed in along with the system and environment 114 * variables are all inspected with the value for system or environment variables "winning" over the value from the properties passed in. 115 */ 116 public static boolean getGlobalBoolean(String key, Properties properties) { 117 String defaultValue = properties.getProperty(key); 118 String value = getGlobalProperty(key, defaultValue); 119 return Boolean.parseBoolean(value); 120 } 121 122 public static boolean getBoolean(String key, Properties properties, boolean defaultValue) { 123 String value = properties.getProperty(key); 124 if (value == null) { 125 return defaultValue; 126 } else { 127 return Boolean.parseBoolean(value); 128 } 129 } 130 131 /** 132 * Return true if both contain an identical set of string keys and values, or both are <code>null</code>, false otherwise. 133 */ 134 public static boolean equals(Properties one, Properties two) { 135 136 // Return true if they are the same object 137 if (one == two) { 138 return true; 139 } 140 141 // Return true if they are both null 142 if (one == null && two == null) { 143 return true; 144 } 145 146 // If we get here, both are not null (but one or the other might be) 147 148 // Return false if one is null but not the other 149 if (one == null || two == null) { 150 return false; 151 } 152 153 // If we get here, neither one is null 154 155 // Extract the string property keys 156 List<String> keys1 = getSortedKeys(one); 157 List<String> keys2 = getSortedKeys(two); 158 159 // If the sizes are different, return false 160 if (keys1.size() != keys2.size()) { 161 return false; 162 } 163 164 // If we get here, they have the same number of string property keys 165 166 // The sizes are the same, just pick one 167 int size = keys1.size(); 168 169 // Iterate through the keys comparing both the keys and values for equality 170 for (int i = 0; i < size; i++) { 171 172 // Extract the keys 173 String key1 = keys1.get(i); 174 String key2 = keys2.get(i); 175 176 // Compare the keys for equality (this works because the keys are in sorted order) 177 if (!StringUtils.equals(key1, key2)) { 178 return false; 179 } 180 181 // Extract the values 182 String val1 = one.getProperty(key1); 183 String val2 = two.getProperty(key2); 184 185 // Compare the values for equality 186 if (!StringUtils.equals(val1, val2)) { 187 return false; 188 } 189 } 190 191 // If we get here we know 3 things: 192 193 // 1 - Both have the exact same number of string based keys/values 194 // 2 - Both have an identical set of string based keys 195 // 3 - Both have the exact same string value for each string key 196 197 // This means they are equal, return true 198 return true; 199 200 } 201 202 public static String getProperty(Properties properties, String key, String defaultValue) { 203 String value = properties.getProperty(key); 204 if (StringUtils.isBlank(value)) { 205 return defaultValue; 206 } else { 207 return value; 208 } 209 } 210 211 public static boolean isEmpty(Properties properties) { 212 return properties == null || properties.size() == 0; 213 } 214 215 public static String getRiceXML(Properties properties) { 216 StringBuilder sb = new StringBuilder(); 217 sb.append("<config>\n"); 218 List<String> keys = getSortedKeys(properties); 219 for (String key : keys) { 220 String value = properties.getProperty(key); 221 // Convert to CDATA if the value contains characters that would blow up an XML parser 222 if (StringUtils.contains(value, "<") || StringUtils.contains(value, "&")) { 223 value = Str.cdata(value); 224 } 225 sb.append(" <param name=" + Str.quote(key) + ">"); 226 sb.append(value); 227 sb.append("</param>\n"); 228 } 229 sb.append("</config>\n"); 230 return sb.toString(); 231 } 232 233 public static String getRequiredResolvedProperty(Properties properties, String key) { 234 return getRequiredResolvedProperty(properties, key, null); 235 } 236 237 public static String getRequiredResolvedProperty(Properties properties, String key, String defaultValue) { 238 String value = properties.getProperty(key); 239 value = StringUtils.isBlank(value) ? defaultValue : value; 240 if (StringUtils.isBlank(value)) { 241 throw new IllegalArgumentException("[" + key + "] is not set"); 242 } else { 243 return HELPER.replacePlaceholders(value, properties); 244 } 245 } 246 247 /** 248 * Process the properties passed in so they are ready for use by a Spring context.<br> 249 * 250 * 1 - Override with system/environment properties<br> 251 * 2 - Decrypt any ENC(...) values<br> 252 * 3 - Resolve all property values throwing an exception if any are unresolvable.<br> 253 */ 254 public static void prepareContextProperties(Properties properties, String encoding) { 255 256 // Override with additional properties (if any) 257 properties.putAll(getAdditionalProperties(properties, encoding)); 258 259 // Override with system/environment properties 260 properties.putAll(getGlobalProperties()); 261 262 // Are we decrypting property values? 263 decrypt(properties); 264 265 // Are we resolving placeholders 266 resolve(properties); 267 } 268 269 /** 270 * Process the properties passed in so they are ready for use by a Spring context.<br> 271 * 272 * 1 - Override with system/environment properties<br> 273 * 2 - Decrypt any ENC(...) values<br> 274 * 3 - Resolve all property values throwing an exception if any are unresolvable.<br> 275 */ 276 public static void prepareContextProperties(Properties properties) { 277 prepareContextProperties(properties, null); 278 } 279 280 public static void resolve(Properties properties, PropertyPlaceholderHelper helper) { 281 List<String> keys = getSortedKeys(properties); 282 for (String key : keys) { 283 String original = properties.getProperty(key); 284 String resolved = helper.replacePlaceholders(original, properties); 285 if (!StringUtils.equals(original, resolved)) { 286 logger.debug("Resolved [{}]", key); 287 properties.setProperty(key, resolved); 288 } 289 } 290 } 291 292 public static void removeSystemProperty(String key) { 293 if (System.getProperty(key) != null) { 294 logger.info("Removing system property [{}]", key); 295 System.getProperties().remove(key); 296 } 297 } 298 299 public static void removeSystemProperties(List<String> keys) { 300 for (String key : keys) { 301 removeSystemProperty(key); 302 } 303 } 304 305 @Deprecated 306 public static void resolve(Properties properties) { 307 // Are we resolving placeholders? 308 boolean resolve = new Boolean(getRequiredResolvedProperty(properties, "properties.resolve", "true")); 309 if (resolve) { 310 org.kuali.common.util.property.processor.ResolvePlaceholdersProcessor rpp = new org.kuali.common.util.property.processor.ResolvePlaceholdersProcessor(); 311 rpp.setHelper(HELPER); 312 rpp.process(properties); 313 } 314 } 315 316 @Deprecated 317 public static void decrypt(Properties properties) { 318 // Are we decrypting property values? 319 boolean decrypt = Boolean.parseBoolean(getRequiredResolvedProperty(properties, "properties.decrypt", "false")); 320 if (decrypt) { 321 // If they asked to decrypt, a password is required 322 String password = getRequiredResolvedProperty(properties, "properties.enc.password"); 323 324 // Strength is optional (defaults to BASIC) 325 String defaultStrength = org.kuali.common.util.enc.EncStrength.BASIC.name(); 326 String strength = getRequiredResolvedProperty(properties, "properties.enc.strength", defaultStrength); 327 org.kuali.common.util.enc.EncStrength es = org.kuali.common.util.enc.EncStrength.valueOf(strength); 328 TextEncryptor decryptor = org.kuali.common.util.enc.EncUtils.getTextEncryptor(password, es); 329 decrypt(properties, decryptor); 330 } 331 } 332 333 public static Properties getAdditionalProperties(Properties properties) { 334 return getAdditionalProperties(properties, null); 335 } 336 337 public static Properties getAdditionalProperties(Properties properties, String encoding) { 338 String csv = properties.getProperty(ADDITIONAL_LOCATIONS); 339 if (StringUtils.isBlank(csv)) { 340 return new Properties(); 341 } 342 if (StringUtils.isBlank(encoding)) { 343 encoding = properties.getProperty(ADDITIONAL_LOCATIONS_ENCODING, DEFAULT_XML_ENCODING); 344 } 345 List<String> locations = CollectionUtils.getTrimmedListFromCSV(csv); 346 PropertiesContext context = new PropertiesContext(locations, encoding); 347 return load(context); 348 } 349 350 public static void appendToOrSetProperty(Properties properties, String key, String value) { 351 Assert.hasText(value); 352 String existingValue = properties.getProperty(key); 353 if (existingValue == null) { 354 existingValue = ""; 355 } 356 String newValue = existingValue + value; 357 properties.setProperty(key, newValue); 358 } 359 360 @Deprecated 361 public static Properties load(List<org.kuali.common.util.property.ProjectProperties> pps) { 362 363 // Create some storage for the Properties object we will be returning 364 Properties properties = new Properties(); 365 366 // Cycle through the list of project properties, loading them as we go 367 for (org.kuali.common.util.property.ProjectProperties pp : pps) { 368 369 logger.debug("oracle.dba.url.1={}", properties.getProperty("oracle.dba.url")); 370 371 // Extract the properties context object 372 PropertiesContext ctx = pp.getPropertiesContext(); 373 374 // Retain the original properties object from the context 375 Properties original = PropertyUtils.duplicate(PropertyUtils.toEmpty(ctx.getProperties())); 376 377 // Override any existing property values with properties stored directly on the context 378 Properties combined = PropertyUtils.combine(properties, ctx.getProperties()); 379 380 // Store the combined properties on the context itself 381 ctx.setProperties(combined); 382 383 // Load properties as dictated by the context 384 Properties loaded = load(ctx); 385 386 logger.debug("oracle.dba.url.2={}", loaded.getProperty("oracle.dba.url")); 387 388 // Override any existing property values with those we just loaded 389 properties.putAll(loaded); 390 391 // Override any existing property values with the properties that were stored directly on the context 392 properties.putAll(original); 393 394 } 395 396 // Return the property values we now have 397 return properties; 398 } 399 400 public static Properties load(PropertiesContext context) { 401 // If there are no locations specified, add the properties supplied directly on the context (if there are any) 402 if (CollectionUtils.isEmpty(context.getLocations())) { 403 return PropertyUtils.toEmpty(context.getProperties()); 404 } 405 406 // Make sure we are configured correctly 407 Assert.notNull(context.getHelper(), "helper is null"); 408 Assert.notNull(context.getLocations(), "locations are null"); 409 Assert.notNull(context.getEncoding(), "encoding is null"); 410 Assert.notNull(context.getMissingLocationsMode(), "missingLocationsMode is null"); 411 412 // Get system/environment properties 413 Properties global = PropertyUtils.getGlobalProperties(); 414 415 // Convert null to an empty properties object (if necessary) 416 context.setProperties(PropertyUtils.toEmpty(context.getProperties())); 417 418 // Create new storage for the properties we are loading 419 Properties result = new Properties(); 420 421 // Add in any properties stored directly on the context itself (these get overridden by properties loaded elsewhere) 422 result.putAll(context.getProperties()); 423 424 // Cycle through the locations, loading and storing properties as we go 425 for (String location : context.getLocations()) { 426 427 // Get a combined Properties object capable of resolving any placeholders that exist in the property location strings 428 Properties resolverProperties = PropertyUtils.combine(context.getProperties(), result, global); 429 430 // Make sure we have a fully resolved location to load Properties from 431 String resolvedLocation = context.getHelper().replacePlaceholders(location, resolverProperties); 432 433 // If the location exists, load properties from it 434 if (LocationUtils.exists(resolvedLocation)) { 435 436 // Load this set of Properties 437 Properties properties = PropertyUtils.load(resolvedLocation, context.getEncoding()); 438 439 // Add these properties to the result. This follows the traditional "last one in wins" strategy 440 result.putAll(properties); 441 } else { 442 443 // Handle missing locations (might be fine, may need to emit a logging statement, may need to error out) 444 ModeUtils.validate(context.getMissingLocationsMode(), "Non-existent location [" + resolvedLocation + "]"); 445 } 446 } 447 448 // Return the properties we loaded 449 return result; 450 } 451 452 /** 453 * Decrypt any encrypted property values. Encrypted values are surrounded by ENC(...), like: 454 * 455 * <pre> 456 * my.value = ENC(DGA$S24FaIO) 457 * </pre> 458 */ 459 public static void decrypt(Properties properties, TextEncryptor encryptor) { 460 decrypt(properties, encryptor, null, null); 461 } 462 463 /** 464 * Return a new <code>Properties</code> object (never null) containing only those properties whose values are encrypted. Encrypted values are surrounded by ENC(...), like: 465 * 466 * <pre> 467 * my.value = ENC(DGA$S24FaIO) 468 * </pre> 469 */ 470 public static Properties getEncryptedProperties(Properties properties) { 471 List<String> keys = getEncryptedKeys(properties); 472 Properties encrypted = new Properties(); 473 for (String key : keys) { 474 String value = properties.getProperty(key); 475 encrypted.setProperty(key, value); 476 } 477 return encrypted; 478 } 479 480 /** 481 * Return a list containing only those keys whose values are encrypted. Encrypted values are surrounded by ENC(...), like: 482 * 483 * <pre> 484 * my.value = ENC(DGA$S24FaIO) 485 * </pre> 486 * 487 * @deprecated 488 */ 489 @Deprecated 490 public static List<String> getEncryptedKeys(Properties properties) { 491 List<String> all = getSortedKeys(properties); 492 List<String> encrypted = new ArrayList<String>(); 493 for (String key : all) { 494 String value = properties.getProperty(key); 495 if (org.kuali.common.util.enc.EncUtils.isEncrypted(value)) { 496 encrypted.add(key); 497 } 498 } 499 return encrypted; 500 } 501 502 /** 503 * Decrypt any encrypted property values matching the <code>includes</code>, <code>excludes</code> patterns. Encrypted values are surrounded by ENC(...). 504 * 505 * <pre> 506 * my.value = ENC(DGA$S24FaIO) 507 * </pre> 508 * 509 * @deprecated 510 */ 511 @Deprecated 512 public static void decrypt(Properties properties, TextEncryptor encryptor, List<String> includes, List<String> excludes) { 513 List<String> keys = getSortedKeys(properties, includes, excludes); 514 for (String key : keys) { 515 String value = properties.getProperty(key); 516 if (org.kuali.common.util.enc.EncUtils.isEncrypted(value)) { 517 String decryptedValue = decryptPropertyValue(encryptor, value); 518 properties.setProperty(key, decryptedValue); 519 } 520 } 521 } 522 523 /** 524 * Return true if the value starts with <code>ENC(</code> and ends with <code>)</code>, false otherwise. 525 * 526 * @deprecated Use EncUtils.isEncrypted(value) instead 527 */ 528 @Deprecated 529 public static boolean isEncryptedPropertyValue(String value) { 530 return org.kuali.common.util.enc.EncUtils.isEncrypted(value); 531 } 532 533 /** 534 * <p> 535 * A trivial way to conceal property values. Can be reversed using <code>reveal()</code>. Do <b>NOT</b> use this method in an attempt to obscure sensitive data. The algorithm 536 * is completely trivial and exceedingly simple to reverse engineer. Not to mention, the <code>reveal()</code> method can reproduce the original string without requiring any 537 * secret knowledge. 538 * </p> 539 * 540 * <p> 541 * The use case here is to help prevent someone with otherwise mostly good intentions from altering a piece of information in a way they should not. This is <b>NOT</b> intended 542 * to defeat any serious attempt at discovering the original text. 543 * </p> 544 * 545 * <p> 546 * Think a hungry sales or marketing rep who stumbles across a config file with the entry <code>vending.machine.refill.day=wed</code> in it and tries to change that to 547 * <code>mon</code> in order to beat a case of the munchies. :) 548 * </p> 549 * 550 * <p> 551 * If the entry says <code>vending.machine.refill.day=cnc--jrq</code> instead of <code>vending.machine.refill.day=wed</code> they are far more likely to ask around before they 552 * change it <b>OR</b> just give up and head out to lunch instead. 553 * </p> 554 * 555 * @see reveal 556 */ 557 public static void conceal(Properties properties) { 558 List<String> keys = getSortedKeys(properties); 559 for (String key : keys) { 560 String value = properties.getProperty(key); 561 String concealed = Str.conceal(value); 562 properties.setProperty(key, concealed); 563 } 564 } 565 566 /** 567 * Reveal property values that were concealed by the <code>conceal</code> method 568 * 569 * <pre> 570 * foo=cnc--one.onm -> foo=bar.baz 571 * </pre> 572 */ 573 public static void reveal(Properties properties) { 574 List<String> keys = getSortedKeys(properties); 575 for (String key : keys) { 576 String value = properties.getProperty(key); 577 String revealed = Str.reveal(value); 578 properties.setProperty(key, revealed); 579 } 580 } 581 582 /** 583 * Encrypt all of the property values. Encrypted values are surrounded by ENC(...). 584 * 585 * <pre> 586 * my.value = ENC(DGA$S24FaIO) 587 * </pre> 588 */ 589 public static void encrypt(Properties properties, TextEncryptor encryptor) { 590 encrypt(properties, encryptor, null, null); 591 } 592 593 /** 594 * Encrypt properties as dictated by <code>includes</code> and <code>excludes</code>. Encrypted values are surrounded by ENC(...). 595 * 596 * <pre> 597 * my.value = ENC(DGA$S24FaIO) 598 * </pre> 599 */ 600 public static void encrypt(Properties properties, TextEncryptor encryptor, List<String> includes, List<String> excludes) { 601 List<String> keys = getSortedKeys(properties, includes, excludes); 602 for (String key : keys) { 603 String originalValue = properties.getProperty(key); 604 String encryptedValue = encryptPropertyValue(encryptor, originalValue); 605 properties.setProperty(key, encryptedValue); 606 } 607 } 608 609 /** 610 * Return the decrypted version of the property value. Encrypted values are surrounded by ENC(...). 611 * 612 * <pre> 613 * my.value = ENC(DGA$S24FaIO) 614 * </pre> 615 */ 616 public static String decryptPropertyValue(TextEncryptor encryptor, String value) { 617 String unwrapped = unwrapEncryptedValue(value); 618 619 // Return the decrypted value 620 return encryptor.decrypt(unwrapped); 621 } 622 623 /** 624 * Remove the leading <code>ENC(</code> prefix and the trailing <code>)</code> from the encrypted value passed in. 625 * 626 * <pre> 627 * ENC(DGA$S24FaIO) -> DGA$S24FaIO 628 * </pre> 629 * 630 * @deprecated Use EncUtils.unwrap(value) instead 631 */ 632 @Deprecated 633 public static String unwrapEncryptedValue(String encryptedValue) { 634 return org.kuali.common.util.enc.EncUtils.unwrap(encryptedValue); 635 } 636 637 /** 638 * Return the encrypted version of the property value. A value is considered "encrypted" when it appears surrounded by ENC(...). 639 * 640 * <pre> 641 * my.value = ENC(DGA$S24FaIO) 642 * </pre> 643 */ 644 public static String encryptPropertyValue(TextEncryptor encryptor, String value) { 645 String encryptedValue = encryptor.encrypt(value); 646 return wrapEncryptedPropertyValue(encryptedValue); 647 } 648 649 /** 650 * Return the value enclosed with ENC() 651 * 652 * <pre> 653 * DGA$S24FaIO -> ENC(DGA$S24FaIO) 654 * </pre> 655 * 656 * @deprecated Use EncUtils.wrap(value) instead 657 */ 658 @Deprecated 659 public static String wrapEncryptedPropertyValue(String encryptedValue) { 660 return org.kuali.common.util.enc.EncUtils.wrap(encryptedValue); 661 } 662 663 public static void overrideWithGlobalValues(Properties properties, GlobalPropertiesMode mode) { 664 List<String> keys = PropertyUtils.getSortedKeys(properties); 665 Properties global = PropertyUtils.getProperties(mode); 666 for (String key : keys) { 667 String globalValue = global.getProperty(key); 668 if (!StringUtils.isBlank(globalValue)) { 669 properties.setProperty(key, globalValue); 670 } 671 } 672 } 673 674 public static final Properties[] toArray(List<Properties> properties) { 675 return properties.toArray(new Properties[properties.size()]); 676 } 677 678 public static final Properties combine(Properties properties, List<Properties> list) { 679 List<Properties> newList = new ArrayList<Properties>(CollectionUtils.toEmptyList(list)); 680 newList.add(0, toEmpty(properties)); 681 return combine(newList); 682 } 683 684 public static final Properties combine(List<Properties> properties) { 685 Properties combined = new Properties(); 686 for (Properties p : properties) { 687 combined.putAll(toEmpty(p)); 688 } 689 return combined; 690 } 691 692 public static final Properties combine(Properties... properties) { 693 return combine(Arrays.asList(properties)); 694 } 695 696 public static final void process(Properties properties, PropertyProcessor processor) { 697 process(properties, Collections.singletonList(processor)); 698 } 699 700 public static final void process(Properties properties, List<PropertyProcessor> processors) { 701 executeProcessors(properties, processors); 702 } 703 704 public static final void executeProcessors(Properties properties, List<PropertyProcessor> processors) { 705 for (PropertyProcessor processor : CollectionUtils.toEmptyList(processors)) { 706 processor.process(properties); 707 } 708 } 709 710 public static final Properties toEmpty(Properties properties) { 711 return properties == null ? new Properties() : properties; 712 } 713 714 public static final boolean isSingleUnresolvedPlaceholder(String string) { 715 return isSingleUnresolvedPlaceholder(string, Constants.DEFAULT_PLACEHOLDER_PREFIX, Constants.DEFAULT_PLACEHOLDER_SUFFIX); 716 } 717 718 public static final boolean isSingleUnresolvedPlaceholder(String string, String prefix, String suffix) { 719 int prefixMatches = StringUtils.countMatches(string, prefix); 720 int suffixMatches = StringUtils.countMatches(string, suffix); 721 boolean startsWith = StringUtils.startsWith(string, prefix); 722 boolean endsWith = StringUtils.endsWith(string, suffix); 723 return prefixMatches == 1 && suffixMatches == 1 && startsWith && endsWith; 724 } 725 726 public static final boolean containsUnresolvedPlaceholder(String string) { 727 return containsUnresolvedPlaceholder(string, Constants.DEFAULT_PLACEHOLDER_PREFIX, Constants.DEFAULT_PLACEHOLDER_SUFFIX); 728 } 729 730 public static final boolean containsUnresolvedPlaceholder(String string, String prefix, String suffix) { 731 int beginIndex = StringUtils.indexOf(string, prefix); 732 if (beginIndex == -1) { 733 return false; 734 } 735 return StringUtils.indexOf(string, suffix) != -1; 736 } 737 738 /** 739 * Return a new <code>Properties</code> object containing only those properties where the resolved value is different from the original value. Using global properties to 740 * perform property resolution as indicated by <code>Constants.DEFAULT_GLOBAL_PROPERTIES_MODE</code> 741 */ 742 public static final Properties getResolvedProperties(Properties properties) { 743 return getResolvedProperties(properties, Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE); 744 } 745 746 /** 747 * Return a new <code>Properties</code> object containing only those properties where the resolved value is different from the original value. Using global properties to 748 * perform property resolution as indicated by <code>globalPropertiesMode</code> 749 */ 750 public static final Properties getResolvedProperties(Properties properties, GlobalPropertiesMode globalPropertiesMode) { 751 return getResolvedProperties(properties, Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER, globalPropertiesMode); 752 } 753 754 /** 755 * Return a new <code>Properties</code> object containing only those properties where the resolved value is different from the original value. Using global properties to 756 * perform property resolution as indicated by <code>Constants.DEFAULT_GLOBAL_PROPERTIES_MODE</code> 757 */ 758 public static final Properties getResolvedProperties(Properties properties, PropertyPlaceholderHelper helper) { 759 return getResolvedProperties(properties, helper, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE); 760 } 761 762 /** 763 * Return a new <code>Properties</code> object containing only those properties where the resolved value is different from the original value. Using global properties to 764 * perform property resolution as indicated by <code>globalPropertiesMode</code> 765 */ 766 public static final Properties getResolvedProperties(Properties properties, PropertyPlaceholderHelper helper, GlobalPropertiesMode globalPropertiesMode) { 767 Properties global = getProperties(properties, globalPropertiesMode); 768 List<String> keys = getSortedKeys(properties); 769 Properties newProperties = new Properties(); 770 for (String key : keys) { 771 String originalValue = properties.getProperty(key); 772 String resolvedValue = helper.replacePlaceholders(originalValue, global); 773 if (!resolvedValue.equals(originalValue)) { 774 logger.debug("Resolved property '" + key + "' [{}] -> [{}]", Str.flatten(originalValue), Str.flatten(resolvedValue)); 775 newProperties.setProperty(key, resolvedValue); 776 } 777 } 778 return newProperties; 779 } 780 781 /** 782 * Return the property values from <code>keys</code> 783 */ 784 public static final List<String> getValues(Properties properties, List<String> keys) { 785 List<String> values = new ArrayList<String>(); 786 for (String key : keys) { 787 values.add(properties.getProperty(key)); 788 } 789 return values; 790 } 791 792 /** 793 * Return a sorted <code>List</code> of keys from <code>properties</code> that end with <code>suffix</code>. 794 */ 795 public static final List<String> getEndsWithKeys(Properties properties, String suffix) { 796 List<String> keys = getSortedKeys(properties); 797 List<String> matches = new ArrayList<String>(); 798 for (String key : keys) { 799 if (StringUtils.endsWith(key, suffix)) { 800 matches.add(key); 801 } 802 } 803 return matches; 804 } 805 806 /** 807 * Alter the <code>properties</code> passed in to contain only the desired property values. <code>includes</code> and <code>excludes</code> are comma separated values. 808 */ 809 public static final void trim(Properties properties, String includesCSV, String excludesCSV) { 810 List<String> includes = CollectionUtils.getTrimmedListFromCSV(includesCSV); 811 List<String> excludes = CollectionUtils.getTrimmedListFromCSV(excludesCSV); 812 trim(properties, includes, excludes); 813 } 814 815 /** 816 * Alter the <code>properties</code> passed in to contain only the desired property values. 817 */ 818 public static final void trim(Properties properties, List<String> includes, List<String> excludes) { 819 List<String> keys = getSortedKeys(properties); 820 for (String key : keys) { 821 if (!include(key, includes, excludes)) { 822 logger.debug("Removing [{}]", key); 823 properties.remove(key); 824 } 825 } 826 } 827 828 /** 829 * Return true if <code>value</code> should be included, false otherwise.<br> 830 * If <code>excludes</code> is not empty and matches <code>value</code> return false.<br> 831 * If <code>value</code> has not been explicitly excluded, check the <code>includes</code> list.<br> 832 * If <code>includes</code> is empty return true.<br> 833 * If <code>includes</code> is not empty, return true if, and only if, <code>value</code> matches a pattern from the <code>includes</code> list.<br> 834 * A single wildcard <code>*</code> is supported for <code>includes</code> and <code>excludes</code>.<br> 835 */ 836 public static final boolean include(String value, List<String> includes, List<String> excludes) { 837 if (isSingleWildcardMatch(value, excludes)) { 838 // No point incurring the overhead of matching an include pattern 839 return false; 840 } else { 841 // If includes is empty always return true 842 return CollectionUtils.isEmpty(includes) || isSingleWildcardMatch(value, includes); 843 } 844 } 845 846 public static final boolean isSingleWildcardMatch(String s, List<String> patterns) { 847 for (String pattern : CollectionUtils.toEmptyList(patterns)) { 848 if (isSingleWildcardMatch(s, pattern)) { 849 return true; 850 } 851 } 852 return false; 853 } 854 855 /** 856 * Match {@code value} against {@code pattern} where {@code pattern} can optionally contain a single wildcard {@code *}. If both are {@code null} return {@code true}. If one of 857 * {@code value} or {@code pattern} is {@code null} but the other isn't, return {@code false}. Any {@code pattern} containing more than a single wildcard throws 858 * {@code IllegalArgumentException}. 859 * 860 * <pre> 861 * PropertyUtils.isSingleWildcardMatch(null, null) = true 862 * PropertyUtils.isSingleWildcardMatch(null, *) = false 863 * PropertyUtils.isSingleWildcardMatch(*, null) = false 864 * PropertyUtils.isSingleWildcardMatch(*, "*") = true 865 * PropertyUtils.isSingleWildcardMatch("abcdef", "bcd") = false 866 * PropertyUtils.isSingleWildcardMatch("abcdef", "*def") = true 867 * PropertyUtils.isSingleWildcardMatch("abcdef", "abc*") = true 868 * PropertyUtils.isSingleWildcardMatch("abcdef", "ab*ef") = true 869 * PropertyUtils.isSingleWildcardMatch("abcdef", "abc*def") = true 870 * PropertyUtils.isSingleWildcardMatch(*, "**") = IllegalArgumentException 871 * </pre> 872 */ 873 public static final boolean isSingleWildcardMatch(String value, String pattern) { 874 if (value == null && pattern == null) { 875 // both are null 876 return true; 877 } else if (value == null || pattern == null) { 878 // One is null, but not the other 879 return false; 880 } else if (pattern.equals(Constants.WILDCARD)) { 881 // Neither one is null and pattern is the unqualified wildcard. 882 // Value is irrelevant, always return true 883 return true; 884 } else if (StringUtils.countMatches(pattern, Constants.WILDCARD) > 1) { 885 // More than one wildcard in the pattern is not supported 886 throw new IllegalArgumentException("Pattern [" + pattern + "] is not supported. Only one wildcard is allowed in the pattern"); 887 } else if (!StringUtils.contains(pattern, Constants.WILDCARD)) { 888 // Neither one is null and there is no wildcard in the pattern. They must match exactly 889 return StringUtils.equals(value, pattern); 890 } else { 891 // The pattern contains 1 (and only 1) wildcard 892 // Make sure value starts with the characters to the left of the wildcard 893 // and ends with the characters to the right of the wildcard 894 int pos = StringUtils.indexOf(pattern, Constants.WILDCARD); 895 int suffixPos = pos + Constants.WILDCARD.length(); 896 boolean nullPrefix = pos == 0; 897 boolean nullSuffix = suffixPos >= pattern.length(); 898 String prefix = nullPrefix ? null : StringUtils.substring(pattern, 0, pos); 899 String suffix = nullSuffix ? null : StringUtils.substring(pattern, suffixPos); 900 boolean prefixMatch = nullPrefix || StringUtils.startsWith(value, prefix); 901 boolean suffixMatch = nullSuffix || StringUtils.endsWith(value, suffix); 902 return prefixMatch && suffixMatch; 903 } 904 } 905 906 /** 907 * Return property keys that should be included as a sorted list. 908 */ 909 public static final Properties getProperties(Properties properties, String include, String exclude) { 910 List<String> keys = getSortedKeys(properties, include, exclude); 911 Properties newProperties = new Properties(); 912 for (String key : keys) { 913 String value = properties.getProperty(key); 914 newProperties.setProperty(key, value); 915 } 916 return newProperties; 917 } 918 919 /** 920 * Return property keys that should be included as a sorted list. 921 */ 922 public static final List<String> getSortedKeys(Properties properties, String include, String exclude) { 923 return getSortedKeys(properties, CollectionUtils.toEmptyList(include), CollectionUtils.toEmptyList(exclude)); 924 } 925 926 /** 927 * Return property keys that should be included as a sorted list. 928 */ 929 public static final List<String> getSortedKeys(Properties properties, List<String> includes, List<String> excludes) { 930 List<String> keys = getSortedKeys(properties); 931 List<String> includedKeys = new ArrayList<String>(); 932 for (String key : keys) { 933 if (include(key, includes, excludes)) { 934 includedKeys.add(key); 935 } 936 } 937 return includedKeys; 938 } 939 940 /** 941 * Return a sorted <code>List</code> of keys from <code>properties</code> that start with <code>prefix</code> 942 */ 943 public static final List<String> getStartsWithKeys(Properties properties, String prefix) { 944 List<String> keys = getSortedKeys(properties); 945 List<String> matches = new ArrayList<String>(); 946 for (String key : keys) { 947 if (StringUtils.startsWith(key, prefix)) { 948 matches.add(key); 949 } 950 } 951 return matches; 952 } 953 954 /** 955 * Return the property keys as a sorted list. 956 */ 957 public static final List<String> getSortedKeys(Properties properties) { 958 List<String> keys = new ArrayList<String>(properties.stringPropertyNames()); 959 Collections.sort(keys); 960 return keys; 961 } 962 963 public static final String toString(Properties properties) { 964 List<String> keys = getSortedKeys(properties); 965 StringBuilder sb = new StringBuilder(); 966 for (String key : keys) { 967 String value = Str.flatten(properties.getProperty(key)); 968 sb.append(key + "=" + value + "\n"); 969 } 970 return sb.toString(); 971 } 972 973 public static final void info(Properties properties) { 974 properties = toEmpty(properties); 975 logger.info("--- Displaying {} properties ---\n\n{}", properties.size(), toString(properties)); 976 } 977 978 public static final void debug(Properties properties) { 979 properties = toEmpty(properties); 980 logger.debug("--- Displaying {} properties ---\n\n{}", properties.size(), toString(properties)); 981 } 982 983 /** 984 * Store the properties to the indicated file using the platform default encoding. 985 */ 986 public static final void store(Properties properties, File file) { 987 store(properties, file, null); 988 } 989 990 /** 991 * Store the properties to the indicated file using the platform default encoding. 992 */ 993 public static final void storeSilently(Properties properties, File file) { 994 store(properties, file, null, null, true); 995 } 996 997 /** 998 * Store the properties to the indicated file using the indicated encoding. 999 */ 1000 public static final void store(Properties properties, File file, String encoding) { 1001 store(properties, file, encoding, null); 1002 } 1003 1004 /** 1005 * Store the properties to the indicated file using the indicated encoding with the indicated comment appearing at the top of the file. 1006 */ 1007 public static final void store(Properties properties, File file, String encoding, String comment) { 1008 store(properties, file, encoding, comment, false); 1009 } 1010 1011 /** 1012 * Store the properties to the indicated file using the indicated encoding with the indicated comment appearing at the top of the file. 1013 */ 1014 public static final void store(Properties properties, File file, String encoding, String comment, boolean silent) { 1015 OutputStream out = null; 1016 Writer writer = null; 1017 try { 1018 out = FileUtils.openOutputStream(file); 1019 String path = file.getCanonicalPath(); 1020 boolean xml = isXml(path); 1021 Properties sorted = getSortedProperties(properties); 1022 comment = getComment(encoding, comment, xml); 1023 if (xml) { 1024 if (!silent) { 1025 logger.info("Storing XML properties - [{}] encoding={}", path, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING)); 1026 } 1027 if (encoding == null) { 1028 sorted.storeToXML(out, comment); 1029 } else { 1030 sorted.storeToXML(out, comment, encoding); 1031 } 1032 } else { 1033 writer = LocationUtils.getWriter(out, encoding); 1034 if (!silent) { 1035 logger.info("Storing properties - [{}] encoding={}", path, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING)); 1036 } 1037 sorted.store(writer, comment); 1038 } 1039 } catch (IOException e) { 1040 throw new IllegalStateException("Unexpected IO error", e); 1041 } finally { 1042 IOUtils.closeQuietly(writer); 1043 IOUtils.closeQuietly(out); 1044 } 1045 } 1046 1047 /** 1048 * Examine both system properties and environment variables to get a value for <code>key</code>. Return <code>null</code> if nothing is found. 1049 * 1050 * <pre> 1051 * foo.bar -> System property check for "foo.bar" 1052 * foo.bar -> Environment check for "FOO_BAR" 1053 * </pre> 1054 */ 1055 public static final String getGlobalProperty(String key) { 1056 return getGlobalProperty(key, null); 1057 } 1058 1059 /** 1060 * Examine both system properties and environment variables to get a value for <code>key</code>. Return <code>defaultValue</code> if nothing is found 1061 * 1062 * <pre> 1063 * foo.bar -> System property check for "foo.bar" 1064 * foo.bar -> Environment check for "FOO_BAR" 1065 * </pre> 1066 */ 1067 public static final String getGlobalProperty(String key, String defaultValue) { 1068 Assert.noNullsWithMsg("key is required", key); 1069 1070 // Check to see if there is a system property for this key 1071 String systemValue = System.getProperty(key); 1072 1073 // If so, we are done 1074 if (systemValue != null) { 1075 return systemValue; 1076 } 1077 1078 // Reformat the key as an environment variable key 1079 String environmentVariable = convertToEnvironmentVariable(key); 1080 1081 // Check to see if we have a match for an environment variable 1082 String environmentValue = System.getenv(environmentVariable); 1083 1084 if (environmentValue != null) { 1085 // If so, return the value of the environment variable 1086 return environmentValue; 1087 } else { 1088 // If not, return the default value 1089 return defaultValue; 1090 } 1091 } 1092 1093 /** 1094 * Return a new properties object containing the properties from <code>getEnvAsProperties()</code> and <code>System.getProperties()</code>. Properties from 1095 * <code>System.getProperties()</code> override properties from <code>getEnvAsProperties</code> if there are duplicates. 1096 */ 1097 public static final Properties getGlobalProperties() { 1098 return getProperties(Constants.DEFAULT_GLOBAL_PROPERTIES_MODE); 1099 } 1100 1101 /** 1102 * Return a new properties object containing the properties passed in, plus any properties returned by <code>getEnvAsProperties()</code> and <code>System.getProperties()</code> 1103 * . Properties from <code>getEnvAsProperties()</code> override <code>properties</code> and properties from <code>System.getProperties()</code> override everything. 1104 */ 1105 public static final Properties getGlobalProperties(Properties properties) { 1106 return getProperties(properties, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE); 1107 } 1108 1109 /** 1110 * Return a new properties object containing the properties passed in, plus any global properties as requested. If <code>mode</code> is <code>NONE</code> the new properties are 1111 * a duplicate of the properties passed in. If <code>mode</code> is <code>ENVIRONMENT</code> the new properties contain the original properties plus any properties returned by 1112 * <code>getEnvProperties()</code>. If <code>mode</code> is <code>SYSTEM</code> the new properties contain the original properties plus <code>System.getProperties()</code>. If 1113 * <code>mode</code> is <code>BOTH</code> the new properties contain the original properties plus <code>getEnvProperties()</code> and <code>System.getProperties()</code>. 1114 */ 1115 public static final Properties getProperties(Properties properties, GlobalPropertiesMode mode) { 1116 Properties newProperties = duplicate(properties); 1117 List<PropertyProcessor> modifiers = getPropertyProcessors(mode); 1118 for (PropertyProcessor modifier : modifiers) { 1119 modifier.process(newProperties); 1120 } 1121 return newProperties; 1122 } 1123 1124 /** 1125 * Return a new properties object containing global properties as requested. If <code>mode</code> is <code>NONE</code> the new properties are empty. If <code>mode</code> is 1126 * <code>ENVIRONMENT</code> the new properties contain the properties returned by <code>getEnvProperties()</code>. If <code>mode</code> is <code>SYSTEM</code> the new 1127 * properties contain <code>System.getProperties()</code>. If <code>mode</code> is <code>BOTH</code> the new properties contain <code>getEnvProperties</code> plus 1128 * <code>System.getProperties()</code> with system properties overriding environment variables if the same case sensitive property key is supplied in both places. 1129 */ 1130 public static final Properties getProperties(GlobalPropertiesMode mode) { 1131 return getProperties(new Properties(), mode); 1132 } 1133 1134 /** 1135 * Search global properties to find a value for <code>key</code> according to the mode passed in. 1136 */ 1137 public static final String getProperty(String key, GlobalPropertiesMode mode) { 1138 return getProperty(key, new Properties(), mode); 1139 } 1140 1141 /** 1142 * Search <code>properties</code> plus global properties to find a value for <code>key</code> according to the mode passed in. If the property is present in both, the value 1143 * from the global properties is returned. 1144 */ 1145 public static final String getProperty(String key, Properties properties, GlobalPropertiesMode mode) { 1146 return getProperties(properties, mode).getProperty(key); 1147 } 1148 1149 /** 1150 * Return modifiers that add environment variables, system properties, or both, according to the mode passed in. 1151 */ 1152 public static final List<PropertyProcessor> getPropertyProcessors(GlobalPropertiesMode mode) { 1153 List<PropertyProcessor> processors = new ArrayList<PropertyProcessor>(); 1154 switch (mode) { 1155 case NONE: 1156 return processors; 1157 case ENVIRONMENT: 1158 processors.add(new AddPropertiesProcessor(getEnvAsProperties())); 1159 return processors; 1160 case SYSTEM: 1161 processors.add(new AddPropertiesProcessor(System.getProperties())); 1162 return processors; 1163 case BOTH: 1164 processors.add(new AddPropertiesProcessor(getEnvAsProperties())); 1165 processors.add(new AddPropertiesProcessor(System.getProperties())); 1166 return processors; 1167 default: 1168 throw new IllegalStateException(mode + " is unknown"); 1169 } 1170 } 1171 1172 /** 1173 * Convert the <code>Map</code> to a <code>Properties</code> object. 1174 */ 1175 public static final Properties convert(Map<String, String> map) { 1176 return convertToProperties(map); 1177 } 1178 1179 /** 1180 * Convert the <code>Map</code> to a <code>Properties</code> object. 1181 */ 1182 public static final Properties convertToProperties(Map<String, String> map) { 1183 Properties props = new Properties(); 1184 for (String key : map.keySet()) { 1185 props.setProperty(key, map.get(key)); 1186 } 1187 return props; 1188 } 1189 1190 /** 1191 * Convert the <code>Properties</code> to a <code>Map</code> object. 1192 */ 1193 public static Map<String, String> convert(Properties properties) { 1194 return newHashMap(properties); 1195 } 1196 1197 /** 1198 * Convert the {@code Properties} to a {@code Map<String,String>} object. 1199 */ 1200 public static Map<String, String> newHashMap(Properties properties) { 1201 Map<String, String> map = Maps.newHashMap(); 1202 for (String key : properties.stringPropertyNames()) { 1203 map.put(key, properties.getProperty(key)); 1204 } 1205 return map; 1206 } 1207 1208 /** 1209 * Convert the {@code Properties} to a {@code Map<String,String>} object. 1210 */ 1211 public static SortedMap<String, String> newTreeMap(Properties properties) { 1212 SortedMap<String, String> map = Maps.newTreeMap(); 1213 for (String key : properties.stringPropertyNames()) { 1214 map.put(key, properties.getProperty(key)); 1215 } 1216 return map; 1217 } 1218 1219 /** 1220 * Return a new properties object that duplicates the properties passed in. 1221 */ 1222 public static final Properties duplicate(Properties properties) { 1223 Properties newProperties = new Properties(); 1224 newProperties.putAll(properties); 1225 return newProperties; 1226 } 1227 1228 /** 1229 * Return a new properties object containing environment variables as properties prefixed with <code>env</code> 1230 */ 1231 public static Properties getEnvAsProperties() { 1232 return getEnvAsProperties(ENV_PREFIX); 1233 } 1234 1235 /** 1236 * Return a new properties object containing environment variables as properties prefixed with <code>prefix</code> 1237 */ 1238 public static Properties getEnvAsProperties(String prefix) { 1239 Properties properties = convert(System.getenv()); 1240 return getPrefixedProperties(properties, prefix); 1241 } 1242 1243 /** 1244 * Return true if, and only if, location ends with <code>.xml</code> (case insensitive). 1245 */ 1246 public static final boolean isXml(String location) { 1247 return StringUtils.endsWithIgnoreCase(location, XML_EXTENSION); 1248 } 1249 1250 /** 1251 * Return true if, and only if, location ends with <code>rice-properties.xml</code> (case insensitive). 1252 */ 1253 public static final boolean isRiceProperties(String location) { 1254 return StringUtils.endsWithIgnoreCase(location, Constants.RICE_PROPERTIES_SUFFIX); 1255 } 1256 1257 public static final Properties loadRiceProps(File file) { 1258 return RiceLoader.load(file); 1259 } 1260 1261 public static final Properties loadRiceProps(String location) { 1262 return RiceLoader.load(location); 1263 } 1264 1265 /** 1266 * Return a new <code>Properties</code> object loaded from <code>file</code> where the properties are stored in Rice XML style syntax 1267 * 1268 * @deprecated use loadRiceProps() instead 1269 */ 1270 @Deprecated 1271 public static final Properties loadRiceProperties(File file) { 1272 return loadRiceProperties(LocationUtils.getCanonicalPath(file)); 1273 } 1274 1275 /** 1276 * Return a new <code>Properties</code> object loaded from <code>location</code> where the properties are stored in Rice XML style syntax 1277 * 1278 * @deprecated use loadRiceProps() instead 1279 */ 1280 @Deprecated 1281 public static final Properties loadRiceProperties(String location) { 1282 logger.info("Loading Rice properties [{}] encoding={}", location, DEFAULT_XML_ENCODING); 1283 String contents = LocationUtils.toString(location, DEFAULT_XML_ENCODING); 1284 String config = StringUtils.substringBetween(contents, "<config>", "</config>"); 1285 String[] tokens = StringUtils.substringsBetween(config, "<param", "<param"); 1286 1287 Properties properties = new Properties(); 1288 for (String token : tokens) { 1289 String key = StringUtils.substringBetween(token, "name=\"", "\">"); 1290 validateRiceProperties(token, key); 1291 String value = StringUtils.substringBetween(token + "</param>", "\">", "</param>"); 1292 properties.setProperty(key, value); 1293 } 1294 return properties; 1295 } 1296 1297 /** 1298 * Make sure they are just loading simple properties and are not using any of the unsupported "features". Can't have a key named config.location, and can't use the system, 1299 * override, or random attributes. 1300 */ 1301 protected static final void validateRiceProperties(String token, String key) { 1302 if (StringUtils.equalsIgnoreCase("config.location", key)) { 1303 throw new IllegalArgumentException("config.location is not supported"); 1304 } 1305 if (StringUtils.contains(token, "override=\"")) { 1306 throw new IllegalArgumentException("override attribute is not supported"); 1307 } 1308 if (StringUtils.contains(token, "system=\"")) { 1309 throw new IllegalArgumentException("system attribute is not supported"); 1310 } 1311 if (StringUtils.contains(token, "random=\"")) { 1312 throw new IllegalArgumentException("random attribute is not supported"); 1313 } 1314 } 1315 1316 /** 1317 * Return a new <code>Properties</code> object loaded from <code>file</code>. 1318 */ 1319 public static final Properties load(File file) { 1320 return load(file, null); 1321 } 1322 1323 /** 1324 * Return a new <code>Properties</code> object loaded from <code>file</code>. 1325 */ 1326 public static final Properties loadSilently(File file) { 1327 return loadSilently(LocationUtils.getCanonicalPath(file)); 1328 } 1329 1330 /** 1331 * Return a new <code>Properties</code> object loaded from <code>file</code>. 1332 */ 1333 public static final Properties loadSilently(String location) { 1334 return load(location, null, PropertyFormat.NORMAL, true); 1335 } 1336 1337 /** 1338 * Return a new <code>Properties</code> object loaded from <code>file</code> using the given encoding. 1339 */ 1340 public static final Properties load(File file, String encoding) { 1341 String location = LocationUtils.getCanonicalPath(file); 1342 return load(location, encoding); 1343 } 1344 1345 /** 1346 * Return a new <code>Properties</code> object loaded from <code>location</code>. 1347 */ 1348 public static final Properties load(String location) { 1349 return load(location, null); 1350 } 1351 1352 /** 1353 * If location exists, return a new <code>Properties</code> object loaded from <code>location</code>, otherwise return a new <code>Properties</code> object 1354 */ 1355 public static final Properties loadOrCreateSilently(String location) { 1356 if (LocationUtils.exists(location)) { 1357 return load(location, null, PropertyFormat.NORMAL, true); 1358 } else { 1359 return new Properties(); 1360 } 1361 } 1362 1363 /** 1364 * Return a new <code>Properties</code> object loaded from <code>locations</code> using <code>encoding</code>. 1365 */ 1366 public static final Properties load(List<String> locations, String encoding) { 1367 Properties properties = new Properties(); 1368 for (String location : locations) { 1369 properties.putAll(load(location, encoding)); 1370 } 1371 return properties; 1372 } 1373 1374 /** 1375 * Return a new <code>Properties</code> object loaded from <code>location</code> using <code>encoding</code>. 1376 */ 1377 public static final Properties load(String location, String encoding) { 1378 return load(location, encoding, PropertyFormat.NORMAL); 1379 } 1380 1381 /** 1382 * Return a new <code>Properties</code> object loaded from <code>location</code> using <code>encoding</code>. 1383 */ 1384 public static final Properties load(String location, String encoding, PropertyFormat format) { 1385 return load(location, encoding, format, false); 1386 } 1387 1388 /** 1389 * Return a new <code>Properties</code> object loaded from <code>location</code> using <code>encoding</code>. 1390 */ 1391 public static final Properties load(String location, String encoding, PropertyFormat format, boolean silent) { 1392 InputStream in = null; 1393 Reader reader = null; 1394 try { 1395 Properties properties = new Properties(); 1396 boolean xml = isXml(location); 1397 boolean riceProperties = isRiceProperties(location); 1398 location = getCanonicalLocation(location); 1399 if (PropertyFormat.RICE.equals(format) || riceProperties) { 1400 properties = loadRiceProperties(location); 1401 } else if (xml) { 1402 in = LocationUtils.getInputStream(location); 1403 if (!silent) { 1404 logger.info("Loading XML properties - [{}]", location); 1405 } 1406 properties.loadFromXML(in); 1407 } else { 1408 if (!silent) { 1409 logger.info("Loading properties - [{}] encoding={}", location, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING)); 1410 } 1411 reader = LocationUtils.getBufferedReader(location, encoding); 1412 properties.load(reader); 1413 } 1414 return properties; 1415 } catch (IOException e) { 1416 throw new IllegalStateException("Unexpected IO error", e); 1417 } finally { 1418 IOUtils.closeQuietly(in); 1419 IOUtils.closeQuietly(reader); 1420 } 1421 } 1422 1423 protected static String getCanonicalLocation(String location) { 1424 if (LocationUtils.isExistingFile(location)) { 1425 return LocationUtils.getCanonicalPath(new File(location)); 1426 } else { 1427 return location; 1428 } 1429 } 1430 1431 /** 1432 * Return a new <code>Properties</code> object containing properties prefixed with <code>prefix</code>. If <code>prefix</code> is blank, the new properties object duplicates 1433 * the properties passed in. 1434 */ 1435 public static final Properties getPrefixedProperties(Properties properties, String prefix) { 1436 if (StringUtils.isBlank(prefix)) { 1437 return duplicate(properties); 1438 } 1439 Properties newProperties = new Properties(); 1440 for (String key : properties.stringPropertyNames()) { 1441 String value = properties.getProperty(key); 1442 String newKey = StringUtils.startsWith(key, prefix + ".") ? key : prefix + "." + key; 1443 newProperties.setProperty(newKey, value); 1444 } 1445 return newProperties; 1446 } 1447 1448 /** 1449 * Replace periods with an underscore and convert to uppercase 1450 * 1451 * <pre> 1452 * foo.bar -> FOO_BAR 1453 * </pre> 1454 */ 1455 public static final String convertToEnvironmentVariable(String key) { 1456 return StringUtils.upperCase(StringUtils.replace(key, ".", "_")); 1457 } 1458 1459 /** 1460 * Replace periods with an underscore, convert to uppercase, and prefix with <code>env</code> 1461 * 1462 * <pre> 1463 * foo.bar -> env.FOO_BAR 1464 * </pre> 1465 */ 1466 public static final String getEnvironmentVariableKey(String key) { 1467 return ENV_PREFIX + "." + convertToEnvironmentVariable(key); 1468 } 1469 1470 /** 1471 * Return a new properties object where the keys have been converted to upper case and periods have been replaced with an underscore. 1472 */ 1473 public static final Properties reformatKeysAsEnvVars(Properties properties) { 1474 Properties newProperties = new Properties(); 1475 for (String key : properties.stringPropertyNames()) { 1476 String value = properties.getProperty(key); 1477 String newKey = convertToEnvironmentVariable(key); 1478 newProperties.setProperty(newKey, value); 1479 } 1480 return newProperties; 1481 } 1482 1483 /** 1484 * Before setting the newValue, check to see if there is a conflict with an existing value. If there is no existing value, add the property. If there is a conflict, check 1485 * <code>propertyOverwriteMode</code> to make sure we have permission to override the value. 1486 */ 1487 public static final void addOrOverrideProperty(Properties properties, String key, String newValue, Mode propertyOverwriteMode) { 1488 addOrOverrideProperty(properties, key, newValue, propertyOverwriteMode, 0); 1489 } 1490 1491 public static final void addOrOverrideProperty(Properties properties, String key, String newValue, Mode overrideMode, int indent) { 1492 String oldValue = properties.getProperty(key); 1493 if (StringUtils.equals(newValue, oldValue)) { 1494 // Nothing to do! New value is the same as old value. 1495 return; 1496 } 1497 boolean overwrite = !StringUtils.isBlank(oldValue); 1498 1499 String logNewValue = newValue; 1500 String logOldValue = oldValue; 1501 1502 // TODO Yuck! Do something smarter here 1503 if (obscure(key)) { 1504 logNewValue = "*********"; 1505 logOldValue = "*********"; 1506 } 1507 1508 if (overwrite) { 1509 // This property already has a value, and it is different from the new value 1510 // Check to make sure we are allowed to override the old value before doing so 1511 Object[] args = new Object[] { StringUtils.repeat(" ", indent), key, Str.flatten(logNewValue), Str.flatten(logOldValue) }; 1512 ModeUtils.validate(overrideMode, "{}override [{}] -> [{}]", args, "Override of existing property [" + key + "] is not allowed."); 1513 } else { 1514 // There is no existing value for this key 1515 logger.debug("Adding [{}={}]", key, Str.flatten(logNewValue)); 1516 } 1517 properties.setProperty(key, newValue); 1518 } 1519 1520 protected static boolean obscure(String key) { 1521 if (StringUtils.containsIgnoreCase(key, "password")) { 1522 return true; 1523 } 1524 if (StringUtils.containsIgnoreCase(key, "secret")) { 1525 return true; 1526 } 1527 if (StringUtils.containsIgnoreCase(key, "private")) { 1528 return true; 1529 } 1530 return false; 1531 } 1532 1533 private static final String getDefaultComment(String encoding, boolean xml) { 1534 if (encoding == null) { 1535 if (xml) { 1536 // Java defaults XML properties files to UTF-8 if no encoding is provided 1537 return "encoding.default=" + DEFAULT_XML_ENCODING; 1538 } else { 1539 // For normal properties files the platform default encoding is used 1540 return "encoding.default=" + DEFAULT_ENCODING; 1541 } 1542 } else { 1543 return "encoding.specified=" + encoding; 1544 } 1545 } 1546 1547 private static final String getComment(String encoding, String comment, boolean xml) { 1548 if (StringUtils.isBlank(comment)) { 1549 return getDefaultComment(encoding, xml); 1550 } else { 1551 return comment + "\n#" + getDefaultComment(encoding, xml); 1552 } 1553 } 1554 1555 /** 1556 * This is private because <code>SortedProperties</code> does not fully honor the contract for <code>Properties</code> 1557 */ 1558 private static final SortedProperties getSortedProperties(Properties properties) { 1559 SortedProperties sp = new PropertyUtils().new SortedProperties(); 1560 sp.putAll(properties); 1561 return sp; 1562 } 1563 1564 /** 1565 * This is private since it does not honor the full contract for <code>Properties</code>. <code>PropertyUtils</code> uses it internally to store properties in sorted order. 1566 */ 1567 private class SortedProperties extends Properties { 1568 1569 private static final long serialVersionUID = 1330825236411537386L; 1570 1571 /** 1572 * <code>Properties.storeToXML()</code> uses <code>keySet()</code> 1573 */ 1574 @Override 1575 public Set<Object> keySet() { 1576 return Collections.unmodifiableSet(new TreeSet<Object>(super.keySet())); 1577 } 1578 1579 /** 1580 * <code>Properties.store()</code> uses <code>keys()</code> 1581 */ 1582 @Override 1583 public synchronized Enumeration<Object> keys() { 1584 return Collections.enumeration(new TreeSet<Object>(super.keySet())); 1585 } 1586 } 1587 1588 /** 1589 * Set properties in the given Properties to CSV versions of the lists in the ComparisonResults 1590 * 1591 * @param properties 1592 * the Properties to populate 1593 * @param listComparison 1594 * the ComparisonResults to use for data 1595 * @param propertyNames 1596 * the list of property keys to set. Exactly 3 names are required, and the assumed order is: index 0: key for the ADDED list index 1: key for the SAME list index 2: 1597 * key for the DELETED list 1598 */ 1599 public static final void addListComparisonProperties(Properties properties, ComparisonResults listComparison, List<String> propertyNames) { 1600 // make sure that there are three names in the list of property names 1601 Assert.isTrue(propertyNames.size() == 3); 1602 1603 properties.setProperty(propertyNames.get(0), CollectionUtils.getCSV(listComparison.getAdded())); 1604 properties.setProperty(propertyNames.get(1), CollectionUtils.getCSV(listComparison.getSame())); 1605 properties.setProperty(propertyNames.get(2), CollectionUtils.getCSV(listComparison.getDeleted())); 1606 } 1607 1608}