10

My problem is as follows:

I have server.properties for different environments. The path to those properties is provided trough a system property called propertyPath. How can I instruct my applicationContext.xml to load the properties with the given propertyPath system property without some ugly MethodInvokingBean which calls System.getProperty('');

My applicationContext.xml

<bean id="systemPropertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE"/>
        <property name="placeholderPrefix" value="sys{"/>
        <property name="properties">
            <props>
                <prop key="propertyPath">/default/path/to/server.properties</prop>
            </props>
        </property>
    </bean>


    <bean id="propertyResource" class="org.springframework.core.io.FileSystemResource" dependency-check="all" depends-on="systemPropertyConfigurer">
        <constructor-arg value="sys{propertyPath}"/>
    </bean>

    <bean id="serviceProperties" class="org.springframework.beans.factory.config.PropertiesFactoryBean">
        <property name="location" ref="propertyResource"/>
    </bean>

    <bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
        <property name="location" ref="propertyResource"/>
        <property name="placeholderPrefix" value="prop{"/>

        <property name="ignoreUnresolvablePlaceholders" value="true"/>
        <property name="ignoreResourceNotFound" value="false"/>
    </bean>

    <bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean">
         <property name="jndiName" value="prop{datasource.name}"/>
    </bean>

with this configuration the propertyResource alsways complains about

java.io.FileNotFoundException: sys{propertyPath} (The system cannot find the file specified)

Any suggestions? ;-) Thanks gabe

EDIT:

Now I debugged the loading process of the beans and it seems the setLocation Method of the propertyConfigurer is called before the systemPropertyConfigurer is created so the propertyResource is initialized with "sys{propertyPath}". I played around with depends-on but no luck.

n3utrino
  • 2,361
  • 3
  • 22
  • 32
  • how exactly did you play with depends-on? – Bozho May 11 '10 at 08:12
  • 'propertyResource depends-on="systemPropertyConfigurer"' seems to have no effect. propertyResource is initialized first nontheless, maybe because it is initialized with constructor-arg – n3utrino May 11 '10 at 08:43

3 Answers3

6

Ok. I solved it. The problem is both of my PropertyPlaceholders are BeanFactoryPostProcessor those get processed after the context is loaded but the properties are set after. So it is impossible to populate one PropertyPlaceholder with another.

Here is my solution in code ;-)

package property.util;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

import java.io.IOException;
import java.util.Properties;

/**
 * ConfigurablePropertyPlaceholder takes instructions which SystemProperty
 * contains the path to the propertyfile to load.
 *
 * @author Gabe Kaelin
 * 
 */
public class ConfigurablePropertyPlaceholder extends PropertyPlaceholderConfigurer {


    private String propertyLocationSystemProperty;
    private String defaultPropertyFileName;


    public String getPropertyLocationSystemProperty() {
        return propertyLocationSystemProperty;
    }

    public void setPropertyLocationSystemProperty(String propertyLocationSystemProperty) {
        this.propertyLocationSystemProperty = propertyLocationSystemProperty;
    }

    public String getDefaultPropertyFileName() {
        return defaultPropertyFileName;
    }

    public void setDefaultPropertyFileName(String defaultPropertyFileName) {
        this.defaultPropertyFileName = defaultPropertyFileName;
    }

    /**
     * Overridden to fill the location with the path from the {@link #propertyLocationSystemProperty}
     *
     * @param props propeties instance to fill
     * @throws IOException
     */

    @Override
    protected void loadProperties(Properties props) throws IOException {
        Resource location = null;
        if(StringUtils.isNotEmpty(propertyLocationSystemProperty)){

            String propertyFilePath = System.getProperties().getProperty(propertyLocationSystemProperty);
            StringBuilder pathBuilder = new StringBuilder(propertyFilePath);

            if(StringUtils.isNotEmpty(defaultPropertyFileName) && !propertyFilePath.endsWith(defaultPropertyFileName)){
                pathBuilder.append("/").append(defaultPropertyFileName);
            }

            location = new FileSystemResource(pathBuilder.toString());
        }

        setLocation(location);
        super.loadProperties(props);
    }
}

The according applicationContext.xml entry

<bean id="propertyConfigurer" class="property.util.ConfigurablePropertyPlaceholder">
  <property name="propertyLocationSystemProperty" value="propertyPath" />
  <property name="defaultPropertyFileName" value="server.properties" />
  <property name="ignoreResourceNotFound" value="false"/>
</bean>

the java process can be started with

java -DpropertyPath=/path/to/properties

and it loads the properties and they are available in the applicationContext.xml

n3utrino
  • 2,361
  • 3
  • 22
  • 32
2

The solution given to extend PropertyPlaceholderConfigurer seems ok but it should also work relying on the standard org.springframework.beans.factory.config.PropertiesFactoryBean implementation without writing any additional code.

Simply use the variable reference that will be resolved by Spring.

e.g.

Configure spring as follows:

<bean id="configProp"
      class="org.springframework.beans.factory.config.PropertiesFactoryBean">
    <property name="locations">
        <list>
            <value>file:${propertyPath}</value>
        </list>
    </property>
</bean>

Once you invoke Java with the env variable (propertyPath), Spring will resolve it, load the property file and inject it into the application context

java -DpropertyPath=/path/to/properties

Gian Marco
  • 22,140
  • 8
  • 55
  • 44
1

You have two options:

  • use sys: as prefix (and hence sys:propertyPath)

  • set the placeholderSuffix property of the placeholder configurer to }, so that you can access the properties with sys{prop}. If you omit this property, you will have to use sys{prop

skaffman
  • 398,947
  • 96
  • 818
  • 769
Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
  • 1
    Thank you for the tips. But I am unable to get it to work. Maybe I missed something. It always throws an FileNotFoundEx with the property name as file. the default placeholderSuffix is } so there is no need to specify it i think there is a problem with the loading order of the beans – n3utrino May 11 '10 at 06:40