Apache Commons logo Commons Configuration

Basic features and AbstractConfiguration

The Configuration interface defines a whole bunch of methods. Implementing these methods all from scratch can be quite hard. Because of that the AbstractConfiguration class exists. This class serves as a common base class for most of the Configuration implementations in Commons Configuration and provides a great deal of the functionality required by the interface. So for creating a custom Configuration implementation this class will be a good starting point.

In addition to base implementations for lots of the methods declared in the Configuration interface the AbstractConfiguration class provides some other handy and convenient features. Because this class is at the very root of the class hierarchy in Commons Configuration these features are available in most of the specific implementations of the Configuration interface provided by this library. We will cover some of these basic features in this section.

Handling of missing properties

What is a configuration object supposed to do if you pass in a key to one of its get methods that does not map to an existing property? Well, the default behavior as implemented in AbstractConfiguration is to return null if the return value is an object type. For primitive types as return values returning null (or any other special value) is not possible, so in this case a NoSuchElementException is thrown:

// This will return null if no property with key "NonExistingProperty" exists
String strValue = config.getString("NonExistingProperty");

// This will throw a NoSuchElementException exception if no property with
// key "NonExistingProperty" exists
long longValue = config.getLong("NonExistingProperty");

For object types like String, BigDecimal, or BigInteger this default behavior can be changed: If the setThrowExceptionOnMissing() method is called with an argument of true, these methods will behave like their primitive counter parts and also throw an exception if the passed in property key cannot be resolved.

Note: Unfortunately support for the throwExceptionOnMissing property is not always consistent: The methods getList() and getStringArray() do not evaluate this flag, but return an empty list or array if the requested property cannot be found. Maybe this behavior will be changed in a future major release.

List handling

With getList() and getStringArray() the Configuration interface defines methods for dealing with properties that have multiple values. When a configuration source (e.g. a properties file, an XML document, or a JNDI context) is processed the corresponding Configuration implementation detects such properties with multiple values and ensures that the data is correctly stored.

When modifying properties the addProperty() and setProperty() methods of AbstractConfiguration also implement special list handling. The property value that is passed to these methods can be a list or an array resulting in a property with multiple values. If the property value is a string, it is checked whether it contains the list delimiter character. If this is the case, the string is split, and its single parts are added one by one. The list delimiter character is the comma by default. It is also taken into account when the configuration source is loaded (i.e. string values of properties will be checked whether they contain this delimiter). By using the setListDelimiter() method you can set it to a different character. Here are some examples:

// Change the list delimiter character to a slash
config.setListDelimiter('/');
// Now add some properties
config.addProperty("greeting", "Hello, how are you?");
config.addProperty("colors.pie",
  new String[] { "#FF0000", "#00FF00", "#0000FF" });
config.addProperty("colors.graph", "#808080/#00FFCC/#6422FF");

// Access data
String salut = config.getString("greeting");
List<Object> colPie = config.getList("colors.pie");
String[] colGraph = config.getStringArray("colors.graph");

String firstPieColor = config.getString("colors.pie");

In this example the list delimiter character is changed from a comma to a slash. Because of this the greeting property won't be split, but remains a single string. The string passed as value for the colors.graph property in opposite contains the new delimiter character and thus will result in a property with three values. Note that lists are of type Object. This is because the concrete class of the values of a property is not known. For instance, if you call addProperty("answer", 42), an Integer object will be stored in the configuration.

Of interest is also the last line of the example fragment. Here the getString() method is called for a property that has multiple values. This call will return the first value of the list.

If you want to change the list delimiter character for all configuration objects, you can use the static setDefaultListDelimiter() method of AbstractConfiguration. It is also possible to disable splitting of string properties at all for a Configuration instance by calling its setDelimiterParsingDisabled() method with a value of true.

Variable Interpolation

If you are familiar with Ant or Maven, you have most certainly already encountered the variables (like ${token}) that are automatically expanded when the configuration file is loaded. Commons Configuration supports this feature as well, here is an example (we use a properties file in this example, but other configuration sources work the same way; you can learn more about properties files in the chapter Properties files):

application.name = Killer App
application.version = 1.6.2

application.title = ${application.name} ${application.version}

If you now retrieve the value for the application.title property, the result will be Killer App 1.6.2. So per default variables are interpreted as the keys of other properties. This is only a special case, the general syntax of a variable name is ${prefix:name}. The prefix tells Commons Configuration that the variable is to be evaluated in a certain context. We have already seen that the context is the current configuration instance if the prefix is missing. The following other prefix names are supported by default:

