Configuration

Goals

Concepts

Library

Dependencies

Lesson

A well-written application is flexible and can be customized to the needs of its users. The location where an application stores its data files, the name of a user, and formatting options are all examples of things that are commonly configurable within an application.

Selected configuration options are best stored in a separate location from the code—after all, it would hardly be user-friendly to require the software to be recompiled and packaged each time a configuration option were changed. Instead configured values are commonly placed in a separate configuration file in the user's file system. Typical formats include XML, which you've studied, and Java Properties File Format, which you'll learn about in a future lesson.

The term preferences is also used frequently when talking about a program's configuration. In many cases preferences refers to the user's preferred options such as a color scheme, which are likely changeable; while configuration refers to the program's installation options, which may not be alterable by the user. In practice the line between the two can be hard to draw, and sometimes the terms are used interchangeably.

Java Preferences API

Java addressed need for a cross-platform approach to storing program configuration information when it added the Preferences API to the JDK. A program saves and/or retrieves named values from the Preferences API without regard to where the values are actually stored in the system. Preferences are arranged in a hierarchy of nodes, which is simply a way to categorize preferences similar to how the Java package system works.

Preferences Nodes

The main access to Java preferences is through the java.util.prefs.Preferences abstract class, which represents a preferences node. Each value is associated with a key, making a Preferences node similar to a map. To store a string value, call Preferences.put(String key, String value). The Preferences.get(String key, String def) method returns the string value associated with a key. There are also preference storage and retrieval methods for specific types, such Preferences.putInt(String key, int value) and Preferences.getInt(String key, int def) which access a preference stored represented by an int value.

System and User Preferences Trees

Java Preferences are divided into two hierarchical trees of preference nodes: system preferences, which apply to all users; and per-user preferences. To locate a preferences node in which to store information, first start with the root node of one of these two trees using Preferences.systemRoot() or Preferences.userRoot(), respectively.

A particular Preferences node can be located by providing its path, which functions similarly to path names in the file system. Each component represents a Preferences node, with the forward slash / character used as a separator. Calling Preferences.node(String pathName) with a relative path will  resolve the path to that of the Preferences instance on which node(…) was called. An absolute path will be resolved to the root Preferences node. The named node will be created if it doesn't already exist.

Storing and retrieve a Hello World greeting for the user in preferences.

final Preferences userRoot = Preferences.userRoot();
final Preferences exampleNode = userRoot.node("com/example"); //relative to root; here same as "/com/example"
final Preferences appPreferences = exampleNode.node("helloworld"); //relative to "/com/example"
appPreferences.put("greeting", "Hello, World!");

final String greeting = Preferences.userRoot().node("/com/example/helloworld").get("greeting", "Hi");
System.out.println(greeting); //prints "Hello, World!", or "Hi" if no value was present

Apache Commons Configuration

On the other end of the spectrum, from simple to complex, lies the other established API for accessing configuration information: Apache Commons Configuration. The Apache Software Foundation has a large group of general utility libraries that form part of the Apache Commons. The Apache Commons Configuration library is just one of many projects in this group; others include Apache Commons Collections, Apache Commons Imaging, and Apache Commons Math.

Many of the Apache Commons projects are large, interconnected libraries that have evolved comprehensive solutions. Apache Commons Configuration is no exception: it comes with support for storing configuration information in many formats, including XML. Version 2 of the library has recently been completely rewritten to be more modular and flexible.

Configuration

Regardless of the underlying storage format for configuration information, the main interface for access to configuration values is org.apache.commons.configuration2.Configuration. This interface allows reading configuration data as well as setting new configuration values. All methods related solely to retrieving values are found in the Configuration parent interface org.apache.commons.configuration2.ImmutableConfiguration. Retrieving a string value for example is performed using ImmutableConfiguration.getString(String key). Access to other types of data is achieved using methods such as ImmutableConfiguration.getInt(String key). You can also determine if a value exists for a key using ImmutableConfiguration.containsKey(String key).

ImmutableConfiguration methods that retrieve objects will return null if a requested value is not available. For primitive types, a java.util.NoSuchElementException is thrown if a value is missing. This issue can be sidestepped altogether by using one of the method variations that allow the designation of a default value, such as ImmutableConfiguration.getInt(String key, int defaultValue).

ConfigurationBuilder

