Apache Commons logo Commons Configuration

File-based Configurations

Often configuration properties are stored in files on the user's hard disk, e.g. in .properties files or as XML documents. In order to access this data, functionality is needed to select the configuration files, load them into memory, and write changes back to disk. The following sections describe how this can be done.

FileBasedConfigurationBuilder

In Commons Configuration a specialized configuration builder implementation is responsible for the creation of file-based configuration objects and the management of their associated data files: FileBasedConfigurationBuilder. Usage of this class follows the typical pattern for configuration builders, i.e. a builder instance is created providing the class of the Configuration object to be created, the configure() method is called with initialization parameters, and finally getConfiguration() returns an initialized instance of the configuration class. When configuring the builder the file to be loaded can be specified; if this was done, the Configuration object returned by the builder contains all properties read from the underlying file.

In order to define the file to be loaded, a parameters object implementing the FileBasedBuilderProperties interface can be passed to the builder's configure() method. Using this interface the location of the file to be loaded can be provided in multiple ways:

  • With the setFile() method the data file can be specified as a java.io.File object.
  • The setURL() method takes a java.net.URL as argument; the file will be loaded from this URL.
  • The methods setFileName() and setBasePath() allow specifying the path of the data file. The base path is important if relative paths are to be resolved based on this file.
  • With setPath() an absolute path to the file to be loaded can be provided.
A parameters object for file-based configurations is typically obtained from a Parameters instance. Here the fileBased() method or one of the methods returning parameter objects derived from FileBasedBuilderProperties can be used. In addition to the properties that define the location of the file to be loaded, the parameters object support a couple of other properties, too, which are mainly related to way how the file is resolved. This is described later on in this chapter.

As an example for using a file-based configuration builder, the following code fragment shows how a properties file can be read whose location is specified using a File object:

Parameters params = new Parameters();
// Read data from this file
File propertiesFile = new File("config.properties");

FileBasedConfigurationBuilder<Configuration> builder =
    new FileBasedConfigurationBuilder<Configuration>(PropertiesConfiguration.class)
    .configure(params.fileBased()
        .setFile(propertiesFile));
try
{
    Configuration config = builder.getConfiguration();
    // config contains all properties read from the file
}
catch(ConfigurationException cex)
{
    // loading of the configuration file failed
}

In this example a parameters object for file-based configurations is obtained from a Parameters instance. We could of course also have used a derived parameters class - when loading a properties file a parameters object for properties configurations would have been a logic choice. Here only a single parameter, the file to be loaded, is set; but remember that all other initialization parameters common to all configuration classes are available as well.

A configuration instance created this way stays connected to its builder. Especially, the builder stores the location of the underlying configuration file. This comes in handy if changes on the configuration object are to be written back to disk. For this purpose, FileBasedConfigurationBuilder provides a convenient save() method. Calling this method stores the current content of the associated configuration into its original configuration file, overwriting the existing file on disk. This is demonstrated in the following code fragment which continues from the previous example:

// Some manipulations on the configuration object
config.addProperty("newProperty", "new");
config.setProperty("updateProperty", "changedValue");

// Make changes persistent
try
{
    builder.save();
}
catch(ConfigurationException cex)
{
    // saving of the configuration file failed
}

Note that the save() method of the builder does not expect a configuration object as parameter. It always operates on the instance managed by this builder. Because of this relationship it is typically better to store the builder object rather than the configuration. The configuration can always be obtained via the builder's getConfiguration() method, but operations related to the configuration file are only available through the builder.

In addition to the save() method, FileBasedConfigurationBuilder offers functionality for automatically saving changes on its managed configuration. This can be used to ensure that every modification of a configuration object is immideately written to disk. This feature is enabled via the setAutoSave() method as shown in the following example:

FileBasedConfigurationBuilder<Configuration> builder =
    new FileBasedConfigurationBuilder<Configuration>(PropertiesConfiguration.class)
    .configure(params.fileBased()
        .setFile(new File("config.properties")));
// enable auto save mode
builder.setAutoSave(true);

Configuration config = builder.getConfiguration();
config.setProperty("colors.background", "#000000"); // the configuration is saved after this call

