2013/06/05

Resolving Properties With Spring

It is a well known (and not surprising at all) fact, that you can use properties in Spring. You can reference them in you application context configuration file(s), or by the new Spring 3 @Value annotation.

In the XML config the reference to the property named "x.y.z" this simple: 
<bean name="theBean" class="myBean">
  <property name="parameter" value="${x.y.z}" /> 
</bean>

This is usually accompanied by the element from context namespace setting the name of the configuration file:
<context:property-placeholder location="classpath:app.properties"/>

This is good approach for simple scenarios. In a more complex setup things can get a bit unwieldy. 

First of all each occurrence of <context:property-placeholder> element causes creation of a new instance of the class responsible for property names resolving - PropertyPlaceholderConfigurer instance. You can enjoy lot of fun with unresolved placeholders in your application, once the library you're using brings its application context configuration containing <context:property-placeholder>. Even if you set ignoreUnresolvablePlaceholders=false for the element, it does not change the fact you have multiple PropertyPlaceholderconfigurers where you need only one.

You may want to the properties loaded by Spring to a Spring unaware class or further customize the property resolving. In such case you'll create a custom PropertyPlaceholderConfigurer ... and get more collisions with default ones created by the <context:property-placeholder>. 

Custom PropertyPlaceholderConfigurer


The solution is simple, remove all the occurrences of <context:property-placeholder> and create a custom PropertyPlaceholderConfigurer, that will fulfill your desires. Example of one is below. The static method getProperty() can serve properties to Spring unaware code.

import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
import java.util.*;

class MyPropertyResolver extends PropertyPlaceholderConfigurer
{

    private static Map<String, String> propertiesMap = new HashMap<String, String>();

    @Override
    protected void processProperties
    (ConfigurableListableBeanFactory beanFactory, Properties properties)
    {
        super.processProperties(beanFactory, properties);
        for ( Object propertyKey : properties.keySet())
        {
            propertiesMap.put
            (
               propertyKey.toString(),
               // Spring 3.x
               resolvePlaceholder(propertyKey.toString(), properties)
               // for Spring 2.5 :
               // parseStringValue(propertyKey.toString(), properties, Collections.EMPTY_SET)
            );
        }
    }

    public static String getProperty(String name)
    {
        return propertiesMap.get(name);
    }
}
 
Following configuration of bean named appProperties provides setup for multiple property files - the last one is the application property file overriding default values provided by libraries. Option ignoreResorceNotFound=true ensures that the missing files are not taken for an error.
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">

    <!-- load it early, provides properties to other classes -->
    <bean id="appProperties" class="MyPropertyResolver">
        <property name="ignoreResourceNotFound" value="true"/>
        <property name="locations">
            <list>
                <!-- Ordering matters, properties in the later loaded files 
                     override values from the previous files -->
                <value>classpath:lib.properties</value>
                <value>classpath:app.properties</value>
            </list>
        </property>
    </bean>

</beans>

Adding to an Application Context


The last thing you need to do to make it working is to put the bean into your application context and use it. Following setup for a web application ensures that the bean is instantiated ASAP during application context initialization.
<web-app xmlns="http://java.sun.com/xml/ns/j2ee" version="2.4" 
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
         xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee 
                             http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> 

  <context-param> 
    <param-name>contextConfigLocation</param-name> 
    <param-value> 
      classpath:spring-resources.xml 
      ... 
    </param-value> 
  </context-param> 

</web-app>

For command-line application it's very simple too:
ClassPathXmlApplicationContext appContext = new ClassPathXmlApplicationContext(
                                            new String[] {"spring-resources.xml"});
MyPropertyResolver resolver = (MyPropertyResolver) appContext.getBean("appProperties");

2013/03/27

Renamed Logback Config File for Web Application

There are four frameworks frequently used for logging in Java - java.util.logging aka JUL, Apache Commons Logging aka JCL, log4j 1.x, and slf4j. I had "luck" to find them all used by a single web application.

It was impossible to tell what component uses what logging API for what messages. To make the logging manageable I modified the code base to use SLF4J API with Logback - changes to several places in code were made and bridges were added for third-party libraries.

The last request was to name the configuration file uniquely so it can be in the same directory with configuration files for other applications. The problem is Logback  is not particularly  flexible when it comes to the naming of its config files - it looks for logback.groovy,  for logback-test.xml, logback.xml and then fails with JoranException.

According to documentation, there is a system property logback.configurationFile that allows to set a different name for the file.

It can be set on a command line (java -Dlogback.configurationFile=/path/to/config.xml), but that is not what you want or can use for a web application. Usual way how to pass settings to a web application is to use <context-param> in web.xml:
<context-param>
  <param-name>logback.configurationFile</param-name>
  <param-value>custom-logback.xml</param-value>
</context-param> 

The problem is that servlet context parameters are not copied into system properties, so SLF4J ContextInitializer ends up looking for the "usual suspects"  and then gives up:

ContextInitializer.autoConfig()
     -> findURLOfDefaultConfigurationFile()
         -> findConfigFileURLFromSystemProperties()
            // logback.configurationFile is not a system property, returns null:    
            -> OptionHelper.getSystemProperty(CONFIG_FILE_PROPERTY)
         -> getResource("logback.groovy")
         -> getResource("logback-test.xml")
         -> getResource("logback.xml")
         : returns null 
      -> 3/ BasicConfigurator.configure(loggerContext)



The solution is to get the  context-param as soon as possible during web application initialization and pass it to JoranConfigurator. You have to implement custom ServletContextListener and put the logic inside contextInitialized method:

public class CustomServletContextListener 
implements ServletContextListener 
{
  @Override
  public void contextInitialized(ServletContextEvent contextEvent) 
  { 
    String logbackConfigFile = contextEvent.getServletContext().getInitParameter("logback.configurationFile"); 
    URL configFileURL;
    if (logbackConfigFile != null) 
    { 
      configFileURL = Thread.currentThread().getContextClassLoader().getResource(logbackConfigFile);
      if (configFileURL != null)
      {
        JoranConfigurator configurator = new JoranConfigurator(); 
        LoggerContext loggerContext = (LoggerContext) LoggerFactory.getILoggerFactory(); 
        loggerContext.reset(); 
        configurator.setContext(loggerContext); 
        try { configurator.doConfigure(configFileURL); } 
        catch (JoranException ex) { throw new RuntimeException(ex); } 
      }
    }
  } 
To be sure the initialization happens as soon as possible, put the context listener to the top of the listeners list in web.xml:
<listener>
  <listener-class>org.bithill.CustomServletContextListener</listener-class>
</listener>