Prefix Description
sys This prefix marks a variable to be a system property. Commons Configuration will search for a system property with the given name and replace the variable by its value. This is a very easy means for accessing the values of system properties in every configuration implementation.
const The const prefix indicates that a variable is to be interpreted as a constant member field of a class (i.e. a field with the static final modifiers). The name of the variable must be of the form <full qualified class name>.<field name>. The specified class will be loaded and the value of this field will be obtained.
env Variables can also reference OS-specific environment properties. This is indicated by the env prefix.
Here are some examples (again using properties syntax):
user.file = ${sys:user.home}/settings.xml

action.key = ${const:java.awt.event.KeyEvent.VK_CANCEL}

java.home = ${env:JAVA_HOME}

If a variable cannot be resolved, e.g. because the name is invalid or an unknown prefix is used, it won't be replaced, but is returned as is including the dollar sign and the curly braces.

Customizing interpolation

This sub section goes a bit behind the scenes of interpolation and explains some approaches how you can add your own interpolation facilities. Under the hood interpolation is implemented using the StrSubstitutor class of the text package of Commons Lang. This class uses objects derived from the StrLookup class for resolving variables. StrLookup defines a simple lookup() method that must be implemented by custom implementations; it expects the name of a variable as argument and returns the corresponding value (further details can be found in the documentation of Commons Lang). The standard prefixes for variables we have covered so far are indeed realized by special classes derived from StrLookup.

It is now possible to create your own implementation of StrLookup and make it available for all configuration objects under a custom prefix. We will show how this can be achieved. The first step is to create a new class derived from StrLookup, which must implement the lookup() method. As an example we implement a rather dull lookup object that simply returns a kind of "echo" for the variable passed in:

import org.apache.commons.lang.text.StrLookup;

public class EchoLookup extends StrLookup
{
    public String lookup(String varName)
    {
        return "Value of variable " + varName;
    }
}

Now we want this class to be called for variables with the prefix echo. For this purpose the EchoLookup class has to be registered at the ConfigurationInterpolator class with the desired prefix. ConfigurationInterpolator implements a thin wrapper over the StrLookup API defined by Commons Lang. It has a static registerGlobalLookup() method, which we have to call as follows:

// Place this code somewhere in an initialization section of your application
ConfigurationInterpolator.registerGlobalLookup("echo", new EchoLookup());
    

Each AbstractConfiguration object that is created after this line is executed will contain the new lookup class and can thus resolve variables of the form ${echo:my_variable}.

Each instance of AbstractConfiguration is associated with a ConfigurationInterpolator object. This object is created by the createInterpolator() method on first access of one of the interpolation features. By overriding this method even deeper intervention in the interpolation mechanism is possible. For instance a custom implementation can add further lookup objects to the interpolator, which are then only used by this configuration instance.

Using Expressions

In addition to the simple lookup mechanisms previously described, Commond Configuration provides ExprLookup which uses Apache Commons Jexl to allow expressions to be resolved wherever a StrLookup is allowed. This example shows an alternate way of obtaining a system property if the ExprLookup is configured.

user.file = ${expr:System.getProperty("user.home"}/settings.xml

ExprLookup is not enabled by default, it must be manually added or configured via DefaultConfigurationBuilder. Builds that use Maven 2 and reference Commons Configuration will not include a dependency on Jexl, so if this feature is used the dependency on Jexl must be manually added to the project.

When using DefaultConfigurationBuilder adding ExprLookup is straightforward.

<configuration>
  <header>
    <result/>
    <lookups>
      <lookup config-prefix="expr"
              config-class="org.apache.commons.configuration.interpol.ExprLookup">
        <variables>
          <variable name="System" value="Class:java.lang.System"/>
          <variable name"net" value="Class:java.net.InetAddress"/>
          <variable name="String" value="Class:org.apache.commons.lang.StringUtils"/>
        </variables>
      </lookup>
    </lookups>
  </header>
  <override>
    <xml fileName="${expr:System.getProperty('basePath') +
         String.lowercase(net.localHost.hostName) + '/testMultiConfiguration_default.xml'}"
         config-name="defaultConfig" delimiterParsingDisabled="true">
    </xml>
  </override>
</configuration>

The example above shows how to invoke static methods during expression evaluation. The next example shows mixing expression evaluation with a subordinate lookup to obtain the "basePath" system property. Note the difference in how the system property was obtained in the previous example.

<configuration>
  <header>
    <result/>
    <lookups>
      <lookup config-prefix="expr"
              config-class="org.apache.commons.configuration.interpol.ExprLookup">
        <variables>
          <variable name"net" value="Class:java.net.InetAddress"/>
          <variable name="String" value="Class:org.apache.commons.lang.StringUtils"/>
        </variables>
      </lookup>
    </lookups>
  </header>
  <override>
    <xml fileName="${expr:$[sys:basePath] +
         String.lowercase(net.localHost.hostName) + '/testMultiConfiguration_default.xml'}"
         config-name="defaultConfig" delimiterParsingDisabled="true">
    </xml>
  </override>
</configuration>