All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.kuali.common.util.PropertyUtils Maven / Gradle / Ivy

There is a newer version: 4.4.17
Show newest version
/**
 * Copyright 2010-2014 The Kuali Foundation
 *
 * Licensed under the Educational Community License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.opensource.org/licenses/ecl2.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.kuali.common.util;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeSet;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.jasypt.util.text.TextEncryptor;
import org.kuali.common.util.properties.rice.RiceLoader;
import org.kuali.common.util.property.Constants;
import org.kuali.common.util.property.GlobalPropertiesMode;
import org.kuali.common.util.property.ImmutableProperties;
import org.kuali.common.util.property.PropertiesContext;
import org.kuali.common.util.property.PropertyFormat;
import org.kuali.common.util.property.processor.AddPropertiesProcessor;
import org.kuali.common.util.property.processor.PropertyProcessor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.PropertyPlaceholderHelper;

import com.google.common.base.Optional;
import com.google.common.collect.Maps;

/**
 * Simplify handling of Properties especially as it relates to storing and loading. Properties can be loaded from any url Spring resource loading can
 * understand. When storing and loading, locations ending in .xml are automatically handled using storeToXML() and loadFromXML(),
 * respectively. Properties are always stored in sorted order with the encoding indicated via a comment.
 */
public class PropertyUtils {

	private static final Logger logger = LoggerFactory.getLogger(PropertyUtils.class);

	public static final String ADDITIONAL_LOCATIONS = "properties.additional.locations";
	public static final String ADDITIONAL_LOCATIONS_ENCODING = ADDITIONAL_LOCATIONS + ".encoding";
	public static final Properties EMPTY = new ImmutableProperties(new Properties());

	private static final String XML_EXTENSION = ".xml";
	private static final PropertyPlaceholderHelper HELPER = new PropertyPlaceholderHelper("${", "}", ":", false);
	public static final String ENV_PREFIX = "env";
	private static final String DEFAULT_ENCODING = Charset.defaultCharset().name();
	private static final String DEFAULT_XML_ENCODING = Encodings.UTF8;

	/**
	 * If there is no value for key or the value is NULL or NONE, return Optional.absent(), otherwise return Optional.of(value)
	 */
	public static Optional getOptionalString(Properties properties, String key) {
		if (properties.getProperty(key) == null) {
			return Optional.absent();
		} else {
			return Optional.of(properties.getProperty(key));
		}
	}

	public static Optional getString(Properties properties, String key, Optional provided) {
		Optional value = getOptionalString(properties, key);
		if (value.isPresent()) {
			return value;
		} else {
			return provided;
		}
	}

	/**
	 * If the properties passed in are already immutable, just return them, otherwise, return a new ImmutableProperties object
	 */
	public static Properties toImmutable(Properties properties) {
		return (properties instanceof ImmutableProperties) ? properties : new ImmutableProperties(properties);
	}

	/**
	 * The list returned by this method is unmodifiable and contains only ImmutableProperties
	 */
	public static List toImmutable(List properties) {
		List immutables = new ArrayList();
		for (Properties p : properties) {
			immutables.add(toImmutable(p));
		}
		return Collections.unmodifiableList(immutables);
	}

	/**
	 * Return true if the value for key evaluates to the string true (ignoring case). The properties passed in along with the system and environment
	 * variables are all inspected with the value for system or environment variables "winning" over the value from the properties passed in.
	 */
	public static boolean getGlobalBoolean(String key, Properties properties) {
		String defaultValue = properties.getProperty(key);
		String value = getGlobalProperty(key, defaultValue);
		return Boolean.parseBoolean(value);
	}

	public static boolean getBoolean(String key, Properties properties, boolean defaultValue) {
		String value = properties.getProperty(key);
		if (value == null) {
			return defaultValue;
		} else {
			return Boolean.parseBoolean(value);
		}
	}

	/**
	 * Return true if both contain an identical set of string keys and values, or both are null, false otherwise.
	 */
	public static boolean equals(Properties one, Properties two) {

		// Return true if they are the same object
		if (one == two) {
			return true;
		}

		// Return true if they are both null
		if (one == null && two == null) {
			return true;
		}

		// If we get here, both are not null (but one or the other might be)

		// Return false if one is null but not the other
		if (one == null || two == null) {
			return false;
		}

		// If we get here, neither one is null

		// Extract the string property keys
		List keys1 = getSortedKeys(one);
		List keys2 = getSortedKeys(two);

		// If the sizes are different, return false
		if (keys1.size() != keys2.size()) {
			return false;
		}

		// If we get here, they have the same number of string property keys

		// The sizes are the same, just pick one
		int size = keys1.size();

		// Iterate through the keys comparing both the keys and values for equality
		for (int i = 0; i < size; i++) {

			// Extract the keys
			String key1 = keys1.get(i);
			String key2 = keys2.get(i);

			// Compare the keys for equality (this works because the keys are in sorted order)
			if (!StringUtils.equals(key1, key2)) {
				return false;
			}

			// Extract the values
			String val1 = one.getProperty(key1);
			String val2 = two.getProperty(key2);

			// Compare the values for equality
			if (!StringUtils.equals(val1, val2)) {
				return false;
			}
		}

		// If we get here we know 3 things:

		// 1 - Both have the exact same number of string based keys/values
		// 2 - Both have an identical set of string based keys
		// 3 - Both have the exact same string value for each string key

		// This means they are equal, return true
		return true;

	}

	public static String getProperty(Properties properties, String key, String defaultValue) {
		String value = properties.getProperty(key);
		if (StringUtils.isBlank(value)) {
			return defaultValue;
		} else {
			return value;
		}
	}

	public static boolean isEmpty(Properties properties) {
		return properties == null || properties.size() == 0;
	}

