Spring Framework
  1. Spring Framework
  2. SPR-8442

Resolve ${...} placeholders in @PropertySource resource locations

    Details

    • Type: Improvement Improvement
    • Status: Closed
    • Priority: Minor Minor
    • Resolution: Complete
    • Affects Version/s: 3.1 M2
    • Fix Version/s: 3.1 RC1
    • Component/s: None
    • Labels:
      None

      Description

      Allow @PropertySource("classpath:/foo/$

      {placeholder}

      /x.properties")

        Activity

        Hide
        Chris Beams added a comment -

        @PropertySource resource locations now support resolution of $

        {...}

        placeholders.

        See updated Javadoc at https://build.springsource.org/browse/SPR-TRUNKQUICK-JOB1-3772/artifact/javadoc-api/org/springframework/context/annotation/PropertySource.html

        Show
        Chris Beams added a comment - @PropertySource resource locations now support resolution of $ {...} placeholders. See updated Javadoc at https://build.springsource.org/browse/SPR-TRUNKQUICK-JOB1-3772/artifact/javadoc-api/org/springframework/context/annotation/PropertySource.html
        Hide
        Marcel Overdijk added a comment -

        Thanks Chris!

        I think I'm asking to much and the use case is limited but would it also be possible to avoid the IllegalArgumentException in case property is not found?

        The reason is I would have something like:

        @PropertySource({
        "classpath:/com/config.properties",
        "classpath:/com/config-$

        {application.name:Dummy}

        .properties",
        "classpath:/com/config-$

        {runtime.environment:Dummy}

        .properties",
        })

        Dummy.properties will be an empty properties file.
        I'm overriding the default properties (config.properties) with optional properties if provided for the current application, and even runtime environment.

        Show
        Marcel Overdijk added a comment - Thanks Chris! I think I'm asking to much and the use case is limited but would it also be possible to avoid the IllegalArgumentException in case property is not found? The reason is I would have something like: @PropertySource({ "classpath:/com/config.properties", "classpath:/com/config-$ {application.name:Dummy} .properties", "classpath:/com/config-$ {runtime.environment:Dummy} .properties", }) Dummy.properties will be an empty properties file. I'm overriding the default properties (config.properties) with optional properties if provided for the current application, and even runtime environment.
        Hide
        Chris Beams added a comment -

        Hi Marcel,

        I don't quite follow. An IllegalArgumentException will be thrown only if the $

        {...}

        placeholder is unresolvable AND you have not expressed a :default value.

        In the case above, you have 'Dummy' as a default, so no exception should be thrown.

        Are you saying that the scenario above is your workaround, and that you'd like some other way of achieving the same behavior? Are you asking if it would be possible for us to completely ignore a resource location if its placeholder cannot be resolved?

        Please clarify, thanks.

        Show
        Chris Beams added a comment - Hi Marcel, I don't quite follow. An IllegalArgumentException will be thrown only if the $ {...} placeholder is unresolvable AND you have not expressed a :default value. In the case above, you have 'Dummy' as a default, so no exception should be thrown. Are you saying that the scenario above is your workaround, and that you'd like some other way of achieving the same behavior? Are you asking if it would be possible for us to completely ignore a resource location if its placeholder cannot be resolved? Please clarify, thanks.
        Hide
        Marcel Overdijk added a comment -

        "Are you saying that the scenario above is your workaround, and that you'd like some other way of achieving the same behavior? Are you asking if it would be possible for us to completely ignore a resource location if its placeholder cannot be resolved?"

        Yes this what I mean. I use the Dummy as workaround.
        I think this is probably an edge case so I understand if Spring would not offer this and I need to use the Dummy file. But yes, if it would be possible to completely ignore a resource location if the placeholder cannot be resolved, or if just the file cannot be found would be great. I also see the danger of this. I mean people having a type for example. What do you think?

        Show
        Marcel Overdijk added a comment - "Are you saying that the scenario above is your workaround, and that you'd like some other way of achieving the same behavior? Are you asking if it would be possible for us to completely ignore a resource location if its placeholder cannot be resolved?" Yes this what I mean. I use the Dummy as workaround. I think this is probably an edge case so I understand if Spring would not offer this and I need to use the Dummy file. But yes, if it would be possible to completely ignore a resource location if the placeholder cannot be resolved, or if just the file cannot be found would be great. I also see the danger of this. I mean people having a type for example. What do you think?
        Hide
        Chris Beams added a comment -

        Thanks for clearing that up. Your instinct is probably right, in that it would be a surprising effect to most users to ignore the entire resource location if one (or more) placeholders did not resolve. While it's conceivable to add an attribute to the annotation that makes this configurable, it's probably the wrong path to head down as well. This sort of special case really calls out for full programmatic control.

        As you're probably aware, you can manipulate property sources in any way you please by working against the ConfigurableEnvironment API off of your enclosing ApplicationContext. If you're within a webapp, use an ApplicationContextInitializer to do this.

        Additionally, if certain among these property sources are indeed dependent on the runtime environment as your naming above suggests, you could possibly get the job done through the use of @Profile and (possibly nested) @Configuration classes. i.e. a @Configuration marked with @Profile("x") as well as @PropertySource("x.properties"). Only if profile 'x' is active will that @Configuration class be processed, including any @PropertySource annotation it may declare.

        Thanks again for the feedback. Keep it coming!

        Show
        Chris Beams added a comment - Thanks for clearing that up. Your instinct is probably right, in that it would be a surprising effect to most users to ignore the entire resource location if one (or more) placeholders did not resolve. While it's conceivable to add an attribute to the annotation that makes this configurable, it's probably the wrong path to head down as well. This sort of special case really calls out for full programmatic control. As you're probably aware, you can manipulate property sources in any way you please by working against the ConfigurableEnvironment API off of your enclosing ApplicationContext. If you're within a webapp, use an ApplicationContextInitializer to do this. Additionally, if certain among these property sources are indeed dependent on the runtime environment as your naming above suggests, you could possibly get the job done through the use of @Profile and (possibly nested) @Configuration classes. i.e. a @Configuration marked with @Profile("x") as well as @PropertySource("x.properties"). Only if profile 'x' is active will that @Configuration class be processed, including any @PropertySource annotation it may declare. Thanks again for the feedback. Keep it coming!
        Hide
        Marcel Overdijk added a comment -

        Thanks Chris,
        I was already using an ApplicationContextInitializer since 3.1.0.M1 which works indeed perfectly. I'm adding the code I use below as reference for other users.

        I will stick to this, but was just experimenting with the new @PropertySource annotation to see if I would be able to remove a few lines of code
        Note that I'm using the PropertiesFactoryBean.setIgnoreResourceNotFound(true) to ignore property files not found.

        package nl.vooty.config;
        
        import java.io.IOException;
        import java.util.Properties;
        import org.slf4j.Logger;
        import org.slf4j.LoggerFactory;
        import org.springframework.beans.factory.config.PropertiesFactoryBean;
        import org.springframework.context.ApplicationContextInitializer;
        import org.springframework.core.env.ConfigurableEnvironment;
        import org.springframework.core.env.PropertiesPropertySource;
        import org.springframework.core.io.ClassPathResource;
        import org.springframework.core.io.Resource;
        import org.springframework.web.context.ConfigurableWebApplicationContext;
        import com.google.appengine.api.utils.SystemProperty;
        
        public class AppContextInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext> {
        
            private Logger logger = LoggerFactory.getLogger(this.getClass());
            
            private String runtimeEnvironment;
            
            @Override
            public void initialize(ConfigurableWebApplicationContext ctx) {
                this.runtimeEnvironment = SystemProperty.environment.value().value();
                logger.debug("Initializing [" + this.runtimeEnvironment + "] environment");
                ConfigurableEnvironment environment = ctx.getEnvironment();
                environment.setActiveProfiles(this.runtimeEnvironment);
                environment.getPropertySources().addFirst(new PropertiesPropertySource("properties", applicationProperties()));
            }
            
            private Properties applicationProperties() {
                PropertiesFactoryBean factoryBean = new PropertiesFactoryBean();
                Resource[] locations = {
                        new ClassPathResource("META-INF/config.properties"),
                        new ClassPathResource("META-INF/config-" + this.runtimeEnvironment + ".properties")};
                factoryBean.setLocations(locations);
                factoryBean.setIgnoreResourceNotFound(true);
                try {
                    factoryBean.afterPropertiesSet();
                    return factoryBean.getObject();
                }
                catch (IOException e) {
                    throw new IllegalStateException(e);
                }
            }
        }
        
        Show
        Marcel Overdijk added a comment - Thanks Chris, I was already using an ApplicationContextInitializer since 3.1.0.M1 which works indeed perfectly. I'm adding the code I use below as reference for other users. I will stick to this, but was just experimenting with the new @PropertySource annotation to see if I would be able to remove a few lines of code Note that I'm using the PropertiesFactoryBean.setIgnoreResourceNotFound(true) to ignore property files not found. package nl.vooty.config; import java.io.IOException; import java.util.Properties; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.config.PropertiesFactoryBean; import org.springframework.context.ApplicationContextInitializer; import org.springframework.core.env.ConfigurableEnvironment; import org.springframework.core.env.PropertiesPropertySource; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import org.springframework.web.context.ConfigurableWebApplicationContext; import com.google.appengine.api.utils.SystemProperty; public class AppContextInitializer implements ApplicationContextInitializer<ConfigurableWebApplicationContext> { private Logger logger = LoggerFactory.getLogger( this .getClass()); private String runtimeEnvironment; @Override public void initialize(ConfigurableWebApplicationContext ctx) { this .runtimeEnvironment = SystemProperty.environment.value().value(); logger.debug( "Initializing [" + this .runtimeEnvironment + "] environment" ); ConfigurableEnvironment environment = ctx.getEnvironment(); environment.setActiveProfiles( this .runtimeEnvironment); environment.getPropertySources().addFirst( new PropertiesPropertySource( "properties" , applicationProperties())); } private Properties applicationProperties() { PropertiesFactoryBean factoryBean = new PropertiesFactoryBean(); Resource[] locations = { new ClassPathResource( "META-INF/config.properties" ), new ClassPathResource( "META-INF/config-" + this .runtimeEnvironment + ".properties" )}; factoryBean.setLocations(locations); factoryBean.setIgnoreResourceNotFound( true ); try { factoryBean.afterPropertiesSet(); return factoryBean.getObject(); } catch (IOException e) { throw new IllegalStateException(e); } } }
        Hide
        Chris Beams added a comment -

        Would it work equally as well for you to drop the use of the PropertiesFactoryBean completely and instead do a resource.exists() check on each of the resources you create, before creating and adding a PropertySource for each?

        This would have the benefit of (a) avoiding the awkward use of a FactoryBean, (b) less code overall, (c) having a PropertySource dedicated to each individual resource means more control and better traceability.

        Show
        Chris Beams added a comment - Would it work equally as well for you to drop the use of the PropertiesFactoryBean completely and instead do a resource.exists() check on each of the resources you create, before creating and adding a PropertySource for each? This would have the benefit of (a) avoiding the awkward use of a FactoryBean , (b) less code overall, (c) having a PropertySource dedicated to each individual resource means more control and better traceability.

          People

          • Assignee:
            Chris Beams
            Reporter:
            Chris Beams
            Last updater:
            Trevor Marshall
          • Votes:
            0 Vote for this issue
            Watchers:
            2 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:
              Days since last comment:
              2 years, 45 weeks, 1 day ago