org.kuali.common.util.PropertyUtils Maven / Gradle / Ivy
/**
* 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