18

I'm using Spring 3.2 in a web application and I'd like to have a .properties file within the classpath which contains default values. The user should be able to use JNDI to define a location where another .properties is stored which overrides the default values.

The following works as long as the user has set the configLocation as JNDI property.

@Configuration
@PropertySource({ "classpath:default.properties", "file:${java:comp/env/configLocation}/override.properties" })
public class AppConfig
{
}

However, the external overrides should be optional and so should the JNDI property.

Currently I get an exception (java.io.FileNotFoundException: comp\env\configLocation\app.properties (The system cannot find the path specified) when the JNDI property is missing.

How can I define optional .properties that are used only when the JNDI property (configLocation) is set? Is this even possible with @PropertySource or is there another solution?

Gerhard Schlager
  • 3,155
  • 1
  • 31
  • 53

3 Answers3

52

As of Spring 4, issue SPR-8371 has been solved. Consequently, the @PropertySource annotation has a new attribute called ignoreResourceNotFound that has been added for exactly this purpose. Additionally, there is also the new @PropertySources annotation which allows implementations like:

@PropertySources({
    @PropertySource("classpath:default.properties"),
    @PropertySource(value = "file:/path_to_file/optional_override.properties", ignoreResourceNotFound = true)
})
Gerhard Schlager
  • 3,155
  • 1
  • 31
  • 53
matsev
  • 32,104
  • 16
  • 121
  • 156
  • 4
    Note as per `@PropertySources` JavaDoc, in Java 8, you can directly apply multiple `@PropertySource` annotations to a class without the wrapper. – billc.cn Jan 28 '16 at 11:04
  • 3
    @billc.cn Quite right. I wrote a [blog post](http://www.jayway.com/2014/02/16/spring-propertysource/) about a year ago about `@PropertySource` in which I also mentioned [Java 8's repeating annotations](http://openjdk.java.net/jeps/120). – matsev Jan 29 '16 at 18:40
5

If you're not yet on Spring 4 (see matsev's solution), here's a more verbose, but roughly equivalent solution:

@Configuration
@PropertySource("classpath:default.properties")
public class AppConfig {

    @Autowired
    public void addOptionalProperties(StandardEnvironment environment) {
        try {
            String localPropertiesPath = environment.resolvePlaceholders("file:${java:comp/env/configLocation}/override.properties");
            ResourcePropertySource localPropertySource = new ResourcePropertySource(localPropertiesPath);
            environment.getPropertySources().addLast(localPropertySource);
        } catch (IOException ignored) {}
    }

}
Koraktor
  • 41,357
  • 10
  • 69
  • 99
3

Try the following. Create a ApplicationContextInitializer

In a Web Context: ApplicationContextInitializer<ConfigurableWebApplicationContext> and register it in the web.xml via:

<context-param>
    <param-name>contextInitializerClasses</param-name>
    <param-value>...ContextInitializer</param-value>
</context-param>

In the ContextInitializer you can add your property files via classpath and file system (haven't tried JNDI though).

  public void initialize(ConfigurableWebApplicationContext applicationContext) {
    String activeProfileName = null;
    String location = null;

    try {
      ConfigurableEnvironment environment = applicationContext.getEnvironment();
      String appconfigDir = environment.getProperty(APPCONFIG);
      if (appconfigDir == null ) {
        logger.error("missing property: " + APPCONFIG);
        appconfigDir = "/tmp";
      }
      String[] activeProfiles = environment.getActiveProfiles();

      for ( int i = 0; i < activeProfiles.length; i++ ) {
        activeProfileName = activeProfiles[i];
        MutablePropertySources propertySources = environment.getPropertySources();
        location = "file://" + appconfigDir + activeProfileName + ".properties";
        addPropertySource(applicationContext, activeProfileName,
                location, propertySources);
        location = "classpath:/" + activeProfileName + ".properties";
        addPropertySource(applicationContext, activeProfileName,
                          location, propertySources);
      }
      logger.debug("environment: '{}'", environment.getProperty("env"));

    } catch (IOException e) {
      logger.info("could not find properties file for active Spring profile '{}' (tried '{}')", activeProfileName, location);
      e.printStackTrace();
    }
  }

  private void addPropertySource(ConfigurableWebApplicationContext applicationContext, String activeProfileName,
                                 String location, MutablePropertySources propertySources) throws IOException {
    Resource resource = applicationContext.getResource(location);
    if ( resource.exists() ) {
      ResourcePropertySource propertySource = new ResourcePropertySource(location);
      propertySources.addLast(propertySource);
    } else {
      logger.info("could not find properties file for active Spring profile '{}' (tried '{}')", activeProfileName, location);
    }
  }

The code above tries to find a property file per active profile (see: How to set active spring 3.1 environment profile via a properites file and not via an env variable or system property)

Community
  • 1
  • 1
vivo
  • 333
  • 2
  • 8