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