usage
@Test
public void testResolve() {
Map<Object, Object> properties = new HashMap<>();
Function<String, Object> passwordDecoder = (String param) -> {
return param.replace("en", "de");
};
properties.put("property1", "value1");
properties.put("property2", "value2-!{property1}]");
properties.put("property3", "3");
properties.put("property23", "23");
properties.put("null", null);
properties.put("special.symbols", "1.1.1");
properties.put("password", passwordDecoder);
assertEquals("[value1]", resolve("[!{property1}]", properties));
assertEquals("[!{property1}]", resolve("[!!{property1}]", properties));
assertEquals("[23]", resolve("[!{property2!{property3}}]", properties));
assertEquals("[!{unknown}]", resolve("[!{unknown}]", properties));
assertEquals("[null]", resolve("[!{null}]", properties));
assertEquals("[1.1.1]", resolve("[!{special.symbols}]", properties));
assertEquals("[decoded]", resolve("[!{password:encoded}]", properties));
}
implementation
public final class PlaceholderResolver {
private static final Pattern PLACEHOLDER_PATTERN = Pattern.compile("(?<!\\!)\\!\\{(?<name>[^:{}]+):?(?<param>[^{}]+)?\\}");
private PlaceholderResolver() {}
@SuppressWarnings("unchecked")
public static String resolve(String str, Map<Object, Object> properties) {
if (str == null)
return null;
String resolved = str;
while (true) {
StringBuffer buffer = new StringBuffer();
Matcher matcher = PLACEHOLDER_PATTERN.matcher(resolved);
while (matcher.find()) {
String name = matcher.group("name");
if (properties.containsKey(name)) {
String param = matcher.group("param");
Object value = properties.get(name);
if (value instanceof Function)
value = ((Function<String, Object>) value).apply(param);
matcher.appendReplacement(buffer, quoteReplacement(Objects.toString(value)));
} else {
matcher.appendReplacement(buffer, quoteReplacement(matcher.group()));
}
}
matcher.appendTail(buffer);
if (buffer.toString().equals(resolved))
break;
resolved = buffer.toString();
}
return resolved.replace("!!{", "!{");
}
}
Spring integration
@Configuration
@PropertySource("classpath:application.properties")
public class AppConfig {
@Bean
public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
return new MyPropertySourcesPlaceholderConfigurer();
}
}
MyPropertySourcesPlaceholderConfigurer
import static com.google.common.base.Preconditions.checkNotNull;
import static com.ubs.wma.bmss.common.util.PasswordUtils.decrypt;
import static com.ubs.wma.bmss.common.util.PlaceholderResolver.resolve;
import static java.lang.System.getenv;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
public class MyPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer {
public class PasswordDecryptionFunction implements Function<String, String> {
@Override
public String apply(String encrypted) {
return decrypt(encrypted, checkNotNull(getenv("MASTER_PASSWORD"),
"Cannot decrypt password in property file because MASTER_PASSWORD environment variable was not set"));
}
}
private Map<Object, Object> properties = new HashMap<>();
public MyPropertySourcesPlaceholderConfigurer() {
properties.put("password", new PasswordDecryptionFunction());
}
@Override
protected String convertPropertyValue(String originalValue) {
return resolve(originalValue, properties);
}
}