0

I have a Java lib which is based on Spring Boot. In my case I need to resolve placeholders which start with something.* in my own way before this property will be resolved by some Spring Property Resolver. For example:

application.properties:

spring.datasource.url="${something.url}"

So this placeholder is matching my something.* pattern and I want to replace it with specific word before Spring will try to resolve it. Where can I do it so I can avoid creating system properties with System.setProperty?

Here is my attempts on achieving it:

import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.util.PropertyPlaceholderHelper;
import org.springframework.util.StringValueResolver;

import java.util.Properties;

public class MyPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {

    @Override
    protected void processProperties(ConfigurableListableBeanFactory beanFactoryToProcess, Properties props)
            throws BeansException {
        StringValueResolver valueResolver = new ReloadablePlaceholderResolvingStringValueResolver(props);
        this.doProcessProperties(beanFactoryToProcess, valueResolver);
    }

    private class ReloadablePlaceholderResolvingStringValueResolver
            implements StringValueResolver {

        private final PropertyPlaceholderHelper helper;
        private final ReloadablePropertyPlaceholderConfigurerResolver resolver;

        public ReloadablePlaceholderResolvingStringValueResolver(Properties props) {
            this.helper = new MyPropertyPlaceholderHelper(placeholderPrefix, placeholderSuffix, valueSeparator, ignoreUnresolvablePlaceholders);
            this.resolver = new ReloadablePropertyPlaceholderConfigurerResolver(props);
        }

        @Override
        public String resolveStringValue(String strVal) throws BeansException {
            String value = this.helper.replacePlaceholders(strVal, this.resolver);
            return (value.equals(nullValue) ? null : value);
        }
    }

    private class ReloadablePropertyPlaceholderConfigurerResolver
            implements PropertyPlaceholderHelper.PlaceholderResolver {

        private Properties props;
        private ReloadablePropertyPlaceholderConfigurerResolver(Properties props) {
            this.props = props;
        }

        @Override
        public String resolvePlaceholder(String placeholderName) {
            return MyPropertyPlaceholderConfigurer.this.resolvePlaceholder(placeholderName, props, SYSTEM_PROPERTIES_MODE_FALLBACK);
        }
    }
}

Initializing bean:

@Bean
public PropertyPlaceholderConfigurer propertyPlaceholderConfigurer() {
    return new MyPropertyPlaceholderConfigurer();
}

Getting resolved property:

@RestController
public class MainController {

    @Autowired
    private Environment environment;

    @GetMapping("/getEnvProeprty")
    public String getEnvironmentName() {
        return environment.getProperty("spring.datasource.url");
    }
}
Praytic
  • 1,771
  • 4
  • 21
  • 41
  • I'd be interested to know what you would like to replace 'something' with in this case, since this might give a simpler solution. You could look to create a custom bean with extends PropertyPlaceholderConfigurer, this gives you hook points to apply your property name substitution logic and then pass back to spring to set the values see https://stackoverflow.com/questions/26150527/how-can-i-reload-properties-file-in-spring-4-using-annotations/39138850#39138850 – emeraldjava Feb 05 '18 at 16:14
  • I created a custom PropertyPlaceholderConfigurer bean like in your example but it didn't work. – Praytic Feb 06 '18 at 10:27
  • It is created via `new` declaration in `AbstractEnvironment`: `private final ConfigurablePropertyResolver propertyResolver = new PropertySourcesPropertyResolver(this.propertySources);` – Praytic Feb 06 '18 at 10:38
  • I suggest you post the code for your custom class and details of the error you are seeing. – emeraldjava Feb 06 '18 at 10:41
  • I posted my code. I also debuged the stacktrace of getting property and I could not find where this `PropertyPlaceholderConfigurer` is used. – Praytic Feb 06 '18 at 11:37
  • @Praytic did you solve this use case? I'm also looking for the similar one. – vinter Jun 12 '23 at 01:51
  • No, feel free to post the answer here if you find one. – Praytic Jun 13 '23 at 18:37
  • @Praytic i have solved a similar use case and posted an answer, for more details you can refer to the article that i have shared in the answer – vinter Jun 17 '23 at 14:16

1 Answers1

0

I had a similar requirement and solved this use case.

In short:

Since spring tries to resolve the properties in the order from list of porperty sources, the basic idea is to add our custom property source at the top[0th index] of the PropertySources in the current Environment.

To do that, override the getAppliedPropertySources() method in PropertySourcesPlaceholderConfigurer.java class and do addFirst the custom property source in the current spring Evnironment. So that spring will try to resolve the placeholder from our custom source first, and if not found, goes through the remaining property sources.

@Configuration

public class CustomPropertySourcesPlaceholderConfigurer extends PropertySourcesPlaceholderConfigurer {

private static final String[] IGNORABLE_SECRETS = new String[] {"secrets.property1", "secrets.property2"};

@Override
public PropertySources getAppliedPropertySources() throws IllegalStateException {

    // gets the applied property sources
    MutablePropertySources appliedPropertySources = (MutablePropertySources) super.getAppliedPropertySources();

    // fetches the property source of the current evnironment
    PropertySource<?> environmentPropertySource = appliedPropertySources.get("environmentProperties");

    if (Objects.nonNull(environmentPropertySource)) {
        addCustomPropertySourceToIgnoreRefresh(environmentPropertySource);
    }
    return appliedPropertySources;
}

private void addCustomPropertySourceToIgnoreRefresh(PropertySource<?> environmentPropertySource) {
    Map<String, Object> customPropertyMap = new HashMap<>();
    
    // Iterating over properties listed in IGNORABLE_SECRETS to override them
           
    for (String ignorableSecretProperty: IGNORABLE_SECRETS) {

        customPropertyMap.put(ignorableSecretProperty, "customValueForThatPorperty");
        
        // For my use case, i'm trying to fetch the secrets from the property sources that are loaded
        // during the bootstrap phase and using them as sort of cache so that spring boot doesn't 
        // need to make grpc calls to google to resolve the secrets during the consul refresh
        //  customPropertyMap.put(ignorableSecretProperty,
        //                  environmentPropertySource.getProperty(ignorableSecretProperty));

    }
    ConfigurableEnvironment configurableEnvironment =
            (ConfigurableEnvironment) environmentPropertySource.getSource();

    // post fetching the configurable environment, adding my hash map as custom map property source
    configurableEnvironment.getPropertySources()
                           .addFirst(new MapPropertySource("customPropertySource", customPropertyMap));
}

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    super.postProcessBeanFactory(beanFactory);
}

}

For more details you can refer to the article that I have published in medium.

vinter
  • 466
  • 1
  • 3
  • 13