com.github.springbootPlus.config.utils.PropertyPlaceholderHelper Maven / Gradle / Ivy
package com.github.springbootPlus.config.utils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.core.Constants;
import org.springframework.core.SpringProperties;
import org.springframework.core.env.AbstractEnvironment;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import java.util.*;
/**
* 占位符替换工具类
*
* @Test public void replacePlaceholders1() {
* Properties properties = new Properties();
* properties.put("id", "aaa");
* String val = new PropertyPlaceholderHelper("{","}").replacePlaceholders1("ABC${TEMP}123", properties);
* System.out.println(val);
* }
* @className: PropertyPlaceholderHelper
* @description:
* @author: WANGHUI
* @createDate: 2018/5/23 9:24
* @version: 1.0
*/
public class PropertyPlaceholderHelper {
private static final Log logger = LogFactory.getLog(PropertyPlaceholderHelper.class);
private static final Map wellKnownSimplePrefixes = new HashMap(4);
static {
wellKnownSimplePrefixes.put("}", "{");
wellKnownSimplePrefixes.put("]", "[");
wellKnownSimplePrefixes.put(")", "(");
}
/**
* Default placeholder prefix: {@value}
*/
public static final String DEFAULT_PLACEHOLDER_PREFIX = "${";
/**
* Default placeholder suffix: {@value}
*/
public static final String DEFAULT_PLACEHOLDER_SUFFIX = "}";
/**
* Default value separator: {@value}
*/
public static final String DEFAULT_VALUE_SEPARATOR = ":";
/**
* Defaults to {@value #DEFAULT_PLACEHOLDER_PREFIX}
*/
protected String placeholderPrefix = DEFAULT_PLACEHOLDER_PREFIX;
/**
* Defaults to {@value #DEFAULT_PLACEHOLDER_SUFFIX}
*/
protected String placeholderSuffix = DEFAULT_PLACEHOLDER_SUFFIX;
/**
* Defaults to {@value #DEFAULT_VALUE_SEPARATOR}
*/
protected String valueSeparator = DEFAULT_VALUE_SEPARATOR;
private final String simplePrefix;
private final boolean ignoreUnresolvablePlaceholders;
public PropertyPlaceholderHelper() {
this(DEFAULT_PLACEHOLDER_PREFIX, DEFAULT_PLACEHOLDER_SUFFIX, DEFAULT_VALUE_SEPARATOR, true);
}
/**
* Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and suffix.
* Unresolvable placeholders are ignored.
*
* @param placeholderPrefix the prefix that denotes the start of a placeholder
* @param placeholderSuffix the suffix that denotes the end of a placeholder
*/
public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix) {
this(placeholderPrefix, placeholderSuffix, null, true);
}
/**
* Creates a new {@code PropertyPlaceholderHelper} that uses the supplied prefix and suffix.
*
* @param placeholderPrefix the prefix that denotes the start of a placeholder
* @param placeholderSuffix the suffix that denotes the end of a placeholder
* @param valueSeparator the separating character between the placeholder variable
* and the associated default value, if any
* @param ignoreUnresolvablePlaceholders indicates whether unresolvable placeholders should
* be ignored ({@code true}) or cause an exception ({@code false})
*/
public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix,
String valueSeparator, boolean ignoreUnresolvablePlaceholders) {
Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");
this.placeholderPrefix = placeholderPrefix;
this.placeholderSuffix = placeholderSuffix;
String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
this.simplePrefix = simplePrefixForSuffix;
} else {
this.simplePrefix = this.placeholderPrefix;
}
this.valueSeparator = valueSeparator;
this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}
/**
* Replaces all placeholders of format {@code ${name}} with the corresponding
* property from the supplied {@link Properties}.
*
* @param value the value containing the placeholders to be replaced
* @param properties the {@code Properties} to use for replacement
* @return the supplied value with placeholders replaced inline
*/
public String replacePlaceholders(String value, final Properties properties) {
Assert.notNull(properties, "'properties' must not be null");
return replacePlaceholders(value, new PropertyPlaceholderConfigurerResolver(properties));
}
/**
* Replaces all placeholders of format {@code ${name}} with the value returned
* from the supplied {@link PlaceholderResolver}.
*
* @param value the value containing the placeholders to be replaced
* @param placeholderResolver the {@code PlaceholderResolver} to use for replacement
* @return the supplied value with placeholders replaced inline
*/
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
Assert.notNull(value, "'value' must not be null");
return parseStringValue(value, placeholderResolver, new HashSet());
}
protected String parseStringValue(
String value, PlaceholderResolver placeholderResolver, Set visitedPlaceholders) {
StringBuilder result = new StringBuilder(value);
int startIndex = value.indexOf(this.placeholderPrefix);
while (startIndex != -1) {
int endIndex = findPlaceholderEndIndex(result, startIndex);
if (endIndex != -1) {
String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
String originalPlaceholder = placeholder;
if (!visitedPlaceholders.add(originalPlaceholder)) {
throw new IllegalArgumentException(
"Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
}
// Recursive invocation, parsing placeholders contained in the placeholder key.
placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
// Now obtain the value for the fully resolved key...
String propVal = placeholderResolver.resolvePlaceholder(placeholder);
if (propVal == null && this.valueSeparator != null) {
int separatorIndex = placeholder.indexOf(this.valueSeparator);
if (separatorIndex != -1) {
String actualPlaceholder = placeholder.substring(0, separatorIndex);
String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
if (propVal == null) {
propVal = defaultValue;
}
}
}
if (propVal != null) {
// Recursive invocation, parsing placeholders contained in the
// previously resolved placeholder value.
propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
if (logger.isTraceEnabled()) {
logger.trace("Resolved placeholder '" + placeholder + "'");
}
startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
} else if (this.ignoreUnresolvablePlaceholders) {
// Proceed with unprocessed value.
startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
} else {
throw new IllegalArgumentException("Could not resolve placeholder '" +
placeholder + "'" + " in value \"" + value + "\"");
}
visitedPlaceholders.remove(originalPlaceholder);
} else {
startIndex = -1;
}
}
return result.toString();
}
private int findPlaceholderEndIndex(CharSequence buf, int startIndex) {
int index = startIndex + this.placeholderPrefix.length();
int withinNestedPlaceholder = 0;
while (index < buf.length()) {
if (StringUtils.substringMatch(buf, index, this.placeholderSuffix)) {
if (withinNestedPlaceholder > 0) {
withinNestedPlaceholder--;
index = index + this.placeholderSuffix.length();
} else {
return index;
}
} else if (StringUtils.substringMatch(buf, index, this.simplePrefix)) {
withinNestedPlaceholder++;
index = index + this.simplePrefix.length();
} else {
index++;
}
}
return -1;
}
protected boolean trimValues = false;
protected String nullValue;
/**
* Strategy interface used to resolve replacement values for placeholders contained in Strings.
*/
public interface PlaceholderResolver {
/**
* Resolve the supplied placeholder name to the replacement value.
*
* @param placeholderName the name of the placeholder to resolve
* @return the replacement value, or {@code null} if no replacement is to be made
*/
String resolvePlaceholder(String placeholderName);
}
public interface StringValueResolver {
/**
* Resolve the given String value, for example parsing placeholders.
*
* @param strVal the original String value (never {@code null})
* @return the resolved String value (may be {@code null} when resolved to a null
* value), possibly the original String value itself (in case of no placeholders
* to resolve or when ignoring unresolvable placeholders)
* @throws IllegalArgumentException in case of an unresolvable String value
*/
String resolveStringValue(String strVal);
}
private class PlaceholderResolvingStringValueResolver implements StringValueResolver {
private final PropertyPlaceholderHelper helper;
private final PlaceholderResolver resolver;
public PlaceholderResolvingStringValueResolver(Properties props) {
this.helper = new PropertyPlaceholderHelper(
placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders);
this.resolver = new PropertyPlaceholderConfigurerResolver(props);
}
@Override
public String resolveStringValue(String strVal) throws BeansException {
String resolved = this.helper.replacePlaceholders(strVal, this.resolver);
if (trimValues) {
resolved = resolved.trim();
}
return (resolved.equals(nullValue) ? null : resolved);
}
}
private class PropertyPlaceholderConfigurerResolver implements PlaceholderResolver {
private final Properties props;
private PropertyPlaceholderConfigurerResolver(Properties props) {
this.props = props;
}
@Override
public String resolvePlaceholder(String placeholderName) {
return PropertyPlaceholderHelper.this.resolvePlaceholder(placeholderName, props, systemPropertiesMode);
}
}
/**
* Set the system property mode by the name of the corresponding constant,
* e.g. "SYSTEM_PROPERTIES_MODE_OVERRIDE".
*
* @param constantName name of the constant
* @throws IllegalArgumentException if an invalid constant was specified
* @see #setSystemPropertiesMode
*/
public void setSystemPropertiesModeName(String constantName) throws IllegalArgumentException {
this.systemPropertiesMode = constants.asNumber(constantName).intValue();
}
/**
* Set how to check system properties: as fallback, as override, or never.
* For example, will resolve ${user.dir} to the "user.dir" system property.
* The default is "fallback": If not being able to resolve a placeholder
* with the specified properties, a system property will be tried.
* "override" will check for a system property first, before trying the
* specified properties. "never" will not check system properties at all.
*
* @see #SYSTEM_PROPERTIES_MODE_NEVER
* @see #SYSTEM_PROPERTIES_MODE_FALLBACK
* @see #SYSTEM_PROPERTIES_MODE_OVERRIDE
* @see #setSystemPropertiesModeName
*/
public void setSystemPropertiesMode(int systemPropertiesMode) {
this.systemPropertiesMode = systemPropertiesMode;
}
private int systemPropertiesMode = SYSTEM_PROPERTIES_MODE_FALLBACK;
private static final Constants constants = new Constants(PropertyPlaceholderHelper.class);
/**
* Never check system properties.
*/
public static final int SYSTEM_PROPERTIES_MODE_NEVER = 0;
/**
* Check system properties if not resolvable in the specified properties.
* This is the default.
*/
public static final int SYSTEM_PROPERTIES_MODE_FALLBACK = 1;
/**
* Check system properties first, before trying the specified properties.
* This allows system properties to override any other property source.
*/
public static final int SYSTEM_PROPERTIES_MODE_OVERRIDE = 2;
private boolean searchSystemEnvironment =
!SpringProperties.getFlag(AbstractEnvironment.IGNORE_GETENV_PROPERTY_NAME);
/**
* Resolve the given placeholder using the given properties, performing
* a system properties check according to the given mode.
*
The default implementation delegates to {@code resolvePlaceholder
* (placeholder, props)} before/after the system properties check.
*
Subclasses can override this for custom resolution strategies,
* including customized points for the system properties check.
*
* @param placeholder the placeholder to resolve
* @param props the merged properties of this configurer
* @param systemPropertiesMode the system properties mode,
* according to the constants in this class
* @return the resolved value, of null if none
* @see #setSystemPropertiesMode
* @see System#getProperty
* @see #resolvePlaceholder(String, Properties)
*/
protected String resolvePlaceholder(String placeholder, Properties props, int systemPropertiesMode) {
String propVal = null;
if (systemPropertiesMode == SYSTEM_PROPERTIES_MODE_OVERRIDE) {
propVal = resolveSystemProperty(placeholder);
}
if (propVal == null) {
propVal = resolvePlaceholder(placeholder, props);
}
if (propVal == null && systemPropertiesMode == SYSTEM_PROPERTIES_MODE_FALLBACK) {
propVal = resolveSystemProperty(placeholder);
}
return propVal;
}
protected String resolvePlaceholder(String placeholder, Properties props) {
return props.getProperty(placeholder);
}
protected String resolveSystemProperty(String key) {
try {
String value = System.getProperty(key);
if (value == null && this.searchSystemEnvironment) {
value = System.getenv(key);
}
return value;
} catch (Throwable ex) {
if (logger.isDebugEnabled()) {
logger.debug("Could not access system property '" + key + "': " + ex);
}
return null;
}
}
}