0

I want something very similar to this question, but my case is a bit complicated.

Is there any lovely way I can replace dynamic placeholders in text?
For example resolve masked password and replace it with decoded one in the property file:

encoded.url=jdbc:oracle:thin:SCHEMA/${password:abtBKwUnmBfTDCz04k83ew==}@host:port:sid
Mike
  • 20,010
  • 25
  • 97
  • 140

2 Answers2

0

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);
    }
}
Mike
  • 20,010
  • 25
  • 97
  • 140
0

This is the imperative approach. First, find the substring that only contains the password. Then, copy the string appearing before the password and the string appearing after the password. Last, we can simply put the two copied string together, where the new password is in between.

  private static String changePassword(String str, String newPassword)
  {
    String oldPassword = str.split("password:")[1];
    oldPassword = oldPassword.substring(0, oldPassword.indexOf('}'));
    final String part1 = str.split(oldPassword)[0], part2 = str.split(oldPassword)[1];

    return part1 + newPassword + part2;
  }

I believe this is a simple solution, but keep in mind this solution only works for strings on the same format as your example string.