While it is possible to manually create and configure the Configuration subclass appropriate for your configuration file format, Apache Commons Configuration provides the org.apache.commons.configuration2.builder.ConfigurationBuilder<T extends ImmutableConfiguration> interface for provides a specification for the Configuration you would like to create. ConfigurationBuilder is not simply a throwaway builder like the builder design pattern you've seen in the past. Instead it functions more like the factory design pattern; once you configure a ConfigurationBuilder as you like it, you use it to get a Configuration instance whenever you need one using the ConfigurationBuilder.getConfiguration() method.

There are many concrete ConfigurationBuilder subclasses that relate to the specific type of configuration source desired. Here are a few of the most common ones:

org.apache.commons.configuration2.builder.BasicConfigurationBuilder<T extends ImmutableConfiguration>
Base ConfigurationBuilder implementation; provides the BasicConfiguration.configure(BuilderParameters... params) method.
org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder<T extends FileBasedConfiguration>
Subclass of BasicConfigurationBuilder for creating configurations based on file content such as XML.
org.apache.commons.configuration2.builder.ReloadingFileBasedConfigurationBuilder<T extends FileBasedConfiguration>
Specialized FileBasedConfigurationBuilder that allows reloading upon detection of configuration file changes.
BuilderParameters

A ConfigurationBuilder must itself be configured before use, and this is done using an instance of the org.apache.commons.configuration2.builder.BuilderParameters interface. As with ConfigurationBuilder, there is a hierarchy of BuilderParameters implementations and interfaces, such as:

org.apache.commons.configuration2.builder.BasicBuilderParameters
The base class for other BuilderParameter implementations using configuration properties defined in org.apache.commons.configuration2.builder.BasicBuilderProperties<T>.
org.apache.commons.configuration2.builder.fluent.FileBasedBuilderParameters
Interfaces for configuring file-based configuration builders using configuration properties defined in org.apache.commons.configuration2.builder.FileBasedBuilderProperties<T>.
org.apache.commons.configuration2.builder.fluent.XMLBuilderParameters
Configures access to a configuration stored in an XML file using configuration properties defined in org.apache.commons.configuration2.builder.XMLBuilderProperties<T>.
org.apache.commons.configuration2.builder.fluent.PropertiesBuilderParameters
Configures access to a configuration stored in a Java properties file using configuration properties defined in org.apache.commons.configuration2.builder.PropertiesBuilderProperties<T>. You will learn about Java properties files in an upcoming lesson.
org.apache.commons.configuration2.builder.fluent.DatabaseBuilderParameters
Configures access to a configuration stored in a database using configuration properties defined in org.apache.commons.configuration2.builder.DatabaseBuilderProperties<T>.

Many of the BuilderParameters types listed above are interfaces; actual configuration of the parameters requires some concrete implementation. The implementations of BuilderParameters use a fluent API, allowing chaining of parameter-setting calls as shown in the example below.

Parameters

Creating and access BuilderParameter instances is made slightly less easier through use of the org.apache.commons.configuration2.builder.fluent.Parameters utility class. Parameters has various factory methods that produce instances of the BuilderParameters interfaces discussed above, including:

The following example shows how to create a simple configuration stored in XML format.

Creating a simple configuration stored in XML format in ~/.myapp/config.xml.
public class MyApp {

  //configuration factory
  private final FileBasedConfigurationBuilder<XMLConfiguration> configBuilder;

  /** Returns the configuration, loading it if needed.
   * @return The application configuration.
   * @throws ConfigurationException if there is an error loading the configuration.
   */
  protected Configuration getConfiguration() {
    return configBuilder.getConfiguration(); //load the configuration when needed
  }

  /** Constructor. */
  public MyApp() {
    final Path configPath = Paths.get(System.getProperty("user.home"), "myapp", "config.xml");
    configBuilder = new FileBasedConfigurationBuilder<XMLConfiguration>(XMLConfiguration.class)
        .configure(new Parameters().xml()
            .setFile(configPath.toFile())
            .setThrowExceptionsOnMissing(true));
  }

  public void doSomething() {
    if(getConfiguration().getInt("fooEnabled", true)) {
      System.out.println("Foo is enabled.")
    }
  }

}

XML Configurations

A configuration based on an XML files is a hierarchical configuration, supporting a tree-like hierarchy of configuration keys. In Apache Commons Configuration these types of configurations implement org.apache.commons.configuration2.HierarchicalConfiguration<T>, which allows access of subtrees of configuration nodes within a configuration file. Hierarchical configurations can be used like basic configurations, using the full stop . character to indicate levels in the hierarchy.

