Multi-tenant Configurations
In a multi-tenant environment a single instance of the application
while run on behalf of many clients. Typically, this will require
that each client have its own unique configuration. The simplest
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 take 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.
MultiFileHierarchicalConfiguration
The constructor for this class accepts a pattern. The pattern can
contain keys that will be resolved using the ConfigurationInterpolator
on each call to a method in the class. The configuration file will then
be located using the resolved pattern and a new XMLConfiguration
will be created and cached for subsequent requests. The ExpressionEngine,
ReloadingStrategy and listeners will be propagated to each of the
created configurations.
When used in a combined configuration it is often acceptable for a file
matching a particular pattern to be missing so, by default, most exceptions
encountered when loading files are ignored. To change this behavior
call setIgnoreException(false) or configure the attribute to false in
DefaultConfigurationBuilder's configuration file. If schema validation
is enabled validation exceptions will always cause a failure.
DynamicCombinedConfiguration
The CombinedConfiguration class allows multiple configuration files to be
merged together. However, it will not merge a MultiFileHierarchicalConfiguration
properly since the underlying configuration file 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 DynamicCombinedConfiguration
in combination with MultiFileHierarchicalConfiguration to create a multi-tenant
configuration.
<?xml version="1.0" encoding="ISO-8859-1" ?>
<!-- Test configuration definition file that demonstrates complex initialization -->
<configuration>
<header>
<result delimiterParsingDisabled="true" forceReloadCheck="true"
config-class="org.apache.commons.configuration.DynamicCombinedConfiguration"
keyPattern="$${sys:Id}">
<expressionEngine
config-class="org.apache.commons.configuration.tree.xpath.XPathExpressionEngine"/>
</result>
<providers>
<provider config-tag="multifile"
config-class="org.apache.commons.configuration.DefaultConfigurationBuilder$FileConfigurationProvider"
configurationClass="org.apache.commons.configuration.MultiFileHierarchicalConfiguration"/>
</providers>
</header>
<override>
<multifile filePattern="/opt/configs/$$${sys:Id}/config.xml" config-name="clientConfig"/>
<xml fileName="/opt/configs/default/config.xml" config-name="defaultConfig"/>
</override>
</configuration>
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 DefaultConfigurationBuilder will interpolate any variables
in the configuration. In the case of the multifile configuration item two
leading '$' characters are necessary before the variable because it will be
interpolated by both DefaultConfigurationBuilder and DynamicCombinedConfiguration
before MultiFileHierarchicalConfiguration 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.
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.configuration.DefaultConfigurationBuilder">
<property name="fileName">
<value>configuration.xml</value>
</property>
</bean>
<bean id="applicationConfig" factory-bean="configurationBuilder"
factory-method="getConfiguration">
</bean>
<bean id="subcomponentConfig"
class="org.apache.commons.configuration.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>
|