Using a Configuration Factory

This section explains how a ConfigurationFactory object is setup that provides access to a collection of different configuration sources. It also discusses using Hierarchical and Structured datasets.

The configuration definition file

When a single configuration file is the only source of configuration data it is very simple to load it using a PropertiesConfiguration object (this is the class that handles files of this type). But because we think that later other sources will be added (otherwise this example section would be too silly) we will use a ConfigurationFactory object to load it.

ConfigurationFactory allows to combine multiple configuration sources. The properties defined in these sources can then be accessed as if they were defined in a single configuration file. To make use of this we have to create a XML file which tells the factory from which sources the properties are to be collected. The following listing shows the content of this file:

<?xml version="1.0" encoding="ISO-8859-1" ?>

<configuration>
  <properties fileName="usergui.properties"/>
</configuration>

Definition files for ConfigurationFactory are normal XML files. The root element must be named configuration. It can contain different sub elements that specify the configuration sources to load. The properties element is one of these; it is used to include properties files.

For this example we store the definition file for ConfigurationFactory in the same directory as the properties file and call it config.xml.

Setting up a ConfigurationFactory

Now we have to create a ConfigurationFactory object and let it read this definition file. This is quite simple: Just create a new instance and set the name of the definition file with the setConfigurationFileName() method.

ConfigurationFactory factory = new ConfigurationFactory();
URL configURL = new File("config.xml").toURL();
factory.setConfigurationFileName(configURL.toString());
Configuration config = factory.getConfiguration();

As this code fragment shows the file name passed to the factory can be a full URL. This is also the recommended way of specifying the file because it provides the greatest flexibility and a consistent way of handling relative file names found in the definition file.

Here we assumed the configuration definition file to be located in the current directory. It is also possible (and probably a better approach) to load the file from the class path. This could be done as follows:

ConfigurationFactory factory = new ConfigurationFactory();
URL configURL = getClass().getResource("/config.xml");
factory.setConfigurationURL(configURL);
Configuration config = factory.getConfiguration();

Accessing properties

Whatever way we used to load the configuration factory, we should now have a Configuration object that was returned by the factory's getConfiguration() method. This object defines a large amount of methods for querying properties. The most generic one is getProperty(), which returns an object, but there are lots of other methods that return other datatypes. In our example the property we have defined has a string value, so we would use the getString() method.

All of these methods have in common that they expect a property key as argument. Here the name of the searched property must be provided in exact the same way as it is contained in the properties file. To obtain the value of the background color property that is defined in the properties file shown earlier the following code fragment can be used:

String backColor = config.getString("color.background");

Multiple configuration sources

Using ConfigurationFactory to collect configuration sources does not make much sense if there is only one source to be loaded. So let's add another one! This time we will embedd a XML file.

A XML configuration file

Many applications use the popular XML format for storing configuration information. So it is no wonder that Configuration also supports this type of configuration sources. In general each XML document can be used to define configuration settings. We start here with a rather simple one:

<?xml version="1.0" encoding="ISO-8859-1" ?>
<gui-definition>
  <colors>
    <background>#808080</background>
    <text>#000000</text>
    <header>#008000</header>
    <link normal="#000080" visited="#800080"/>
  </colors>
  <rowsPerPage>15</rowsPerPage>
</gui-definition>

(As becomes obvious, this tutorial does not bother with good design of XML documents, the example file should rather demonstrate the different ways of accessing properties.) This XML document should be stored under the name gui.xml in the same directory as the so far created configuration files.

Overriding properties

To make this XML document part of our global configuration we have to modify our configuration definition file to also include the new file. For XML documents the element xml can be used so that we have now:

<?xml version="1.0" encoding="ISO-8859-1" ?>

<configuration>
  <properties fileName="usergui.properties"/>
  <xml fileName="gui.xml"/>
</configuration>

The code for setting up the ConfigurationFactory object remains the same. The following fragment shows how the new properties can be accessed:

String backColor = config.getString("color.background");
String textColor = config.getString("color.text");
String linkNormal = config.getString("color.link[@normal]");
int rowsPerPage = config.getInt("rowsPerPage");

This listing demonstrates some important points of constructing keys for accessing properties load from XML documents:

  • Nested elements are accessed using a dot notation. In the example document there is an element <text> in the body of the <color> element. The corresponding key is color.text.
  • The root element is ignored when constructing keys. In the example you do not write gui-definition.color.text, but only color.text.
  • Attributes of XML elements are accessed in a XPath like notation.

There is one problem with the example code fragement: It queries the value of the color.background property, but this is defined in both the properties and the XML file and - to make things worse - with different values. Which value will be returned by the corresponding call to getString()?

The answer is that the configuration sources are searched in the order they are defined in the configuration definition file. Here the properties file is included first, then comes the XML file. Because the color.background property can be found in the properties file the value specified there will be returned (which happens to be #FFFFFF).

It might not be obvious why it makes sense to define the value of one and the same property in multiple configuration sources. But consider the following scenario: An application comes with a set of default properties and allows the user to override some or all of them. This can now easy be realized by saving the user's settings in a file and the default settings in another. Then in the configuration definition file the file with the user settings is included first and after that the file with the default values. The application code that queries these settings need not be aware whether a property was overriden by the user. The ConfigurationFactory takes care that properties defined in the first file (the user file) are found; other properties which the user has not changed will still be returned from the second file (the defaults file).

Optional configuration sources

The example above with two configuration sources - one for user settings and one with default values - raises an interesting question: What will happen if the user has not defined specific properties yet? Or what if a new user starts our application for the first time and thus no user specific properties exist?

The default behavior of ConfigurationFactory is to throw a ConfigurationException exception if one of the sources defined in the configuration definition file cannot be loaded. For our example this behavior is not desired: the properties file with specific user settings is not required. If it cannot be loaded, the example application will still work because a complete set of configuration properties is defined in the second file.

ConfigurationFactory supports such optional configuration sources. For this purpose in the definition of a (file based) configuration source the optional attribute can be placed. An example of this is shown below:

<?xml version="1.0" encoding="ISO-8859-1" ?>

<configuration>
  <properties fileName="usersettings.properties" optional="true"/>
  <properties fileName="default.properties"/>
</configuration>

In this configuration definition file the first properties file with user specific settings is marked as optional. This means that if it cannot be loaded, ConfigurationFactory will not throw an exception, but only write a warning message to its logger. Note that the optional attribute is absent for the second properties file. Thus it is mandatory, and the getConfiguration() method of ConfigurationFactory would throw an exception if it could not be found.