XML configurations reflect the hierarchical nature of XML elements. Consider the XML file used in a previous lesson to illustrate storing information about a vehicle. The content of element nodes such <make> can be accessed in the Apache Commons Configuration API by indicating all the XML element hierarchy names separated by the full stop . character, or in this case "type.make". Note that XML configurations ignore the root element of the hierarchy, in this case the document element <car>.

Example XML file for storing information about a vehicle, used as a configuration file.
<car vin="123456789">
  <color>blue</color>
  <type>
    <make>Camaro</make>
    <model>Z28</model>
  </type>
</car>
final ImmutableConfiguration config = getConfiguration(); //see example above
System.out.println(configuration.getString("color")); //prints "blue"
System.out.println(configuration.getString("type.make")); //prints "Camaro"
System.out.println(configuration.getString("type.model")); //prints "Z28"

Interpolation

By default the configuration implementations support string interpolation by automatically replacing placeholders in configuration values that appear in the form ${variable}. Normally the variable is another configuration key, allowing you to create configuration values made up of other configuration values. For example you might make the name of a car contain the car's make and model using interpolation:

Example XML file for storing information about a vehicle, used as a configuration file.
<car vin="123456789">
  <color>blue</color>
  <type>
    <make>Camaro</make>
    <model>Z28</model>
  </type>
  <name>John's ${type.make} ${type.model}</name>
</car>
final ImmutableConfiguration config = getConfiguration(); //see example above
System.out.println(configuration.getString("name")); //prints "John's Camaro Z28"

Apache Commons Configurations support several prefixes that indicate variables from contexts other than the current configuration

Prefix Context Example
sys System properties. ${sys:user.home}
const Constant (i.e. static final) variables in the form com.example.package.Class.CONSTANT. ${const:java.lang.Math.PI}
env Operating system environment variables. ${env:JAVA_HOME}

Modifying Configurations

The most common use case for a configuration framework is to retrieve static configuration data that has been prepared for the application. But in some situations, such as when you allow a user to configure parts of the application, you may want to update configuration values and save them back to the disk. As already mentioned the Configuration interface is mutable, allowing you to change a value using Configuration.setProperty(String key, Object value) or add a property using Configuration.addProperty(String key, Object value). You can even remove a property altogether using Configuration.clearProperty(String key).

After modifying a configuration you will likely want to persist it to disk. If you have followed the recommendations above, you will have a separate configuration builder which serves as a configuration factory. File-based configuration builders also serve as as a persistence mechanism for your configuration via the FileBasedConfigurationBuilder.save() method.

Automatic Reloading

Most often an application will load its configuration once and assume that it has complete control over the configuration data until the program ends. Sometimes a program will want to detect if the configuration file has been modified externally, either by a user or another program, and reload the configuration data when it changes in the file system. This can be configured using the ReloadingFileBasedConfigurationBuilder mentioned above.

A ReloadingFileBasedConfigurationBuilder contains a org.apache.commons.configuration2.reloading.ReloadingController which governs the process of checking for file changes and clearing the configuration for reloading when appropriate. Internally the ReloadingController uses a org.apache.commons.configuration2.reloading.ReloadingDetector strategy for determining when reloading should take place. The most commonly used ReloadingDetector is a org.apache.commons.configuration2.reloading.FileHandlerReloadingDetector, which detects that a configuration file needs reloading based on a change in its last-modified timestamp.

Apache Commons Configuration uses a trigger that determines when a ReloadingController should ask its ReloadingDetector to check for configuration changes. The library comes with a org.apache.commons.configuration2.reloading.PeriodicReloadingTrigger which uses a background service to trigger the ReloadingController every so often. You can start and stop this activity using PeriodicReloadingTrigger.start() and PeriodicReloadingTrigger.stop(), respectively.

Setting up a trigger to reload a configuration when the configuration file changes external to the program.

configBuilder = …
final PeriodicReloadingTrigger reloadingTrigger = new PeriodicReloadingTrigger(
    configBuilder.getReloadingController(), null, 1, TimeUnit.MINUTES);
reloadingTrigger.start(); //start reload detection
//call reloadingTrigger.shutdown() at the end of the program

Review

Gotchas

In the Real World

Think About It

Self Evaluation

Task

When Booker starts, in addition to printing the current date and time, print the number of years, days, hours, and minutes since the last time the program was run.

In a previous lesson you stored the Booker user name in an XML configuration file, parsing it manually using the XML DOM. Switch to using the Apache Commons Configuration library to load this same configuration file, and read the user information using the appropriate Apache Commons Configuration API calls.

See Also

References

Resources

Acknowledgments