	public static String getRiceXML(Properties properties) {
		StringBuilder sb = new StringBuilder();
		sb.append("\n");
		List keys = getSortedKeys(properties);
		for (String key : keys) {
			String value = properties.getProperty(key);
			// Convert to CDATA if the value contains characters that would blow up an XML parser
			if (StringUtils.contains(value, "<") || StringUtils.contains(value, "&")) {
				value = Str.cdata(value);
			}
			sb.append("  ");
			sb.append(value);
			sb.append("\n");
		}
		sb.append("\n");
		return sb.toString();
	}

	public static String getRequiredResolvedProperty(Properties properties, String key) {
		return getRequiredResolvedProperty(properties, key, null);
	}

	public static String getRequiredResolvedProperty(Properties properties, String key, String defaultValue) {
		String value = properties.getProperty(key);
		value = StringUtils.isBlank(value) ? defaultValue : value;
		if (StringUtils.isBlank(value)) {
			throw new IllegalArgumentException("[" + key + "] is not set");
		} else {
			return HELPER.replacePlaceholders(value, properties);
		}
	}

	/**
	 * Process the properties passed in so they are ready for use by a Spring context.
* * 1 - Override with system/environment properties
* 2 - Decrypt any ENC(...) values
* 3 - Resolve all property values throwing an exception if any are unresolvable.
*/ public static void prepareContextProperties(Properties properties, String encoding) { // Override with additional properties (if any) properties.putAll(getAdditionalProperties(properties, encoding)); // Override with system/environment properties properties.putAll(getGlobalProperties()); // Are we decrypting property values? decrypt(properties); // Are we resolving placeholders resolve(properties); } /** * Process the properties passed in so they are ready for use by a Spring context.
* * 1 - Override with system/environment properties
* 2 - Decrypt any ENC(...) values
* 3 - Resolve all property values throwing an exception if any are unresolvable.
*/ public static void prepareContextProperties(Properties properties) { prepareContextProperties(properties, null); } public static void resolve(Properties properties, PropertyPlaceholderHelper helper) { List keys = getSortedKeys(properties); for (String key : keys) { String original = properties.getProperty(key); String resolved = helper.replacePlaceholders(original, properties); if (!StringUtils.equals(original, resolved)) { logger.debug("Resolved [{}]", key); properties.setProperty(key, resolved); } } } public static void removeSystemProperty(String key) { if (System.getProperty(key) != null) { logger.info("Removing system property [{}]", key); System.getProperties().remove(key); } } public static void removeSystemProperties(List keys) { for (String key : keys) { removeSystemProperty(key); } } @Deprecated public static void resolve(Properties properties) { // Are we resolving placeholders? boolean resolve = new Boolean(getRequiredResolvedProperty(properties, "properties.resolve", "true")); if (resolve) { org.kuali.common.util.property.processor.ResolvePlaceholdersProcessor rpp = new org.kuali.common.util.property.processor.ResolvePlaceholdersProcessor(); rpp.setHelper(HELPER); rpp.process(properties); } } @Deprecated public static void decrypt(Properties properties) { // Are we decrypting property values? boolean decrypt = Boolean.parseBoolean(getRequiredResolvedProperty(properties, "properties.decrypt", "false")); if (decrypt) { // If they asked to decrypt, a password is required String password = getRequiredResolvedProperty(properties, "properties.enc.password"); // Strength is optional (defaults to BASIC) String defaultStrength = org.kuali.common.util.enc.EncStrength.BASIC.name(); String strength = getRequiredResolvedProperty(properties, "properties.enc.strength", defaultStrength); org.kuali.common.util.enc.EncStrength es = org.kuali.common.util.enc.EncStrength.valueOf(strength); TextEncryptor decryptor = org.kuali.common.util.enc.EncUtils.getTextEncryptor(password, es); decrypt(properties, decryptor); } } public static Properties getAdditionalProperties(Properties properties) { return getAdditionalProperties(properties, null); } public static Properties getAdditionalProperties(Properties properties, String encoding) { String csv = properties.getProperty(ADDITIONAL_LOCATIONS); if (StringUtils.isBlank(csv)) { return new Properties(); } if (StringUtils.isBlank(encoding)) { encoding = properties.getProperty(ADDITIONAL_LOCATIONS_ENCODING, DEFAULT_XML_ENCODING); } List locations = CollectionUtils.getTrimmedListFromCSV(csv); PropertiesContext context = new PropertiesContext(locations, encoding); return load(context); } public static void appendToOrSetProperty(Properties properties, String key, String value) { Assert.hasText(value); String existingValue = properties.getProperty(key); if (existingValue == null) { existingValue = ""; } String newValue = existingValue + value; properties.setProperty(key, newValue); } @Deprecated public static Properties load(List pps) { // Create some storage for the Properties object we will be returning Properties properties = new Properties(); // Cycle through the list of project properties, loading them as we go for (org.kuali.common.util.property.ProjectProperties pp : pps) { logger.debug("oracle.dba.url.1={}", properties.getProperty("oracle.dba.url")); // Extract the properties context object PropertiesContext ctx = pp.getPropertiesContext(); // Retain the original properties object from the context Properties original = PropertyUtils.duplicate(PropertyUtils.toEmpty(ctx.getProperties())); // Override any existing property values with properties stored directly on the context Properties combined = PropertyUtils.combine(properties, ctx.getProperties()); // Store the combined properties on the context itself ctx.setProperties(combined); // Load properties as dictated by the context Properties loaded = load(ctx); logger.debug("oracle.dba.url.2={}", loaded.getProperty("oracle.dba.url")); // Override any existing property values with those we just loaded properties.putAll(loaded); // Override any existing property values with the properties that were stored directly on the context properties.putAll(original); } // Return the property values we now have return properties; } public static Properties load(PropertiesContext context) { // If there are no locations specified, add the properties supplied directly on the context (if there are any) if (CollectionUtils.isEmpty(context.getLocations())) { return PropertyUtils.toEmpty(context.getProperties()); } // Make sure we are configured correctly Assert.notNull(context.getHelper(), "helper is null"); Assert.notNull(context.getLocations(), "locations are null"); Assert.notNull(context.getEncoding(), "encoding is null"); Assert.notNull(context.getMissingLocationsMode(), "missingLocationsMode is null"); // Get system/environment properties Properties global = PropertyUtils.getGlobalProperties(); // Convert null to an empty properties object (if necessary) context.setProperties(PropertyUtils.toEmpty(context.getProperties())); // Create new storage for the properties we are loading Properties result = new Properties(); // Add in any properties stored directly on the context itself (these get overridden by properties loaded elsewhere) result.putAll(context.getProperties()); // Cycle through the locations, loading and storing properties as we go for (String location : context.getLocations()) { // Get a combined Properties object capable of resolving any placeholders that exist in the property location strings Properties resolverProperties = PropertyUtils.combine(context.getProperties(), result, global); // Make sure we have a fully resolved location to load Properties from String resolvedLocation = context.getHelper().replacePlaceholders(location, resolverProperties); // If the location exists, load properties from it if (LocationUtils.exists(resolvedLocation)) { // Load this set of Properties Properties properties = PropertyUtils.load(resolvedLocation, context.getEncoding()); // Add these properties to the result. This follows the traditional "last one in wins" strategy result.putAll(properties); } else { // Handle missing locations (might be fine, may need to emit a logging statement, may need to error out) ModeUtils.validate(context.getMissingLocationsMode(), "Non-existent location [" + resolvedLocation + "]"); } } // Return the properties we loaded return result; } /** * Decrypt any encrypted property values. Encrypted values are surrounded by ENC(...), like: * *
	 * my.value = ENC(DGA$S24FaIO)
	 * 
*/ public static void decrypt(Properties properties, TextEncryptor encryptor) { decrypt(properties, encryptor, null, null); } /** * Return a new Properties object (never null) containing only those properties whose values are encrypted. Encrypted values are surrounded by ENC(...), like: * *
	 * my.value = ENC(DGA$S24FaIO)
	 * 
*/ public static Properties getEncryptedProperties(Properties properties) { List keys = getEncryptedKeys(properties); Properties encrypted = new Properties(); for (String key : keys) { String value = properties.getProperty(key); encrypted.setProperty(key, value); } return encrypted; } /** * Return a list containing only those keys whose values are encrypted. Encrypted values are surrounded by ENC(...), like: * *
	 * my.value = ENC(DGA$S24FaIO)
	 * 
* * @deprecated */ @Deprecated public static List getEncryptedKeys(Properties properties) { List all = getSortedKeys(properties); List encrypted = new ArrayList(); for (String key : all) { String value = properties.getProperty(key); if (org.kuali.common.util.enc.EncUtils.isEncrypted(value)) { encrypted.add(key); } } return encrypted; } /** * Decrypt any encrypted property values matching the includes, excludes patterns. Encrypted values are surrounded by ENC(...). * *
	 * my.value = ENC(DGA$S24FaIO)
	 * 
* * @deprecated */ @Deprecated public static void decrypt(Properties properties, TextEncryptor encryptor, List includes, List excludes) { List keys = getSortedKeys(properties, includes, excludes); for (String key : keys) { String value = properties.getProperty(key); if (org.kuali.common.util.enc.EncUtils.isEncrypted(value)) { String decryptedValue = decryptPropertyValue(encryptor, value); properties.setProperty(key, decryptedValue); } } } /** * Return true if the value starts with ENC( and ends with ), false otherwise. * * @deprecated Use EncUtils.isEncrypted(value) instead */ @Deprecated public static boolean isEncryptedPropertyValue(String value) { return org.kuali.common.util.enc.EncUtils.isEncrypted(value); } /** *

* A trivial way to conceal property values. Can be reversed using reveal(). Do NOT use this method in an attempt to obscure sensitive data. The algorithm * is completely trivial and exceedingly simple to reverse engineer. Not to mention, the reveal() method can reproduce the original string without requiring any * secret knowledge. *

* *

* 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 NOT intended * to defeat any serious attempt at discovering the original text. *

* *

* Think a hungry sales or marketing rep who stumbles across a config file with the entry vending.machine.refill.day=wed in it and tries to change that to * mon in order to beat a case of the munchies. :) *

* *

* If the entry says vending.machine.refill.day=cnc--jrq instead of vending.machine.refill.day=wed they are far more likely to ask around before they * change it OR just give up and head out to lunch instead. *

* * @see reveal */ public static void conceal(Properties properties) { List keys = getSortedKeys(properties); for (String key : keys) { String value = properties.getProperty(key); String concealed = Str.conceal(value); properties.setProperty(key, concealed); } } /** * Reveal property values that were concealed by the conceal method * *
	 * foo=cnc--one.onm -> foo=bar.baz
	 * 
*/ public static void reveal(Properties properties) { List keys = getSortedKeys(properties); for (String key : keys) { String value = properties.getProperty(key); String revealed = Str.reveal(value); properties.setProperty(key, revealed); } } /** * Encrypt all of the property values. Encrypted values are surrounded by ENC(...). * *
	 * my.value = ENC(DGA$S24FaIO)
	 * 
*/ public static void encrypt(Properties properties, TextEncryptor encryptor) { encrypt(properties, encryptor, null, null); } /** * Encrypt properties as dictated by includes and excludes. Encrypted values are surrounded by ENC(...). * *
	 * my.value = ENC(DGA$S24FaIO)
	 * 
*/ public static void encrypt(Properties properties, TextEncryptor encryptor, List includes, List excludes) { List keys = getSortedKeys(properties, includes, excludes); for (String key : keys) { String originalValue = properties.getProperty(key); String encryptedValue = encryptPropertyValue(encryptor, originalValue); properties.setProperty(key, encryptedValue); } } /** * Return the decrypted version of the property value. Encrypted values are surrounded by ENC(...). * *
	 * my.value = ENC(DGA$S24FaIO)
	 * 
*/ public static String decryptPropertyValue(TextEncryptor encryptor, String value) { String unwrapped = unwrapEncryptedValue(value); // Return the decrypted value return encryptor.decrypt(unwrapped); } /** * Remove the leading ENC( prefix and the trailing ) from the encrypted value passed in. * *
	 * ENC(DGA$S24FaIO) -> DGA$S24FaIO
	 * 
* * @deprecated Use EncUtils.unwrap(value) instead */ @Deprecated public static String unwrapEncryptedValue(String encryptedValue) { return org.kuali.common.util.enc.EncUtils.unwrap(encryptedValue); } /** * Return the encrypted version of the property value. A value is considered "encrypted" when it appears surrounded by ENC(...). * *
	 * my.value = ENC(DGA$S24FaIO)
	 * 
*/ public static String encryptPropertyValue(TextEncryptor encryptor, String value) { String encryptedValue = encryptor.encrypt(value); return wrapEncryptedPropertyValue(encryptedValue); } /** * Return the value enclosed with ENC() * *
	 * DGA$S24FaIO -> ENC(DGA$S24FaIO)
	 * 
* * @deprecated Use EncUtils.wrap(value) instead */ @Deprecated public static String wrapEncryptedPropertyValue(String encryptedValue) { return org.kuali.common.util.enc.EncUtils.wrap(encryptedValue); } public static void overrideWithGlobalValues(Properties properties, GlobalPropertiesMode mode) { List keys = PropertyUtils.getSortedKeys(properties); Properties global = PropertyUtils.getProperties(mode); for (String key : keys) { String globalValue = global.getProperty(key); if (!StringUtils.isBlank(globalValue)) { properties.setProperty(key, globalValue); } } } public static final Properties[] toArray(List properties) { return properties.toArray(new Properties[properties.size()]); } public static final Properties combine(Properties properties, List list) { List newList = new ArrayList(CollectionUtils.toEmptyList(list)); newList.add(0, toEmpty(properties)); return combine(newList); } public static final Properties combine(List properties) { Properties combined = new Properties(); for (Properties p : properties) { combined.putAll(toEmpty(p)); } return combined; } public static final Properties combine(Properties... properties) { return combine(Arrays.asList(properties)); } public static final void process(Properties properties, PropertyProcessor processor) { process(properties, Collections.singletonList(processor)); } public static final void process(Properties properties, List processors) { for (PropertyProcessor processor : CollectionUtils.toEmptyList(processors)) { processor.process(properties); } } public static final Properties toEmpty(Properties properties) { return properties == null ? new Properties() : properties; } public static final boolean isSingleUnresolvedPlaceholder(String string) { return isSingleUnresolvedPlaceholder(string, Constants.DEFAULT_PLACEHOLDER_PREFIX, Constants.DEFAULT_PLACEHOLDER_SUFFIX); } public static final boolean isSingleUnresolvedPlaceholder(String string, String prefix, String suffix) { int prefixMatches = StringUtils.countMatches(string, prefix); int suffixMatches = StringUtils.countMatches(string, suffix); boolean startsWith = StringUtils.startsWith(string, prefix); boolean endsWith = StringUtils.endsWith(string, suffix); return prefixMatches == 1 && suffixMatches == 1 && startsWith && endsWith; } public static final boolean containsUnresolvedPlaceholder(String string) { return containsUnresolvedPlaceholder(string, Constants.DEFAULT_PLACEHOLDER_PREFIX, Constants.DEFAULT_PLACEHOLDER_SUFFIX); } public static final boolean containsUnresolvedPlaceholder(String string, String prefix, String suffix) { int beginIndex = StringUtils.indexOf(string, prefix); if (beginIndex == -1) { return false; } return StringUtils.indexOf(string, suffix) != -1; } /** * Return a new Properties object containing only those properties where the resolved value is different from the original value. Using global properties to * perform property resolution as indicated by Constants.DEFAULT_GLOBAL_PROPERTIES_MODE */ public static final Properties getResolvedProperties(Properties properties) { return getResolvedProperties(properties, Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE); } /** * Return a new Properties object containing only those properties where the resolved value is different from the original value. Using global properties to * perform property resolution as indicated by globalPropertiesMode */ public static final Properties getResolvedProperties(Properties properties, GlobalPropertiesMode globalPropertiesMode) { return getResolvedProperties(properties, Constants.DEFAULT_PROPERTY_PLACEHOLDER_HELPER, globalPropertiesMode); } /** * Return a new Properties object containing only those properties where the resolved value is different from the original value. Using global properties to * perform property resolution as indicated by Constants.DEFAULT_GLOBAL_PROPERTIES_MODE */ public static final Properties getResolvedProperties(Properties properties, PropertyPlaceholderHelper helper) { return getResolvedProperties(properties, helper, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE); } /** * Return a new Properties object containing only those properties where the resolved value is different from the original value. Using global properties to * perform property resolution as indicated by globalPropertiesMode */ public static final Properties getResolvedProperties(Properties properties, PropertyPlaceholderHelper helper, GlobalPropertiesMode globalPropertiesMode) { Properties global = getProperties(properties, globalPropertiesMode); List keys = getSortedKeys(properties); Properties newProperties = new Properties(); for (String key : keys) { String originalValue = properties.getProperty(key); String resolvedValue = helper.replacePlaceholders(originalValue, global); if (!resolvedValue.equals(originalValue)) { logger.debug("Resolved property '" + key + "' [{}] -> [{}]", Str.flatten(originalValue), Str.flatten(resolvedValue)); newProperties.setProperty(key, resolvedValue); } } return newProperties; } /** * Return the property values from keys */ public static final List getValues(Properties properties, List keys) { List values = new ArrayList(); for (String key : keys) { values.add(properties.getProperty(key)); } return values; } /** * Return a sorted List of keys from properties that end with suffix. */ public static final List getEndsWithKeys(Properties properties, String suffix) { List keys = getSortedKeys(properties); List matches = new ArrayList(); for (String key : keys) { if (StringUtils.endsWith(key, suffix)) { matches.add(key); } } return matches; } /** * Alter the properties passed in to contain only the desired property values. includes and excludes are comma separated values. */ public static final void trim(Properties properties, String includesCSV, String excludesCSV) { List includes = CollectionUtils.getTrimmedListFromCSV(includesCSV); List excludes = CollectionUtils.getTrimmedListFromCSV(excludesCSV); trim(properties, includes, excludes); } /** * Alter the properties passed in to contain only the desired property values. */ public static final void trim(Properties properties, List includes, List excludes) { List keys = getSortedKeys(properties); for (String key : keys) { if (!include(key, includes, excludes)) { logger.debug("Removing [{}]", key); properties.remove(key); } } } /** * Return true if value should be included, false otherwise.
* If excludes is not empty and matches value return false.
* If value has not been explicitly excluded, check the includes list.
* If includes is empty return true.
* If includes is not empty, return true if, and only if, value matches a pattern from the includes list.
* A single wildcard * is supported for includes and excludes.
*/ public static final boolean include(String value, List includes, List excludes) { if (isSingleWildcardMatch(value, excludes)) { // No point incurring the overhead of matching an include pattern return false; } else { // If includes is empty always return true return CollectionUtils.isEmpty(includes) || isSingleWildcardMatch(value, includes); } } public static final boolean isSingleWildcardMatch(String s, List patterns) { for (String pattern : CollectionUtils.toEmptyList(patterns)) { if (isSingleWildcardMatch(s, pattern)) { return true; } } return false; } /** * 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 * {@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 * {@code IllegalArgumentException}. * *
	 * PropertyUtils.isSingleWildcardMatch(null, null)          = true
	 * PropertyUtils.isSingleWildcardMatch(null, *)             = false
	 * PropertyUtils.isSingleWildcardMatch(*, null)             = false
	 * PropertyUtils.isSingleWildcardMatch(*, "*")              = true
	 * PropertyUtils.isSingleWildcardMatch("abcdef", "bcd")     = false
	 * PropertyUtils.isSingleWildcardMatch("abcdef", "*def")    = true
	 * PropertyUtils.isSingleWildcardMatch("abcdef", "abc*")    = true
	 * PropertyUtils.isSingleWildcardMatch("abcdef", "ab*ef")   = true
	 * PropertyUtils.isSingleWildcardMatch("abcdef", "abc*def") = true
	 * PropertyUtils.isSingleWildcardMatch(*, "**")             = IllegalArgumentException
	 * 
*/ public static final boolean isSingleWildcardMatch(String value, String pattern) { if (value == null && pattern == null) { // both are null return true; } else if (value == null || pattern == null) { // One is null, but not the other return false; } else if (pattern.equals(Constants.WILDCARD)) { // Neither one is null and pattern is the unqualified wildcard. // Value is irrelevant, always return true return true; } else if (StringUtils.countMatches(pattern, Constants.WILDCARD) > 1) { // More than one wildcard in the pattern is not supported throw new IllegalArgumentException("Pattern [" + pattern + "] is not supported. Only one wildcard is allowed in the pattern"); } else if (!StringUtils.contains(pattern, Constants.WILDCARD)) { // Neither one is null and there is no wildcard in the pattern. They must match exactly return StringUtils.equals(value, pattern); } else { // The pattern contains 1 (and only 1) wildcard // Make sure value starts with the characters to the left of the wildcard // and ends with the characters to the right of the wildcard int pos = StringUtils.indexOf(pattern, Constants.WILDCARD); int suffixPos = pos + Constants.WILDCARD.length(); boolean nullPrefix = pos == 0; boolean nullSuffix = suffixPos >= pattern.length(); String prefix = nullPrefix ? null : StringUtils.substring(pattern, 0, pos); String suffix = nullSuffix ? null : StringUtils.substring(pattern, suffixPos); boolean prefixMatch = nullPrefix || StringUtils.startsWith(value, prefix); boolean suffixMatch = nullSuffix || StringUtils.endsWith(value, suffix); return prefixMatch && suffixMatch; } } /** * Return property keys that should be included as a sorted list. */ public static final Properties getProperties(Properties properties, String include, String exclude) { List keys = getSortedKeys(properties, include, exclude); Properties newProperties = new Properties(); for (String key : keys) { String value = properties.getProperty(key); newProperties.setProperty(key, value); } return newProperties; } /** * Return property keys that should be included as a sorted list. */ public static final List getSortedKeys(Properties properties, String include, String exclude) { return getSortedKeys(properties, CollectionUtils.toEmptyList(include), CollectionUtils.toEmptyList(exclude)); } /** * Return property keys that should be included as a sorted list. */ public static final List getSortedKeys(Properties properties, List includes, List excludes) { List keys = getSortedKeys(properties); List includedKeys = new ArrayList(); for (String key : keys) { if (include(key, includes, excludes)) { includedKeys.add(key); } } return includedKeys; } /** * Return a sorted List of keys from properties that start with prefix */ public static final List getStartsWithKeys(Properties properties, String prefix) { List keys = getSortedKeys(properties); List matches = new ArrayList(); for (String key : keys) { if (StringUtils.startsWith(key, prefix)) { matches.add(key); } } return matches; } /** * Return the property keys as a sorted list. */ public static final List getSortedKeys(Properties properties) { List keys = new ArrayList(properties.stringPropertyNames()); Collections.sort(keys); return keys; } public static final String toString(Properties properties) { List keys = getSortedKeys(properties); StringBuilder sb = new StringBuilder(); for (String key : keys) { String value = Str.flatten(properties.getProperty(key)); sb.append(key + "=" + value + "\n"); } return sb.toString(); } public static final void info(Properties properties) { properties = toEmpty(properties); logger.info("--- Displaying {} properties ---\n\n{}", properties.size(), toString(properties)); } public static final void debug(Properties properties) { properties = toEmpty(properties); logger.debug("--- Displaying {} properties ---\n\n{}", properties.size(), toString(properties)); } /** * Store the properties to the indicated file using the platform default encoding. */ public static final void store(Properties properties, File file) { store(properties, file, null); } /** * Store the properties to the indicated file using the platform default encoding. */ public static final void storeSilently(Properties properties, File file) { store(properties, file, null, null, true); } /** * Store the properties to the indicated file using the indicated encoding. */ public static final void store(Properties properties, File file, String encoding) { store(properties, file, encoding, null); } /** * Store the properties to the indicated file using the indicated encoding with the indicated comment appearing at the top of the file. */ public static final void store(Properties properties, File file, String encoding, String comment) { store(properties, file, encoding, comment, false); } /** * Store the properties to the indicated file using the indicated encoding with the indicated comment appearing at the top of the file. */ public static final void store(Properties properties, File file, String encoding, String comment, boolean silent) { OutputStream out = null; Writer writer = null; try { out = FileUtils.openOutputStream(file); String path = file.getCanonicalPath(); boolean xml = isXml(path); Properties sorted = getSortedProperties(properties); comment = getComment(encoding, comment, xml); if (xml) { if (!silent) { logger.info("Storing XML properties - [{}] encoding={}", path, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING)); } if (encoding == null) { sorted.storeToXML(out, comment); } else { sorted.storeToXML(out, comment, encoding); } } else { writer = LocationUtils.getWriter(out, encoding); if (!silent) { logger.info("Storing properties - [{}] encoding={}", path, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING)); } sorted.store(writer, comment); } } catch (IOException e) { throw new IllegalStateException("Unexpected IO error", e); } finally { IOUtils.closeQuietly(writer); IOUtils.closeQuietly(out); } } /** * Examine both system properties and environment variables to get a value for key. Return null if nothing is found. * *
	 *   foo.bar -> System property check for "foo.bar"
	 *   foo.bar -> Environment check for "FOO_BAR"
	 * 
*/ public static final String getGlobalProperty(String key) { return getGlobalProperty(key, null); } /** * Examine both system properties and environment variables to get a value for key. Return defaultValue if nothing is found * *
	 *   foo.bar -> System property check for "foo.bar"
	 *   foo.bar -> Environment check for "FOO_BAR"
	 * 
*/ public static final String getGlobalProperty(String key, String defaultValue) { Assert.noNullsWithMsg("key is required", key); // Check to see if there is a system property for this key String systemValue = System.getProperty(key); // If so, we are done if (systemValue != null) { return systemValue; } // Reformat the key as an environment variable key String environmentVariable = convertToEnvironmentVariable(key); // Check to see if we have a match for an environment variable String environmentValue = System.getenv(environmentVariable); if (environmentValue != null) { // If so, return the value of the environment variable return environmentValue; } else { // If not, return the default value return defaultValue; } } /** * Return a new properties object containing the properties from getEnvAsProperties() and System.getProperties(). Properties from * System.getProperties() override properties from getEnvAsProperties if there are duplicates. */ public static final Properties getGlobalProperties() { return getProperties(Constants.DEFAULT_GLOBAL_PROPERTIES_MODE); } /** * Return a new properties object containing the properties passed in, plus any properties returned by getEnvAsProperties() and System.getProperties() * . Properties from getEnvAsProperties() override properties and properties from System.getProperties() override everything. */ public static final Properties getGlobalProperties(Properties properties) { return getProperties(properties, Constants.DEFAULT_GLOBAL_PROPERTIES_MODE); } /** * Return a new properties object containing the properties passed in, plus any global properties as requested. If mode is NONE the new properties are * a duplicate of the properties passed in. If mode is ENVIRONMENT the new properties contain the original properties plus any properties returned by * getEnvProperties(). If mode is SYSTEM the new properties contain the original properties plus System.getProperties(). If * mode is BOTH the new properties contain the original properties plus getEnvProperties() and System.getProperties(). */ public static final Properties getProperties(Properties properties, GlobalPropertiesMode mode) { Properties newProperties = duplicate(properties); List modifiers = getPropertyProcessors(mode); for (PropertyProcessor modifier : modifiers) { modifier.process(newProperties); } return newProperties; } /** * Return a new properties object containing global properties as requested. If mode is NONE the new properties are empty. If mode is * ENVIRONMENT the new properties contain the properties returned by getEnvProperties(). If mode is SYSTEM the new * properties contain System.getProperties(). If mode is BOTH the new properties contain getEnvProperties plus * System.getProperties() with system properties overriding environment variables if the same case sensitive property key is supplied in both places. */ public static final Properties getProperties(GlobalPropertiesMode mode) { return getProperties(new Properties(), mode); } /** * Search global properties to find a value for key according to the mode passed in. */ public static final String getProperty(String key, GlobalPropertiesMode mode) { return getProperty(key, new Properties(), mode); } /** * Search properties plus global properties to find a value for key according to the mode passed in. If the property is present in both, the value * from the global properties is returned. */ public static final String getProperty(String key, Properties properties, GlobalPropertiesMode mode) { return getProperties(properties, mode).getProperty(key); } /** * Return modifiers that add environment variables, system properties, or both, according to the mode passed in. */ public static final List getPropertyProcessors(GlobalPropertiesMode mode) { List processors = new ArrayList(); switch (mode) { case NONE: return processors; case ENVIRONMENT: processors.add(new AddPropertiesProcessor(getEnvAsProperties())); return processors; case SYSTEM: processors.add(new AddPropertiesProcessor(System.getProperties())); return processors; case BOTH: processors.add(new AddPropertiesProcessor(getEnvAsProperties())); processors.add(new AddPropertiesProcessor(System.getProperties())); return processors; default: throw new IllegalStateException(mode + " is unknown"); } } /** * Convert the Map to a Properties object. */ public static final Properties convert(Map map) { return convertToProperties(map); } /** * Convert the Map to a Properties object. */ public static final Properties convertToProperties(Map map) { Properties props = new Properties(); for (String key : map.keySet()) { props.setProperty(key, map.get(key)); } return props; } /** * Convert the Properties to a Map object. */ public static Map convert(Properties properties) { return newHashMap(properties); } /** * Convert the {@code Properties} to a {@code Map} object. */ public static Map newHashMap(Properties properties) { Map map = Maps.newHashMap(); for (String key : properties.stringPropertyNames()) { map.put(key, properties.getProperty(key)); } return map; } /** * Convert the {@code Properties} to a {@code Map} object. */ public static SortedMap newTreeMap(Properties properties) { SortedMap map = Maps.newTreeMap(); for (String key : properties.stringPropertyNames()) { map.put(key, properties.getProperty(key)); } return map; } /** * Return a new properties object that duplicates the properties passed in. */ public static final Properties duplicate(Properties properties) { Properties newProperties = new Properties(); newProperties.putAll(properties); return newProperties; } /** * Return a new properties object containing environment variables as properties prefixed with env */ public static Properties getEnvAsProperties() { return getEnvAsProperties(ENV_PREFIX); } /** * Return a new properties object containing environment variables as properties prefixed with prefix */ public static Properties getEnvAsProperties(String prefix) { Properties properties = convert(System.getenv()); return getPrefixedProperties(properties, prefix); } /** * Return true if, and only if, location ends with .xml (case insensitive). */ public static final boolean isXml(String location) { return StringUtils.endsWithIgnoreCase(location, XML_EXTENSION); } /** * Return true if, and only if, location ends with rice-properties.xml (case insensitive). */ public static final boolean isRiceProperties(String location) { return StringUtils.endsWithIgnoreCase(location, Constants.RICE_PROPERTIES_SUFFIX); } public static final Properties loadRiceProps(File file) { return RiceLoader.load(file); } public static final Properties loadRiceProps(String location) { return RiceLoader.load(location); } /** * Return a new Properties object loaded from file where the properties are stored in Rice XML style syntax * * @deprecated use loadRiceProps() instead */ @Deprecated public static final Properties loadRiceProperties(File file) { return loadRiceProperties(LocationUtils.getCanonicalPath(file)); } /** * Return a new Properties object loaded from location where the properties are stored in Rice XML style syntax * * @deprecated use loadRiceProps() instead */ @Deprecated public static final Properties loadRiceProperties(String location) { logger.info("Loading Rice properties [{}] encoding={}", location, DEFAULT_XML_ENCODING); String contents = LocationUtils.toString(location, DEFAULT_XML_ENCODING); String config = StringUtils.substringBetween(contents, "", ""); String[] tokens = StringUtils.substringsBetween(config, ""); validateRiceProperties(token, key); String value = StringUtils.substringBetween(token + "", "\">", ""); properties.setProperty(key, value); } return properties; } /** * 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, * override, or random attributes. */ protected static final void validateRiceProperties(String token, String key) { if (StringUtils.equalsIgnoreCase("config.location", key)) { throw new IllegalArgumentException("config.location is not supported"); } if (StringUtils.contains(token, "override=\"")) { throw new IllegalArgumentException("override attribute is not supported"); } if (StringUtils.contains(token, "system=\"")) { throw new IllegalArgumentException("system attribute is not supported"); } if (StringUtils.contains(token, "random=\"")) { throw new IllegalArgumentException("random attribute is not supported"); } } /** * Return a new Properties object loaded from file. */ public static final Properties load(File file) { return load(file, null); } /** * Return a new Properties object loaded from file. */ public static final Properties loadSilently(File file) { return loadSilently(LocationUtils.getCanonicalPath(file)); } /** * Return a new Properties object loaded from file. */ public static final Properties loadSilently(String location) { return load(location, null, PropertyFormat.NORMAL, true); } /** * Return a new Properties object loaded from file using the given encoding. */ public static final Properties load(File file, String encoding) { String location = LocationUtils.getCanonicalPath(file); return load(location, encoding); } /** * Return a new Properties object loaded from location. */ public static final Properties load(String location) { return load(location, null); } /** * If location exists, return a new Properties object loaded from location, otherwise return a new Properties object */ public static final Properties loadOrCreateSilently(String location) { if (LocationUtils.exists(location)) { return load(location, null, PropertyFormat.NORMAL, true); } else { return new Properties(); } } /** * Return a new Properties object loaded from locations using encoding. */ public static final Properties load(List locations, String encoding) { Properties properties = new Properties(); for (String location : locations) { properties.putAll(load(location, encoding)); } return properties; } /** * Return a new Properties object loaded from location using encoding. */ public static final Properties load(String location, String encoding) { return load(location, encoding, PropertyFormat.NORMAL); } /** * Return a new Properties object loaded from location using encoding. */ public static final Properties load(String location, String encoding, PropertyFormat format) { return load(location, encoding, format, false); } /** * Return a new Properties object loaded from location using encoding. */ public static final Properties load(String location, String encoding, PropertyFormat format, boolean silent) { InputStream in = null; Reader reader = null; try { Properties properties = new Properties(); boolean xml = isXml(location); boolean riceProperties = isRiceProperties(location); location = getCanonicalLocation(location); if (PropertyFormat.RICE.equals(format) || riceProperties) { properties = loadRiceProperties(location); } else if (xml) { in = LocationUtils.getInputStream(location); if (!silent) { logger.info("Loading XML properties - [{}]", location); } properties.loadFromXML(in); } else { if (!silent) { logger.info("Loading properties - [{}] encoding={}", location, StringUtils.defaultIfBlank(encoding, DEFAULT_ENCODING)); } reader = LocationUtils.getBufferedReader(location, encoding); properties.load(reader); } return properties; } catch (IOException e) { throw new IllegalStateException("Unexpected IO error", e); } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(reader); } } protected static String getCanonicalLocation(String location) { if (LocationUtils.isExistingFile(location)) { return LocationUtils.getCanonicalPath(new File(location)); } else { return location; } } /** * Return a new Properties object containing properties prefixed with prefix. If prefix is blank, the new properties object duplicates * the properties passed in. */ public static final Properties getPrefixedProperties(Properties properties, String prefix) { if (StringUtils.isBlank(prefix)) { return duplicate(properties); } Properties newProperties = new Properties(); for (String key : properties.stringPropertyNames()) { String value = properties.getProperty(key); String newKey = StringUtils.startsWith(key, prefix + ".") ? key : prefix + "." + key; newProperties.setProperty(newKey, value); } return newProperties; } /** * Replace periods with an underscore and convert to uppercase * *
	 *   foo.bar -> FOO_BAR
	 * 
*/ public static final String convertToEnvironmentVariable(String key) { return StringUtils.upperCase(StringUtils.replace(key, ".", "_")); } /** * Replace periods with an underscore, convert to uppercase, and prefix with env * *
	 *   foo.bar -> env.FOO_BAR
	 * 
*/ public static final String getEnvironmentVariableKey(String key) { return ENV_PREFIX + "." + convertToEnvironmentVariable(key); } /** * Return a new properties object where the keys have been converted to upper case and periods have been replaced with an underscore. */ public static final Properties reformatKeysAsEnvVars(Properties properties) { Properties newProperties = new Properties(); for (String key : properties.stringPropertyNames()) { String value = properties.getProperty(key); String newKey = convertToEnvironmentVariable(key); newProperties.setProperty(newKey, value); } return newProperties; } /** * 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 * propertyOverwriteMode to make sure we have permission to override the value. */ public static final void addOrOverrideProperty(Properties properties, String key, String newValue, Mode propertyOverwriteMode) { addOrOverrideProperty(properties, key, newValue, propertyOverwriteMode, 0); } public static final void addOrOverrideProperty(Properties properties, String key, String newValue, Mode overrideMode, int indent) { String oldValue = properties.getProperty(key); if (StringUtils.equals(newValue, oldValue)) { // Nothing to do! New value is the same as old value. return; } boolean overwrite = !StringUtils.isBlank(oldValue); String logNewValue = newValue; String logOldValue = oldValue; // TODO Yuck! Do something smarter here if (obscure(key)) { logNewValue = "*********"; logOldValue = "*********"; } if (overwrite) { // This property already has a value, and it is different from the new value // Check to make sure we are allowed to override the old value before doing so Object[] args = new Object[] { StringUtils.repeat(" ", indent), key, Str.flatten(logNewValue), Str.flatten(logOldValue) }; ModeUtils.validate(overrideMode, "{}override [{}] -> [{}]", args, "Override of existing property [" + key + "] is not allowed."); } else { // There is no existing value for this key logger.debug("Adding [{}={}]", key, Str.flatten(logNewValue)); } properties.setProperty(key, newValue); } protected static boolean obscure(String key) { if (StringUtils.containsIgnoreCase(key, "password")) { return true; } if (StringUtils.containsIgnoreCase(key, "secret")) { return true; } if (StringUtils.containsIgnoreCase(key, "private")) { return true; } return false; } private static final String getDefaultComment(String encoding, boolean xml) { if (encoding == null) { if (xml) { // Java defaults XML properties files to UTF-8 if no encoding is provided return "encoding.default=" + DEFAULT_XML_ENCODING; } else { // For normal properties files the platform default encoding is used return "encoding.default=" + DEFAULT_ENCODING; } } else { return "encoding.specified=" + encoding; } } private static final String getComment(String encoding, String comment, boolean xml) { if (StringUtils.isBlank(comment)) { return getDefaultComment(encoding, xml); } else { return comment + "\n#" + getDefaultComment(encoding, xml); } } /** * This is private because SortedProperties does not fully honor the contract for Properties */ private static final SortedProperties getSortedProperties(Properties properties) { SortedProperties sp = new PropertyUtils().new SortedProperties(); sp.putAll(properties); return sp; } /** * This is private since it does not honor the full contract for Properties. PropertyUtils uses it internally to store properties in sorted order. */ private class SortedProperties extends Properties { private static final long serialVersionUID = 1330825236411537386L; /** * Properties.storeToXML() uses keySet() */ @Override public Set keySet() { return Collections.unmodifiableSet(new TreeSet(super.keySet())); } /** * Properties.store() uses keys() */ @Override public synchronized Enumeration keys() { return Collections.enumeration(new TreeSet(super.keySet())); } } /** * Set properties in the given Properties to CSV versions of the lists in the ComparisonResults * * @param properties * the Properties to populate * @param listComparison * the ComparisonResults to use for data * @param propertyNames * 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: * key for the DELETED list */ public static final void addListComparisonProperties(Properties properties, ComparisonResults listComparison, List propertyNames) { // make sure that there are three names in the list of property names Assert.isTrue(propertyNames.size() == 3); properties.setProperty(propertyNames.get(0), CollectionUtils.getCSV(listComparison.getAdded())); properties.setProperty(propertyNames.get(1), CollectionUtils.getCSV(listComparison.getSame())); properties.setProperty(propertyNames.get(2), CollectionUtils.getCSV(listComparison.getDeleted())); } }