Apache Commons logo Commons Configuration

Multi-tenant Configurations

In a multi-tenant environment a single instance of the application will run on behalf of many clients. Typically, this will require that each client has its own unique configuration. The easiest approach to enable an application to be multi-tenant is for it to not really be aware of it at all. This requires that the configuration framework takes on some of the responsibility for making the application work correctly.

One approach to enable this support in a web application might be to use a Servlet Filter and then use the Log4j or SLF4J MDC to save the attributes needed to identify the client. These attributes can then be used to identify the specific client configuration to be used. The classes described below use this technique to allow configurations to transparently provide the configuration appropriate for the clients.

MultiFileConfigurationBuilder

MultiFileConfigurationBuilder is a specialized configuration builder implementation which internally manages a set of builders for file-based configurations. In the initialization parameters of this builder a file name pattern has to be passed. The pattern can contain keys that will be resolved using the ConfigurationInterpolator every time the builder's getConfiguration() method is called. The resolved pattern is then interpreted as the name of a configuration file to be loaded by a newly created FileBasedConfigurationBuilder.

The file-based configuration builder is stored in an internal map together with the naming pattern. The next time the same pattern is accessed, it can be retrieved from the map and used directly. If the evaluation of the pattern changes, a new file-based configuration builder is created, and a new configuration file is loaded.

When used in a combined configuration it is often acceptable for a file matching a particular pattern to be missing. The behavior of MultiFileConfigurationBuilder regarding exceptions thrown for missing configuration files can be controlled using the boolean allowFailOnInit argument accepted by the most generic constructor. If here the value true is passed, exceptions while loading a configuration file are ignored. Instead, an empty configuration of the configured type is created for this pattern.

DynamicCombinedConfiguration

The CombinedConfigurationBuilder class allows multiple configuration files to be merged together. However, it will not collaborate with a MultiFileConfiguration properly since the underlying managed configuration will be different depending on the resolution of the location pattern. DynamicCombinedConfiguration solves this by creating a new CombinedConfiguration for each pattern.

Sample Configuration

This sample configuration illustrates how to use CombinedConfigurationBuilder in combination with MultiFileConfigurationBuilder to create a multi-tenant configuration.

<configuration>
  <header>
    <result loggerName="TestLogger"
            config-class="org.apache.commons.configuration2.DynamicCombinedConfiguration"
            keyPattern="$${sys:Id}">
      <nodeCombiner config-class="org.apache.commons.configuration2.tree.MergeCombiner"/>
      <expressionEngine
          config-class="org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine"/>
    </result>
  </header>
  <override>
    <multiFile filePattern="testMultiConfiguration_$${sys:Id}.xml"
               config-name="clientConfig" config-optional="true"
               config-forceCreate="true"
               schemaValidation="true">
       <expressionEngine
          config-class="org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine"/>
    </multiFile>
    <xml fileName="testMultiConfiguration_default.xml"
         config-name="defaultConfig" schemaValidation="true">
      <expressionEngine
          config-class="org.apache.commons.configuration2.tree.xpath.XPathExpressionEngine"/>
    </xml>
  </override>
</configuration>

In this example configuration definition file for a CombinedConfigurationBuilder two configuration sources are declared:

  • A multi-file source with a file pattern containing a system property. This means that depending on the current value of this system property a different configuration file is loaded. Because this source is declared as optional it is legal that for certain values of the property no configuration file exists.
  • An XML configuration source with common setting independent on the value of a system property. This source may also contain default values in case no configuration file could be loaded by the multi-file source.

Of special importance is the declaration of the result configuration in form of the <result> element in the header section. Here the config-class attribute specifies that an instance of DynamicCombinedConfiguration is created rather than a default CombinedConfiguration object. Also, the value of the keyPattern property is set which has to conform to the pattern used by the multi-file source.

Note how the variables have multiple '$'. This is how variables are escaped and is necessary because the variables will be interpolated multiple times. Each attempt will remove the leading '$'. When there is only a single '$' in front of the '{' the interpolator will then resolve the variable. The first extra '$' is necessary because CombinedConfigurationBuilder will interpolate any variables in the configuration. In the case of the multi-file configuration item two leading '$' characters are necessary before the variable because it will be interpolated by both CombinedConfigurationBuilder and DynamicCombinedConfiguration before MultiFileConfigurationBuilder gets the chance to evaluate it. Although in this example one would not expect system properties to change at runtime, other types of lookups such as the MDCStrLookup provided with SLF4J require that the variables be evaluated as the configuration is being accessed instead of when the configuration file is processed to behave as desired.