Be careful with this mode when you have many updates on your configuration. This will lead to many I/O operations, too. Behind the scenes, automatic saving is implemented via the event notification mechanism available for all configuration objects. A specialized event listener is registered at the builder's managed configuration object which triggers the save() method every time an update event is received.

Making it easier

The code fragments presented so far in this chapter and the previous one show that the fluent API offered by configuration builders in many cases allows the creation and initialization of a configuration builder in a single expression. Nevertheless, especially in simple cases when no complex initialization is required, this approach tends to become verbose. For instance, if just a configuration is to be loaded from a file, you always have to create a file-based parameters object, initialize it, create a builder, and pass the parameters to its configure() method.

To support this frequent use case in a more convenient way, the Configurations class exists. This class contains a bunch of convenience methods that simplify the creation of many standard configurations from different sources like files or URLs. Using this class, the code for the creation of a configuration builder can be reduced. The example for loading a properties configuration presented above becomes now:

Configurations configs = new Configurations();
// Read data from this file
File propertiesFile = new File("config.properties");

FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
    configs.propertiesBuilder(propertiesFile);

From this builder the properties configuration can be obtained in the usual way. It is even possible to by-pass the builder at all:

Configurations configs = new Configurations();
// Read data from this file
File propertiesFile = new File("config.properties");

PropertiesConfiguration config = configs.properties(propertiesFile);

Here behind the scenes a configuration builder is created and initialized; then its managed configuration is queried and returned to the caller. A ConfigurationException is thrown if an error occurs. Skipping the configuration builder and accessing the configuration directly is recommended only for simple use cases. A builder typically offers more flexibility for the handling and management of configuration objects.

In these examples, a java.io.File object was used to access configuration data. There are overloaded methods for specifying the data to be loaded in alternative ways: using URLs or file names/paths. In addition to properties configurations, the Configurations class supports a couple of other frequently used configuration formats. For instance, the methods xml() and xmlBuilder() provide easy access to XML documents.

Even if there is no direct support for a specific configuration implementation, with the generic fileBased() or fileBasedBuilder() methods, access to all kinds of file-based configurations can be simplified. We take the PropertyListConfiguration class as an example for which no specific access methods exist. The code fragment below shows how a builder for such a configuration can be constructed using a generic method:

Configurations configs = new Configurations();
// Read data from this URL
URL sourceURL = ...;

FileBasedConfigurationBuilder<PropertyListConfiguration> builder =
    configs.fileBasedBuilder(PropertyListConfiguration.class, sourceURL);
PropertyListConfiguration config = builder.getConfiguration();

Configurations instances are thread-safe and can be stored centrally by an application. So they can be used as a central configuration factory - of course, with limited flexibility; this is the price to be payed for simplicity. However, these restrictions can be partly circumvented by making use of default initialization parameters. An instance is associated with a Parameters object which is used to construct parameter objects for the created configuration builders. By assigning default parameters to this object the default settings used for the created builders can be tweaked. Note however, that the class typically creates only generic parameter objects; file-based parameters rather than, say, specialized parameters for properties configurations. This makes default settings only possible for basic parameters.

File Operations on Configurations

With FileBasedConfigurationBuilder a single configuration file is assigned to a configuration instance. For some use cases a more flexible approach is required. For instance, a modified configuration is to be stored in another file, or multiple configuration files should be loaded into the same instance. To achieve this, the underlying mechanisms for dealing with files have to be used.

