View Javadoc

    1   /*
    2    * Licensed to the Apache Software Foundation (ASF) under one or more
    3    * contributor license agreements.  See the NOTICE file distributed with
    4    * this work for additional information regarding copyright ownership.
    5    * The ASF licenses this file to You under the Apache License, Version 2.0
    6    * (the "License"); you may not use this file except in compliance with
    7    * the License.  You may obtain a copy of the License at
    8    *
    9    *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  package org.apache.commons.configuration;
    18
    19  import java.io.File;
    20  import java.net.URL;
    21  import java.util.HashMap;
    22  import java.util.Iterator;
    23  import java.util.List;
    24  import java.util.Map;
    25
    26  import org.apache.commons.configuration.beanutils.BeanDeclaration;
    27  import org.apache.commons.configuration.beanutils.BeanFactory;
    28  import org.apache.commons.configuration.beanutils.BeanHelper;
    29  import org.apache.commons.configuration.beanutils.DefaultBeanFactory;
    30  import org.apache.commons.configuration.beanutils.XMLBeanDeclaration;
    31  import org.apache.commons.configuration.plist.PropertyListConfiguration;
    32  import org.apache.commons.configuration.plist.XMLPropertyListConfiguration;
    33  import org.apache.commons.configuration.tree.ConfigurationNode;
    34  import org.apache.commons.configuration.tree.DefaultExpressionEngine;
    35  import org.apache.commons.configuration.tree.OverrideCombiner;
    36  import org.apache.commons.configuration.tree.UnionCombiner;
    37  import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
    38
    39  /***
    40   * <p>
    41   * A factory class that creates a composite configuration from an XML based
    42   * <em>configuration definition file</em>.
    43   * </p>
    44   * <p>
    45   * This class provides an easy and flexible means for loading multiple
    46   * configuration sources and combining the results into a single configuration
    47   * object. The sources to be loaded are defined in an XML document that can
    48   * contain certain tags representing the different supported configuration
    49   * classes. If such a tag is found, the corresponding <code>Configuration</code>
    50   * class is instantiated and initialized using the classes of the
    51   * <code>beanutils</code> package (namely
    52   * <code>{@link org.apache.commons.configuration.beanutils.XMLBeanDeclaration XMLBeanDeclaration}</code>
    53   * will be used to extract the configuration's initialization parameters, which
    54   * allows for complex initialization szenarios).
    55   * </p>
    56   * <p>
    57   * It is also possible to add custom tags to the configuration definition file.
    58   * For this purpose register your own <code>ConfigurationProvider</code>
    59   * implementation for your tag using the <code>addConfigurationProvider()</code>
    60   * method. This provider will then be called when the corresponding custom tag
    61   * is detected. For the default configuration classes providers are already
    62   * registered.
    63   * </p>
    64   * <p>
    65   * The configuration definition file has the following basic structure:
    66   * </p>
    67   * <p>
    68   *
    69   * <pre>
    70   * &lt;configuration&gt;
    71   *   &lt;header&gt;
    72   *     &lt;!-- Optional meta information about the composite configuration --&gt;
    73   *   &lt;/header&gt;
    74   *   &lt;override&gt;
    75   *     &lt;!-- Declarations for override configurations --&gt;
    76   *   &lt;/override&gt;
    77   *   &lt;additional&gt;
    78   *     &lt;!-- Declarations for union configurations --&gt;
    79   *   &lt;/additional&gt;
    80   * &lt;/configuration&gt;
    81   * </pre>
    82   *
    83   * </p>
    84   * <p>
    85   * The name of the root element (here <code>configuration</code>) is
    86   * arbitrary. There are two sections (both of them are optional) for declaring
    87   * <em>override</em> and <em>additional</em> configurations. Configurations
    88   * in the former section are evaluated in the order of their declaration, and
    89   * properties of configurations declared earlier hide those of configurations
    90   * declared later. Configurations in the latter section are combined to a union
    91   * configuration, i.e. all of their properties are added to a large hierarchical
    92   * configuration. Configuration declarations that occur as direct children of
    93   * the root element are treated as override declarations.
    94   * </p>
    95   * <p>
    96   * Each configuration declaration consists of a tag whose name is associated
    97   * with a <code>ConfigurationProvider</code>. This can be one of the
    98   * pre-defined tags like <code>properties</code>, or <code>xml</code>, or
    99   * a custom tag, for which a configuration provider was registered. Attributes
    100  * and sub elements with specific initialization parameters can be added. There
    101  * are some reserved attributes with a special meaning that can be used in every
    102  * configuration declaration:
    103  * </p>
    104  * <p>
    105  * <table border="1">
    106  * <tr>
    107  * <th>Attribute</th>
    108  * <th>Meaning</th>
    109  * </tr>
    110  * <tr>
    111  * <td valign="top"><code>config-name</code></td>
    112  * <td>Allows to specify a name for this configuration. This name can be used
    113  * to obtain a reference to the configuration from the resulting combined
    114  * configuration (see below).</td>
    115  * </tr>
    116  * <tr>
    117  * <td valign="top"><code>config-at</code></td>
    118  * <td>With this attribute an optional prefix can be specified for the
    119  * properties of the corresponding configuration.</td>
    120  * </tr>
    121  * <tr>
    122  * <td valign="top"><code>config-optional</code></td>
    123  * <td>Declares a configuration as optional. This means that errors that occur
    124  * when creating the configuration are silently ignored.</td>
    125  * </tr>
    126  * </table>
    127  * </p>
    128  * <p>
    129  * The optional <em>header</em> section can contain some meta data about the
    130  * created configuration itself. For instance, it is possible to set further
    131  * properties of the <code>NodeCombiner</code> objects used for constructing
    132  * the resulting configuration.
    133  * </p>
    134  * <p>
    135  * The configuration object returned by this builder is an instance of the
    136  * <code>{@link CombinedConfiguration}</code> class. The return value of the
    137  * <code>getConfiguration()</code> method can be casted to this type, and the
    138  * <code>getConfiguration(boolean)</code> method directly declares
    139  * <code>CombinedConfiguration</code> as return type. This allows for
    140  * convenient access to the configuration objects maintained by the combined
    141  * configuration (e.g. for updates of single configuration objects). It has also
    142  * the advantage that the properties stored in all declared configuration
    143  * objects are collected and transformed into a single hierarchical structure,
    144  * which can be accessed using different expression engines.
    145  * </p>
    146  * <p>
    147  * All declared override configurations are directly added to the resulting
    148  * combined configuration. If they are given names (using the
    149  * <code>config-name</code> attribute), they can directly be accessed using
    150  * the <code>getConfiguration(String)</code> method of
    151  * <code>CombinedConfiguration</code>. The additional configurations are
    152  * alltogether added to another combined configuration, which uses a union
    153  * combiner. Then this union configuration is added to the resulting combined
    154  * configuration under the name defined by the <code>ADDITIONAL_NAME</code>
    155  * constant.
    156  * </p>
    157  * <p>
    158  * Implementation note: This class is not thread-safe. Especially the
    159  * <code>getConfiguration()</code> methods should be called by a single thread
    160  * only.
    161  * </p>
    162  *
    163  * @since 1.3
    164  * @author <a
    165  * href="http://jakarta.apache.org/commons/configuration/team-list.html">Commons
    166  * Configuration team</a>
    167  * @version $Id: DefaultConfigurationBuilder.java 384601 2006-03-09 20:22:58Z
    168  * oheger $
    169  */
    170 public class DefaultConfigurationBuilder extends XMLConfiguration implements
    171         ConfigurationBuilder
    172 {
    173     /***
    174      * Constant for the name of the additional configuration. If the
    175      * configuration definition file contains an <code>additional</code>
    176      * section, a special union configuration is created and added under this
    177      * name to the resulting combined configuration.
    178      */
    179     public static final String ADDITIONAL_NAME = DefaultConfigurationBuilder.class
    180             .getName()
    181             + "/ADDITIONAL_CONFIG";
    182
    183     /*** Constant for the expression engine used by this builder. */
    184     static final XPathExpressionEngine EXPRESSION_ENGINE = new XPathExpressionEngine();
    185
    186     /*** Constant for the name of the configuration bean factory. */
    187     static final String CONFIG_BEAN_FACTORY_NAME = DefaultConfigurationBuilder.class
    188             .getName()
    189             + ".CONFIG_BEAN_FACTORY_NAME";
    190
    191     /*** Constant for the reserved name attribute. */
    192     static final String ATTR_NAME = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
    193             + XMLBeanDeclaration.RESERVED_PREFIX
    194             + "name"
    195             + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
    196
    197     /*** Constant for the name of the at attribute. */
    198     static final String ATTR_ATNAME = "at";
    199
    200     /*** Constant for the reserved at attribute. */
    201     static final String ATTR_AT_RES = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
    202             + XMLBeanDeclaration.RESERVED_PREFIX
    203             + ATTR_ATNAME
    204             + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
    205
    206     /*** Constant for the at attribute without the reserved prefix. */
    207     static final String ATTR_AT = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
    208             + ATTR_ATNAME + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
    209
    210     /*** Constant for the name of the optional attribute. */
    211     static final String ATTR_OPTIONALNAME = "optional";
    212
    213     /*** Constant for the reserved optional attribute. */
    214     static final String ATTR_OPTIONAL_RES = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
    215             + XMLBeanDeclaration.RESERVED_PREFIX
    216             + ATTR_OPTIONALNAME
    217             + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
    218
    219     /*** Constant for the optional attribute without the reserved prefix. */
    220     static final String ATTR_OPTIONAL = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
    221             + ATTR_OPTIONALNAME + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
    222
    223     /*** Constant for the file name attribute. */
    224     static final String ATTR_FILENAME = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
    225             + "fileName" + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
    226
    227     /*** Constant for the name of the header section. */
    228     static final String SEC_HEADER = "header";
    229
    230     /*** Constant for an expression that selects the union configurations. */
    231     static final String KEY_UNION = "/additional/*";
    232
    233     /*** Constant for an expression that selects override configurations. */
    234     static final String KEY_OVERRIDE1 = "/*[local-name() != 'additional' and "
    235             + "local-name() != 'override' and local-name() != '"
    236             + SEC_HEADER + "']";
    237
    238     /***
    239      * Constant for an expression that selects override configurations in the
    240      * override section.
    241      */
    242     static final String KEY_OVERRIDE2 = "/override/*";
    243
    244     /***
    245      * Constant for the key that points to the list nodes definition of the
    246      * override combiner.
    247      */
    248     static final String KEY_OVERRIDE_LIST = SEC_HEADER
    249             + "/combiner/override/list-nodes/node";
    250
    251     /***
    252      * Constant for the key that points to the list nodes definition of the
    253      * additional combiner.
    254      */
    255     static final String KEY_ADDITIONAL_LIST = SEC_HEADER
    256             + "/combiner/additional/list-nodes/node";
    257
    258     /***
    259      * Constant for the key of the result declaration. This key can point to a
    260      * bean declaration, which defines properties of the resulting combined
    261      * configuration.
    262      */
    263     static final String KEY_RESULT = SEC_HEADER + "/result";
    264
    265     /*** Constant for the key of the combiner in the result declaration.*/
    266     static final String KEY_COMBINER = KEY_RESULT + "/nodeCombiner";
    267
    268     /*** Constant for the XML file extension. */
    269     static final String EXT_XML = ".xml";
    270
    271     /*** Constant for the provider for properties files. */
    272     private static final ConfigurationProvider PROPERTIES_PROVIDER = new FileExtensionConfigurationProvider(
    273             XMLPropertiesConfiguration.class, PropertiesConfiguration.class,
    274             EXT_XML);
    275
    276     /*** Constant for the provider for XML files. */
    277     private static final ConfigurationProvider XML_PROVIDER = new FileConfigurationProvider(
    278             XMLConfiguration.class);
    279
    280     /*** Constant for the provider for JNDI sources. */
    281     private static final ConfigurationProvider JNDI_PROVIDER = new ConfigurationProvider(
    282             JNDIConfiguration.class);
    283
    284     /*** Constant for the provider for system properties. */
    285     private static final ConfigurationProvider SYSTEM_PROVIDER = new ConfigurationProvider(
    286             SystemConfiguration.class);
    287
    288     /*** Constant for the provider for plist files. */
    289     private static final ConfigurationProvider PLIST_PROVIDER = new FileExtensionConfigurationProvider(
    290             XMLPropertyListConfiguration.class,
    291             PropertyListConfiguration.class, EXT_XML);
    292
    293     /*** Constant for the provider for configuration definition files.*/
    294     private static final ConfigurationProvider BUILDER_PROVIDER = new ConfigurationBuilderProvider();
    295
    296     /*** An array with the names of the default tags. */
    297     private static final String[] DEFAULT_TAGS =
    298     {"properties", "xml", "hierarchicalXml", "jndi", "system", "plist", "configuration"};
    299
    300     /*** An array with the providers for the default tags. */
    301     private static final ConfigurationProvider[] DEFAULT_PROVIDERS =
    302     {PROPERTIES_PROVIDER, XML_PROVIDER, XML_PROVIDER, JNDI_PROVIDER,
    303             SYSTEM_PROVIDER, PLIST_PROVIDER, BUILDER_PROVIDER};
    304
    305     /***
    306      * The serial version UID.
    307      */
    308     private static final long serialVersionUID = -3113777854714492123L;
    309
    310     /*** Stores the configuration that is currently constructed.*/
    311     private CombinedConfiguration constructedConfiguration;
    312
    313     /*** Stores a map with the registered configuration providers. */
    314     private Map providers;
    315
    316     /*** Stores the base path to the configuration sources to load. */
    317     private String configurationBasePath;
    318
    319     /***
    320      * Creates a new instance of <code>DefaultConfigurationBuilder</code>. A
    321      * configuration definition file is not yet loaded. Use the diverse setter
    322      * methods provided by file based configurations to specify the
    323      * configuration definition file.
    324      */
    325     public DefaultConfigurationBuilder()
    326     {
    327         super();
    328         providers = new HashMap();
    329         setExpressionEngine(EXPRESSION_ENGINE);
    330         registerDefaultProviders();
    331     }
    332
    333     /***
    334      * Creates a new instance of <code>DefaultConfigurationBuilder</code> and
    335      * sets the specified configuration definition file.
    336      *
    337      * @param file the configuration definition file
    338      */
    339     public DefaultConfigurationBuilder(File file)
    340     {
    341         this();
    342         setFile(file);
    343     }
    344
    345     /***
    346      * Creates a new instance of <code>DefaultConfigurationBuilder</code> and
    347      * sets the specified configuration definition file.
    348      *
    349      * @param fileName the name of the configuration definition file
    350      * @throws ConfigurationException if an error occurs when the file is loaded
    351      */
    352     public DefaultConfigurationBuilder(String fileName)
    353             throws ConfigurationException
    354     {
    355         this();
    356         setFileName(fileName);
    357     }
    358
    359     /***
    360      * Creates a new instance of <code>DefaultConfigurationBuilder</code> and
    361      * sets the specified configuration definition file.
    362      *
    363      * @param url the URL to the configuration definition file
    364      * @throws ConfigurationException if an error occurs when the file is loaded
    365      */
    366     public DefaultConfigurationBuilder(URL url) throws ConfigurationException
    367     {
    368         this();
    369         setURL(url);
    370     }
    371
    372     /***
    373      * Returns the base path for the configuration sources to load. This path is
    374      * used to resolve relative paths in the configuration definition file.
    375      *
    376      * @return the base path for configuration sources
    377      */
    378     public String getConfigurationBasePath()
    379     {
    380         return (configurationBasePath != null) ? configurationBasePath
    381                 : getBasePath();
    382     }
    383
    384     /***
    385      * Sets the base path for the configuration sources to load. Normally a base
    386      * path need not to be set because it is determined by the location of the
    387      * configuration definition file to load. All relative pathes in this file
    388      * are resolved relative to this file. Setting a base path makes sense if
    389      * such relative pathes should be otherwise resolved, e.g. if the
    390      * configuration file is loaded from the class path and all sub
    391      * configurations it refers to are stored in a special config directory.
    392      *
    393      * @param configurationBasePath the new base path to set
    394      */
    395     public void setConfigurationBasePath(String configurationBasePath)
    396     {
    397         this.configurationBasePath = configurationBasePath;
    398     }
    399
    400     /***
    401      * Adds a configuration provider for the specified tag. Whenever this tag is
    402      * encountered in the configuration definition file this provider will be
    403      * called to create the configuration object.
    404      *
    405      * @param tagName the name of the tag in the configuration definition file
    406      * @param provider the provider for this tag
    407      */
    408     public void addConfigurationProvider(String tagName,
    409             ConfigurationProvider provider)
    410     {
    411         if (tagName == null)
    412         {
    413             throw new IllegalArgumentException("Tag name must not be null!");
    414         }
    415         if (provider == null)
    416         {
    417             throw new IllegalArgumentException("Provider must not be null!");
    418         }
    419
    420         providers.put(tagName, provider);
    421     }
    422
    423     /***
    424      * Removes the configuration provider for the specified tag name.
    425      *
    426      * @param tagName the tag name
    427      * @return the removed configuration provider or <b>null</b> if none was
    428      * registered for that tag
    429      */
    430     public ConfigurationProvider removeConfigurationProvider(String tagName)
    431     {
    432         return (ConfigurationProvider) providers.remove(tagName);
    433     }
    434
    435     /***
    436      * Returns the configuration provider for the given tag.
    437      *
    438      * @param tagName the name of the tag
    439      * @return the provider that was registered for this tag or <b>null</b> if
    440      * there is none
    441      */
    442     public ConfigurationProvider providerForTag(String tagName)
    443     {
    444         return (ConfigurationProvider) providers.get(tagName);
    445     }
    446
    447     /***
    448      * Returns the configuration provided by this builder. Loads and parses the
    449      * configuration definition file and creates instances for the declared
    450      * configurations.
    451      *
    452      * @return the configuration
    453      * @throws ConfigurationException if an error occurs
    454      */
    455     public Configuration getConfiguration() throws ConfigurationException
    456     {
    457         return getConfiguration(true);
    458     }
    459
    460     /***
    461      * Returns the configuration provided by this builder. If the boolean
    462      * parameter is <b>true</b>, the configuration definition file will be
    463      * loaded. It will then be parsed, and instances for the declared
    464      * configurations will be created.
    465      *
    466      * @param load a flag whether the configuration definition file should be
    467      * loaded; a value of <b>false</b> would make sense if the file has already
    468      * been created or its content was manipulated using some of the property
    469      * accessor methods
    470      * @return the configuration
    471      * @throws ConfigurationException if an error occurs
    472      */
    473     public CombinedConfiguration getConfiguration(boolean load)
    474             throws ConfigurationException
    475     {
    476         if (load)
    477         {
    478             load();
    479         }
    480
    481         CombinedConfiguration result = createResultConfiguration();
    482         constructedConfiguration = result;
    483
    484         List overrides = configurationsAt(KEY_OVERRIDE1);
    485         overrides.addAll(configurationsAt(KEY_OVERRIDE2));
    486         initCombinedConfiguration(result, overrides, KEY_OVERRIDE_LIST);
    487
    488         List additionals = configurationsAt(KEY_UNION);
    489         if (!additionals.isEmpty())
    490         {
    491             CombinedConfiguration addConfig = new CombinedConfiguration(
    492                     new UnionCombiner());
    493             result.addConfiguration(addConfig, ADDITIONAL_NAME);
    494             initCombinedConfiguration(addConfig, additionals,
    495                     KEY_ADDITIONAL_LIST);
    496         }
    497
    498         return result;
    499     }
    500
    501     /***
    502      * Creates the resulting combined configuration. This method is called by
    503      * <code>getConfiguration()</code>. It checks whether the
    504      * <code>header</code> section of the configuration definition file
    505      * contains a <code>result</code> element. If this is the case, it will be
    506      * used to initialize the properties of the newly created configuration
    507      * object.
    508      *
    509      * @return the resulting configuration object
    510      * @throws ConfigurationException if an error occurs
    511      */
    512     protected CombinedConfiguration createResultConfiguration()
    513             throws ConfigurationException
    514     {
    515         XMLBeanDeclaration decl = new XMLBeanDeclaration(this, KEY_RESULT, true);
    516         CombinedConfiguration result = (CombinedConfiguration) BeanHelper
    517                 .createBean(decl, CombinedConfiguration.class);
    518
    519         if (getMaxIndex(KEY_COMBINER) < 0)
    520         {
    521             // No combiner defined => set default
    522             result.setNodeCombiner(new OverrideCombiner());
    523         }
    524
    525         return result;
    526     }
    527
    528     /***
    529      * Initializes a combined configuration for the configurations of a specific
    530      * section. This method is called for the override and for the additional
    531      * section (if it exists).
    532      *
    533      * @param config the configuration to be initialized
    534      * @param containedConfigs the list with the declaratinos of the contained
    535      * configurations
    536      * @param keyListNodes a list with the declaration of list nodes
    537      * @throws ConfigurationException if an error occurs
    538      */
    539     protected void initCombinedConfiguration(CombinedConfiguration config,
    540             List containedConfigs, String keyListNodes) throws ConfigurationException
    541     {
    542         List listNodes = getList(keyListNodes);
    543         for (Iterator it = listNodes.iterator(); it.hasNext();)
    544         {
    545             config.getNodeCombiner().addListNode((String) it.next());
    546         }
    547
    548         for (Iterator it = containedConfigs.iterator(); it.hasNext();)
    549         {
    550             HierarchicalConfiguration conf = (HierarchicalConfiguration) it
    551                     .next();
    552             ConfigurationDeclaration decl = new ConfigurationDeclaration(this,
    553                     conf);
    554             AbstractConfiguration newConf = createConfigurationAt(decl);
    555             if (newConf != null)
    556             {
    557                 config.addConfiguration(newConf, decl.getConfiguration()
    558                         .getString(ATTR_NAME), decl.getAt());
    559             }
    560         }
    561     }
    562
    563     /***
    564      * Registers the default configuration providers supported by this class.
    565      * This method will be called during initialization. It registers
    566      * configuration providers for the tags that are supported by default.
    567      */
    568     protected void registerDefaultProviders()
    569     {
    570         for (int i = 0; i < DEFAULT_TAGS.length; i++)
    571         {
    572             addConfigurationProvider(DEFAULT_TAGS[i], DEFAULT_PROVIDERS[i]);
    573         }
    574     }
    575
    576     /***
    577      * Performs interpolation. This method will not only take this configuration
    578      * instance into account (which is the one that loaded the configuration
    579      * definition file), but also the so far constructed combined configuration.
    580      * So variables can be used that point to properties that are defined in
    581      * configuration sources loaded by this builder.
    582      *
    583      * @param value the value to be interpolated
    584      * @return the interpolated value
    585      */
    586     protected Object interpolate(Object value)
    587     {
    588         Object result = super.interpolate(value);
    589         if (constructedConfiguration != null)
    590         {
    591             result = constructedConfiguration.interpolate(result);
    592         }
    593         return result;
    594     }
    595
    596     /***
    597      * Creates a configuration object from the specified configuration
    598      * declaration.
    599      *
    600      * @param decl the configuration declaration
    601      * @return the new configuration object
    602      * @throws ConfigurationException if an error occurs
    603      */
    604     private AbstractConfiguration createConfigurationAt(
    605             ConfigurationDeclaration decl) throws ConfigurationException
    606     {
    607         try
    608         {
    609             return (AbstractConfiguration) BeanHelper.createBean(decl);
    610         }
    611         catch (Exception ex)
    612         {
    613             // redirect to configuration exceptions
    614             throw new ConfigurationException(ex);
    615         }
    616     }
    617
    618     /***
    619      * <p>
    620      * A base class for creating and initializing configuration sources.
    621      * </p>
    622      * <p>
    623      * Concrete sub classes of this base class are responsible for creating
    624      * specific <code>Configuration</code> objects for the tags in the
    625      * configuration definition file. The configuration factory will parse the
    626      * definition file and try to find a matching
    627      * <code>ConfigurationProvider</code> for each encountered tag. This
    628      * provider is then asked to create a corresponding
    629      * <code>Configuration</code> object. It is up to a concrete
    630      * implementation how this object is created and initialized.
    631      * </p>
    632      * <p>
    633      * Note that at the moment only configuration classes derived from
    634      * <code>{@link AbstractConfiguration}</code> are supported.
    635      * </p>
    636      */
    637     public static class ConfigurationProvider extends DefaultBeanFactory
    638     {
    639         /*** Stores the class of the configuration to be created. */
    640         private Class configurationClass;
    641
    642         /***
    643          * Creates a new uninitialized instance of
    644          * <code>ConfigurationProvider</code>.
    645          */
    646         public ConfigurationProvider()
    647         {
    648             this(null);
    649         }
    650
    651         /***
    652          * Creates a new instance of <code>ConfigurationProvider</code> and
    653          * sets the class of the configuration created by this provider.
    654          *
    655          * @param configClass the configuration class
    656          */
    657         public ConfigurationProvider(Class configClass)
    658         {
    659             setConfigurationClass(configClass);
    660         }
    661
    662         /***
    663          * Returns the class of the configuration returned by this provider.
    664          *
    665          * @return the class of the provided configuration
    666          */
    667         public Class getConfigurationClass()
    668         {
    669             return configurationClass;
    670         }
    671
    672         /***
    673          * Sets the class of the configuration returned by this provider.
    674          *
    675          * @param configurationClass the configuration class
    676          */
    677         public void setConfigurationClass(Class configurationClass)
    678         {
    679             this.configurationClass = configurationClass;
    680         }
    681
    682         /***
    683          * Returns the configuration. This method is called to fetch the
    684          * configuration from the provider. This implementation will call the
    685          * inherited
    686          * <code>{@link org.apache.commons.configuration.beanutils.DefaultBeanFactory#createBean(Class, BeanDeclaration, Object) createBean()}</code>
    687          * method to create a new instance of the configuration class.
    688          *
    689          * @param decl the bean declaration with initialization parameters for
    690          * the configuration
    691          * @return the new configuration object
    692          * @throws Exception if an error occurs
    693          */
    694         public AbstractConfiguration getConfiguration(
    695                 ConfigurationDeclaration decl) throws Exception
    696         {
    697             return (AbstractConfiguration) createBean(getConfigurationClass(),
    698                     decl, null);
    699         }
    700     }
    701
    702     /***
    703      * <p>
    704      * A specialized <code>BeanDeclaration</code> implementation that
    705      * represents the declaration of a configuration source.
    706      * </p>
    707      * <p>
    708      * Instances of this class are able to extract all information about a
    709      * configuration source from the configuration definition file. The
    710      * declaration of a configuration source is very similar to a bean
    711      * declaration processed by <code>XMLBeanDeclaration</code>. There are
    712      * very few differences, e.g. the two reserved attributes
    713      * <code>optional</code> and <code>at</code> and the fact that a bean
    714      * factory is never needed.
    715      * </p>
    716      */
    717     protected static class ConfigurationDeclaration extends XMLBeanDeclaration
    718     {
    719         /*** Stores a reference to the associated configuration builder. */
    720         private DefaultConfigurationBuilder configurationBuilder;
    721
    722         /***
    723          * Creates a new instance of <code>ConfigurationDeclaration</code> and
    724          * initializes it.
    725          *
    726          * @param builder the associated configuration builder
    727          * @param config the configuration this declaration is based onto
    728          */
    729         public ConfigurationDeclaration(DefaultConfigurationBuilder builder,
    730                 HierarchicalConfiguration config)
    731         {
    732             super(config);
    733             configurationBuilder = builder;
    734         }
    735
    736         /***
    737          * Returns the associated configuration builder.
    738          *
    739          * @return the configuration builder
    740          */
    741         public DefaultConfigurationBuilder getConfigurationBuilder()
    742         {
    743             return configurationBuilder;
    744         }
    745
    746         /***
    747          * Returns the value of the <code>at</code> attribute.
    748          *
    749          * @return the value of the <code>at</code> attribute (can be <b>null</b>)
    750          */
    751         public String getAt()
    752         {
    753             String result = this.getConfiguration().getString(ATTR_AT_RES);
    754             return (result == null) ? this.getConfiguration().getString(ATTR_AT)
    755                     : result;
    756         }
    757
    758         /***
    759          * Returns a flag whether this is an optional configuration.
    760          *
    761          * @return a flag if this declaration points to an optional
    762          * configuration
    763          */
    764         public boolean isOptional()
    765         {
    766             Boolean value = this.getConfiguration().getBoolean(ATTR_OPTIONAL_RES,
    767                     null);
    768             if (value == null)
    769             {
    770                 value = this.getConfiguration().getBoolean(ATTR_OPTIONAL,
    771                         Boolean.FALSE);
    772             }
    773             return value.booleanValue();
    774         }
    775
    776         /***
    777          * Returns the name of the bean factory. For configuration source
    778          * declarations always a reserved factory is used. This factory's name
    779          * is returned by this implementation.
    780          *
    781          * @return the name of the bean factory
    782          */
    783         public String getBeanFactoryName()
    784         {
    785             return CONFIG_BEAN_FACTORY_NAME;
    786         }
    787
    788         /***
    789          * Returns the bean's class name. This implementation will always return
    790          * <b>null</b>.
    791          *
    792          * @return the name of the bean's class
    793          */
    794         public String getBeanClassName()
    795         {
    796             return null;
    797         }
    798
    799         /***
    800          * Checks whether the given node is reserved. This method will take
    801          * further reserved attributes into account
    802          *
    803          * @param nd the node
    804          * @return a flag whether this node is reserved
    805          */
    806         protected boolean isReservedNode(ConfigurationNode nd)
    807         {
    808             if (super.isReservedNode(nd))
    809             {
    810                 return true;
    811             }
    812
    813             return nd.isAttribute()
    814                     && ((ATTR_ATNAME.equals(nd.getName()) && nd.getParentNode()
    815                             .getAttributeCount(RESERVED_PREFIX + ATTR_ATNAME) == 0) || (ATTR_OPTIONALNAME
    816                             .equals(nd.getName()) && nd.getParentNode()
    817                             .getAttributeCount(RESERVED_PREFIX + ATTR_OPTIONALNAME) == 0));
    818         }
    819
    820         /***
    821          * Performs interpolation. This implementation will delegate
    822          * interpolation to the configuration builder, which takes care that the
    823          * currently constructed configuration is taken into account, too.
    824          *
    825          * @param value the value to be interpolated
    826          * @return the interpolated value
    827          */
    828         protected Object interpolate(Object value)
    829         {
    830             return getConfigurationBuilder().interpolate(value);
    831         }
    832     }
    833
    834     /***
    835      * A specialized <code>BeanFactory</code> implementation that handles
    836      * configuration declarations. This class will retrieve the correct
    837      * configuration provider and delegate the task of creating the
    838      * configuration to this object.
    839      */
    840     static class ConfigurationBeanFactory implements BeanFactory
    841     {
    842         /***
    843          * Creates an instance of a bean class. This implementation expects that
    844          * the passed in bean declaration is a declaration for a configuration.
    845          * It will determine the responsible configuration provider and delegate
    846          * the call to this instance. If creation of the configuration fails
    847          * and the <code>optional</code> attribute is set, the exception will
    848          * be ignored and <b>null</b> will be returned.
    849          *
    850          * @param beanClass the bean class (will be ignored)
    851          * @param data the declaration
    852          * @param param an additional parameter (will be ignored)
    853          * @return the newly created configuration
    854          * @throws Exception if an error occurs
    855          */
    856         public Object createBean(Class beanClass, BeanDeclaration data,
    857                 Object param) throws Exception
    858         {
    859             ConfigurationDeclaration decl = (ConfigurationDeclaration) data;
    860             String tagName = decl.getNode().getName();
    861             ConfigurationProvider provider = decl.getConfigurationBuilder()
    862                     .providerForTag(tagName);
    863             if (provider == null)
    864             {
    865                 throw new ConfigurationRuntimeException(
    866                         "No ConfigurationProvider registered for tag "
    867                                 + tagName);
    868             }
    869
    870             try
    871             {
    872                 return provider.getConfiguration(decl);
    873             }
    874             catch (Exception ex)
    875             {
    876                 // If this is an optional configuration, ignore the exception
    877                 if (!decl.isOptional())
    878                 {
    879                     throw ex;
    880                 }
    881                 else
    882                 {
    883                     return null;
    884                 }
    885             }
    886         }
    887
    888         /***
    889          * Returns the default class for this bean factory.
    890          *
    891          * @return the default class
    892          */
    893         public Class getDefaultBeanClass()
    894         {
    895             // Here some valid class must be returned, otherwise BeanHelper
    896             // will complain that the bean's class cannot be determined
    897             return Configuration.class;
    898         }
    899     }
    900
    901     /***
    902      * A specialized provider implementation that deals with file based
    903      * configurations. Ensures that the base path is correctly set and that the
    904      * load() method gets called.
    905      */
    906     public static class FileConfigurationProvider extends ConfigurationProvider
    907     {
    908         /***
    909          * Creates a new instance of <code>FileConfigurationProvider</code>.
    910          */
    911         public FileConfigurationProvider()
    912         {
    913             super();
    914         }
    915
    916         /***
    917          * Creates a new instance of <code>FileConfigurationProvider</code>
    918          * and sets the configuration class.
    919          *
    920          * @param configClass the class for the configurations to be created
    921          */
    922         public FileConfigurationProvider(Class configClass)
    923         {
    924             super(configClass);
    925         }
    926
    927         /***
    928          * Creates the configuration. After that <code>load()</code> will be
    929          * called. If this configuration is marked as optional, exceptions will
    930          * be ignored.
    931          *
    932          * @param decl the declaration
    933          * @return the new configuration
    934          * @throws Exception if an error occurs
    935          */
    936         public AbstractConfiguration getConfiguration(
    937                 ConfigurationDeclaration decl) throws Exception
    938         {
    939             FileConfiguration config = (FileConfiguration) super
    940                     .getConfiguration(decl);
    941             config.load();
    942             return (AbstractConfiguration) config;
    943         }
    944
    945         /***
    946          * Initializes the bean instance. Ensures that the file configuration's
    947          * base path will be initialized with the base path of the factory so
    948          * that relative path names can be correctly resolved.
    949          *
    950          * @param bean the bean to be initialized
    951          * @param data the declaration
    952          * @throws Exception if an error occurs
    953          */
    954         protected void initBeanInstance(Object bean, BeanDeclaration data)
    955                 throws Exception
    956         {
    957             FileConfiguration config = (FileConfiguration) bean;
    958             config.setBasePath(((ConfigurationDeclaration) data)
    959                     .getConfigurationBuilder().getConfigurationBasePath());
    960             super.initBeanInstance(bean, data);
    961         }
    962     }
    963
    964     /***
    965      * A specialized configuration provider for file based configurations that
    966      * can handle configuration sources whose concrete type depends on the
    967      * extension of the file to be loaded. One example is the
    968      * <code>properties</code> tag: if the file ends with ".xml" a
    969      * XMLPropertiesConfiguration object must be created, otherwise a
    970      * PropertiesConfiguration object.
    971      */
    972     static class FileExtensionConfigurationProvider extends
    973             FileConfigurationProvider
    974     {
    975         /*** Stores the class to be created when the file extension matches. */
    976         private Class matchingClass;
    977
    978         /***
    979          * Stores the class to be created when the file extension does not
    980          * match.
    981          */
    982         private Class defaultClass;
    983
    984         /*** Stores the file extension to be checked against. */
    985         private String fileExtension;
    986
    987         /***
    988          * Creates a new instance of
    989          * <code>FileExtensionConfigurationProvider</code> and initializes it.
    990          *
    991          * @param matchingClass the class to be created when the file extension
    992          * matches
    993          * @param defaultClass the class to be created when the file extension
    994          * does not match
    995          * @param extension the file extension to be checked agains
    996          */
    997         public FileExtensionConfigurationProvider(Class matchingClass,
    998                 Class defaultClass, String extension)
    999         {
    1000             this.matchingClass = matchingClass;
    1001             this.defaultClass = defaultClass;
    1002             fileExtension = extension;
    1003         }
    1004
    1005         /***
    1006          * Creates the configuration object. The class is determined by the file
    1007          * name's extension.
    1008          *
    1009          * @param beanClass the class
    1010          * @param data the bean declaration
    1011          * @return the new bean
    1012          * @throws Exception if an error occurs
    1013          */
    1014         protected Object createBeanInstance(Class beanClass,
    1015                 BeanDeclaration data) throws Exception
    1016         {
    1017             String fileName = ((ConfigurationDeclaration) data)
    1018                     .getConfiguration().getString(ATTR_FILENAME);
    1019             if (fileName != null
    1020                     && fileName.toLowerCase().trim().endsWith(fileExtension))
    1021             {
    1022                 return super.createBeanInstance(matchingClass, data);
    1023             }
    1024             else
    1025             {
    1026                 return super.createBeanInstance(defaultClass, data);
    1027             }
    1028         }
    1029     }
    1030
    1031     /***
    1032      * A specialized configuration provider class that allows to include other
    1033      * configuration definition files.
    1034      */
    1035     static class ConfigurationBuilderProvider extends ConfigurationProvider
    1036     {
    1037         /***
    1038          * Creates a new instance of <code>ConfigurationBuilderProvider</code>.
    1039          */
    1040         public ConfigurationBuilderProvider()
    1041         {
    1042             super(DefaultConfigurationBuilder.class);
    1043         }
    1044
    1045         /***
    1046          * Creates the configuration. First creates a configuration builder
    1047          * object. Then returns the configuration created by this builder.
    1048          *
    1049          * @param decl the configuration declaration
    1050          * @return the configuration
    1051          * @exception Exception if an error occurs
    1052          */
    1053         public AbstractConfiguration getConfiguration(
    1054                 ConfigurationDeclaration decl) throws Exception
    1055         {
    1056             DefaultConfigurationBuilder builder = (DefaultConfigurationBuilder) super
    1057                     .getConfiguration(decl);
    1058             return builder.getConfiguration(true);
    1059         }
    1060     }
    1061
    1062     static
    1063     {
    1064         // register the configuration bean factory
    1065         BeanHelper.registerBeanFactory(CONFIG_BEAN_FACTORY_NAME,
    1066                 new ConfigurationBeanFactory());
    1067     }
    1068 }