Builder Configuration Related to Multi-file Configurations

When setting up a MultiFileConfigurationBuilder a special object with initialization parameters can be used as argument to the configure() method. It is of type MultiFileBuilderParameters and can be obtained via the multiFile() method of a Parameters object. The properties specific to this configuration type are defined by the MultiFileBuilderProperties interface. They include

  • The pattern string for determining the name of the configuration file to be loaded. This is of course the most important setting as it tells the builder how to perform interpolation in order to resolve the correct configuration file.
  • A parameters object for the file-based configuration builder used behind the scenes to load the configuration file. Here some additional settings can be provided. For instance, if the configuration files to be loaded are XML documents, validation could be enabled via these parameters.

Below is a code fragment demonstrating the set up of a MultiFileConfigurationBuilder which loads configuration files of type XML:

Parameters params = new Parameters();
MultiFileConfigurationBuilder<XMLConfiguration> builder =
    new MultiFileConfigurationBuilder(XMLConfiguration.class)
    .configure(params.multiFile()
        .setFilePattern("configuration_${sys:Id}.xml")
        .setManagedBuilderParameters(params.xml()
            .setValidating(true)
        )
    );

XMLConfiguration config = builder.getConfiguration();

PatternSubtreeConfigurationWrapper

Applications are often composed of many components each of which need their own configuration. This can be accommodated by having a configuration file per component, but this can make things hard to manage when there are many clients and many components. A second approach is to combine them into a single configuration file. However, this either means the subcomponent has to be aware of the surrounding configuration and navigate past it or the application must be provided just the portion of the configuration it can process. PatternSubtreeConfigurationWrapper can be used for this purpose.

Normal practice when using dependency injection frameworks is to have the attributes needed to make components work correctly injected into them. When working with Commons Configuration this works very well. Components simply need to have a HierarchicalConfiguration attribute along with a corresponding setter and getter. The injection framework can then be used to provide the component with the correct configuration using PatternSubtreeConfigurationWrapper as shown in the next example.

  <bean id="configurationBuilder"
        class="org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder">
    <constructor-arg index="0"
      value="org.apache.commons.configuration2.XMLConfiguration"/>
    <constructor-arg index="1">
        <map>
            <entry key="config-fileBased">
                <map>
                    <entry key="fileName" value="configuration.xml"/>
                </map>
            </entry>
        </map>
    </constructor-arg>
  </bean>
  <bean id="applicationConfig" factory-bean="configurationBuilder"
        factory-method="getConfiguration">
  </bean>
  <bean id="subcomponentConfig"
        class="org.apache.commons.configuration2.PatternSubtreeConfigurationWrapper"
        autowire='autodetect'>
    <constructor-arg index="0">
      <ref bean="applicationConfig"/>
    </constructor-arg>
    <constructor-arg index="1" value="/Components/MyComponent"/>
  </bean>
  <bean id='MyComponent' class='org.test.MyComponent' autowire='autodetect'>
    <property name="configuration">
      <ref bean="subcomponentConfig"/>
    </property>
  </bean>

This example shows a Spring configuration defining beans to be used in an application. The bean named "MyComponent" is a component with its own configuration which is injected into the configuration property. Here an instance of PatternSubtreeConfigurationWrapper is passed (defined by the "subcomponentConfig" bean) that selects a subtree in the configuration data specific to this component; the corresponding prefix is defined as second constructor argument.

It is also of interest how the global configuration of the application is defined. It is loaded by a configuration builder declared by the "configurationBuilder" bean. The fluent API offered by configuration builders does not work very well here. Therefore, the builder is initialized via a map with settings passed to its constructor. Simple properties to be propagated to the managed configuration instance can be declared directly as keys of this map. The configuration of the file to be loaded is an exceptional case: This information is stored internally as a FileHandler object, and the properties of this object are contained in a sub map under the key config-fileBased.

With the declaration of the configuration builder in place, the actual configuration object can now be defined as bean using the builder's getConfiguration() method as factory method. From there it can be injected into arbitrary other beans.