I/O operations on files are controlled by the FileHandler class. Basically, this class connects a location of a configuration file (and some other meta information like the file's encoding) with an object which can read data from or write data to this location. FileHandler defines the typical properties for defining the file to be loaded, i.e. the location can be specified as a URL, a File, an absolute path, etc.

The object which actually reads and writes the data is represented by the FileBased interface. This is a pretty lean interface consisting of only two methods for reading data from a Reader and writing data to a Writer. All configuration implementations that can be initialized from configuration files implement this interface; but in theory the FileHandler could interact with other objects implementing FileBased as well.

FileHandler has the two methods load() and save(). They work as follows:

  • The location of the managed file is evaluated, and a corresponding stream is opened. Depending on the way the location was specified, this could mean opening a connection on a URL, opening a stream to a File or an absolute path name, resolving relative file names, etc.
  • The resulting stream is then passed to the associated FileBased's read() or write() method.

Next to these simple load() and save() methods a number of overloaded methods exists which expect additional parameters defining the source or target of the operation. For instance, there is a load(URL) method which reads data directly from the passed in URL ignoring the location stored in the FileHandler instance. In fact, there are overloaded methods for all the supported variants for defining a file. When making use of these methods the following points have to be kept in mind:

  • The location stored in the FileHandler instance is not changed; it is completely by-passed by these methods. Only explicit calls to the various setter methods modify the location.
  • The load() methods eventually call the target object's read() method, no matter if it has already been called before. For configuration objects as target this means that the configuration is not cleared before new data is loaded. (Actually a FileHandler is not aware which kind of target object it is serving; so it has no chance to clear it first.) This behavior makes it easy to construct union configurations by simply executing multiple load operations. But if you want to reuse a configuration object and load a different file, remember to call the clear() method first to ensure that old properties are wiped out.

When constructing a FileHandler instance the FileBased object it operates on has to be passed to the constructor. With this information we are now able to look at a concrete example. The goal is to create a configuration for a properties file, read in another properties file (so that a union of the properties is constructed), and finally write the resulting configuration to a new file. The code can look as follows (the handling of exceptions has been omitted):

// Read first file directly via the builder
FileBasedConfigurationBuilder<PropertiesConfiguration> builder =
    new FileBasedConfigurationBuilder<PropertiesConfiguration>(PropertiesConfiguration.class)
    .configure(params.fileBased()
        .setFile(new File("config.properties")));
PropertiesConfiguration config = builder.getConfiguration();

// Create a file handler and associate it with the configuration
FileHandler handler = new FileHandler(config);

// Load another configuration source, for instance from a relative path
handler.load("user.properties");

// Store the resulting configuguration in a new file
File out = new File("union.properties");
handler.save(out);

The FileHandler class is thread-safe; it is no problem for instance to define a file location in one thread and then call load() on another thread. It is also possible to have multiple FileHandler objects associated with the same target object. Here concurrent I/O operations could cause problems. Therefore, FileHandler checks whether the target object implements the SynchronizerSupport interface. If this is the case, proper synchronization for load and save operations can be performed. Because all configuration implementations implement SynchronizerSupport they can safely be used together with FileHandler.

Another important class related to file access is FileLocator. An instance stores all information required for resolving a file to be accessed. FileHandler uses a FileLocator instance to maintain this part of file-related information. If you need to customize the access to configuration files, you sometimes have to deal with FileLocator objects because the files to be operated on are described in terms of such objects.

Customizing File Access

When working with file-based configurations application code has multiple ways to specify the location of the file to be loaded. If a URL is provided, the source file to be loaded is defined in a pretty unambiguous way. If relative file names or paths are used, situation is less obvious.

Commons Configuration provides two mechanisms to customize the way configuration files are accessed:

  • File systems
  • File location strategies
They are described in the following sub sections.

File Systems

In its default mode of operation Commons Configuration supports retrieving and storing configuration files either on a local file system or via http. However, Commons Configuration provides support for allowing other File System adapters. All file access is accomplished through the FileSystem class so accessing files using other mechanisms is possible.

Commons Configuration also provides a second FileSystem implementation which allows retrieval using Apache Commons VFS. As of this writing Commons VFS supports 18 protocols for manipulating files.

The FileSystem used by Commons Configuration can be set in the builder's parameter object, together with other properties defining the file to be loaded. When working with CombinedConfigurationBuilder it is also possible to define the file system in the configuration definition file to be processed by the builder - in both a global way and for each referenced sub configuration. The following listing shows a configuration definition file for a combined builder making use of this functionality. Per default, the VFSFileSystem is used, but the included XML configuration is loaded via a DefaultFileSystem instance:

<configuration>
  <header>
    <fileSystem config-class="org.apache.commons.configuration2.io.VFSFileSystem"/>
  </header>
  <override>
    <xml fileName="settings.xml" config-name="xml">
      <fileSystem config-class="org.apache.commons.configuration2.io.DefaultFileSystem"/>
    </xml>

    <!-- Other sources omitted -->
  </override>
</configuration>

Commons VFS allows options to the underlying file systems being used. Commons Configuration allows applications to provide these by implementing the FileOptionsProvider interface and registering the provider with the FileSystem. FileOptionsProvider has a single method that must be implemented, getOptions(), which returns a Map containing the keys and values that the FileSystem might use. The getOptions() method is called as each configuration uses VFS to create a FileOjbect to access the file. The map returned does not have to contain the same keys and/or values each time it is called. For example, the value of the currentUser key can be set to the id of the currently logged in user to allow a WebDAV save to record the userid as a file attribute.

File Location Strategies

Before a file can be accessed it has to be located first. In the 1.x versions of Commons Configuration, there was a hard-coded algorithm for looking up configuration files defined by a file name and an optional base path in various places. Starting with version 2.0, it is now possible to adapt this algorithm. The key to this is the FileLocationStrategy interface. The interface defines a single method:

URL locate(FileSystem fileSystem, FileLocator locator);

The purpose of this method is to resolve a file described by the passed in FileLocator object and return a URL for it. If required, the provided FileSystem can be used. The URL yielded by a successful locate operation is directly used to access the affected file. If the file could not be resolved, a FileLocationStrategy implementation should not throw an exception, but return null instead. This allows multiple strategies to be chained so that different locations can be searched for the file one after the other.

Commons Configuration ships with a set of standard FileLocationStrategy implementations. They are pretty specialized, meaning that a single implementation focuses on a very specific search algorithm. The true power lies in combining these strategies in a way suitable for an application or use case. The following table describes the available FileLocationStrategy implementations:

Location Strategy class Description
ProvidedURLLocationStrategy Directly returns the URL stored in the passed in FileLocator. Unless an application needs some special URL transformation, a file locator's URL - if defined - can typically be used directly to access a file. So it makes sense to use this strategy at the very beginning of your chain of strategies.
FileSystemLocationStrategy Passes the base path and the file name stored in the passed in FileLocator to the locateFromURL() method of the current FileSystem. This gives the file system the opportunity to perform a special resolution.
AbsoluteNameLocationStrategy Checks whether the file name stored in the passed in FileLocator is actually an absolute path name pointing to an existing file. If this is the case, the URL to this file is returned.
BasePathLocationStrategy This strategy creates a concatenation of the base path and file name stored in the passed in FileLocator (of course, only if both are defined). If this results in a path pointing to an existing file, this file's URL is returned.
HomeDirectoryLocationStrategy Searches for the referenced file in the current system user's home directory. It is also possible to specify a different directory in which the strategy should search; the path to the target directory can be passed to the constructor.
ClasspathLocationStrategy Interprets the file name stored in the passed in FileLocator as a resource name and tries to look it up on the current classpath.
CombinedLocationStrategy This is a kind of meta strategy which allows combining an arbitrary number of other FileLocationStrategy objects. At construction time a collection with sub strategies has to be passed in. In its implementation of the locate() method, the strategy iterates over all its sub strategies (in the order they were passed to the constructor) until one returns a non null URL. This URL is returned.

As an example, consider that an application wants configuration files to be looked up (in this order)

  • by their URL
  • by the file system (which will evaluate base path and file name)
  • on the classpath
Then a concrete location strategy could be constructed as follows:
List<FileLocationStrategy> subs = Arrays.asList(
  new ProvidedURLLocationStrategy(),
  new FileSystemLocationStrategy(),
  new ClasspathLocationStrategy());
FileLocationStrategy strategy = new CombinedLocationStrategy(subs);

This strategy can now be passed to a file-based configuration builder. If no strategy is passed to a builder, a default one is used. This default strategy is almost identical to the hard-coded search algorithm that was used in earlier versions of Commons Configuration. In fact, the pre-defined basic FileLocationStrategy implementations were extracted from this algorithm.

Because the FileLocationStrategy interface is very simple it should be easy to create a custom implementation. The specific search algorithm just has to be coded into the locate() method. Then this custom strategy implementation can be combined with other standard strategies by making use of a CombinedLocationStrategy.