001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.configuration;
018    
019    import java.io.File;
020    import java.net.URL;
021    import java.util.ArrayList;
022    import java.util.Collections;
023    import java.util.HashMap;
024    import java.util.Iterator;
025    import java.util.List;
026    import java.util.Map;
027    
028    import org.apache.commons.configuration.beanutils.BeanDeclaration;
029    import org.apache.commons.configuration.beanutils.BeanFactory;
030    import org.apache.commons.configuration.beanutils.BeanHelper;
031    import org.apache.commons.configuration.beanutils.DefaultBeanFactory;
032    import org.apache.commons.configuration.beanutils.XMLBeanDeclaration;
033    import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
034    import org.apache.commons.configuration.tree.ConfigurationNode;
035    import org.apache.commons.configuration.tree.DefaultExpressionEngine;
036    import org.apache.commons.configuration.tree.OverrideCombiner;
037    import org.apache.commons.configuration.tree.UnionCombiner;
038    import org.apache.commons.lang.text.StrLookup;
039    import org.apache.commons.logging.LogFactory;
040    
041    /**
042     * <p>
043     * A factory class that creates a composite configuration from an XML based
044     * <em>configuration definition file</em>.
045     * </p>
046     * <p>
047     * This class provides an easy and flexible means for loading multiple
048     * configuration sources and combining the results into a single configuration
049     * object. The sources to be loaded are defined in an XML document that can
050     * contain certain tags representing the different supported configuration
051     * classes. If such a tag is found, the corresponding <code>Configuration</code>
052     * class is instantiated and initialized using the classes of the
053     * <code>beanutils</code> package (namely
054     * <code>{@link org.apache.commons.configuration.beanutils.XMLBeanDeclaration XMLBeanDeclaration}</code>
055     * will be used to extract the configuration's initialization parameters, which
056     * allows for complex initialization scenarios).
057     * </p>
058     * <p>
059     * It is also possible to add custom tags to the configuration definition file.
060     * For this purpose register your own <code>ConfigurationProvider</code>
061     * implementation for your tag using the <code>addConfigurationProvider()</code>
062     * method. This provider will then be called when the corresponding custom tag
063     * is detected. For the default configuration classes providers are already
064     * registered.
065     * </p>
066     * <p>
067     * The configuration definition file has the following basic structure:
068     * </p>
069     * <p>
070     *
071     * <pre>
072     * &lt;configuration systemProperties="properties file name"&gt;
073     *   &lt;header&gt;
074     *     &lt;!-- Optional meta information about the composite configuration --&gt;
075     *   &lt;/header&gt;
076     *   &lt;override&gt;
077     *     &lt;!-- Declarations for override configurations --&gt;
078     *   &lt;/override&gt;
079     *   &lt;additional&gt;
080     *     &lt;!-- Declarations for union configurations --&gt;
081     *   &lt;/additional&gt;
082     * &lt;/configuration&gt;
083     * </pre>
084     *
085     * </p>
086     * <p>
087     * The name of the root element (here <code>configuration</code>) is
088     * arbitrary. The optional systemProperties attribute identifies the path to
089     * a property file containing properties that should be added to the system
090     * properties. If specified on the root element, the system properties are
091     * set before the rest of the configuration is processed.
092     * </p>
093     * <p>
094     * There are two sections (both of them are optional) for declaring
095     * <em>override</em> and <em>additional</em> configurations. Configurations
096     * in the former section are evaluated in the order of their declaration, and
097     * properties of configurations declared earlier hide those of configurations
098     * declared later. Configurations in the latter section are combined to a union
099     * configuration, i.e. all of their properties are added to a large hierarchical
100     * configuration. Configuration declarations that occur as direct children of
101     * the root element are treated as override declarations.
102     * </p>
103     * <p>
104     * Each configuration declaration consists of a tag whose name is associated
105     * with a <code>ConfigurationProvider</code>. This can be one of the
106     * predefined tags like <code>properties</code>, or <code>xml</code>, or
107     * a custom tag, for which a configuration provider was registered. Attributes
108     * and sub elements with specific initialization parameters can be added. There
109     * are some reserved attributes with a special meaning that can be used in every
110     * configuration declaration:
111     * </p>
112     * <p>
113     * <table border="1">
114     * <tr>
115     * <th>Attribute</th>
116     * <th>Meaning</th>
117     * </tr>
118     * <tr>
119     * <td valign="top"><code>config-name</code></td>
120     * <td>Allows to specify a name for this configuration. This name can be used
121     * to obtain a reference to the configuration from the resulting combined
122     * configuration (see below).</td>
123     * </tr>
124     * <tr>
125     * <td valign="top"><code>config-at</code></td>
126     * <td>With this attribute an optional prefix can be specified for the
127     * properties of the corresponding configuration.</td>
128     * </tr>
129     * <tr>
130     * <td valign="top"><code>config-optional</code></td>
131     * <td>Declares a configuration as optional. This means that errors that occur
132     * when creating the configuration are ignored. (However
133     * <code>{@link org.apache.commons.configuration.event.ConfigurationErrorListener}</code>s
134     * registered at the builder instance will get notified about this error: they
135     * receive an event of type <code>EVENT_ERR_LOAD_OPTIONAL</code>. The key
136     * property of this event contains the name of the optional configuration source
137     * that caused this problem.)</td>
138     * </tr>
139     * </table>
140     * </p>
141     * <p>
142     * The optional <em>header</em> section can contain some meta data about the
143     * created configuration itself. For instance, it is possible to set further
144     * properties of the <code>NodeCombiner</code> objects used for constructing
145     * the resulting configuration.
146     * </p>
147     * <p>
148     * The default configuration object returned by this builder is an instance of the
149     * <code>{@link CombinedConfiguration}</code> class. The return value of the
150     * <code>getConfiguration()</code> method can be casted to this type, and the
151     * <code>getConfiguration(boolean)</code> method directly declares
152     * <code>CombinedConfiguration</code> as return type. This allows for
153     * convenient access to the configuration objects maintained by the combined
154     * configuration (e.g. for updates of single configuration objects). It has also
155     * the advantage that the properties stored in all declared configuration
156     * objects are collected and transformed into a single hierarchical structure,
157     * which can be accessed using different expression engines. The actual CombinedConfiguration
158     * implementation can be overridden by specifying the class in the <em>config-class</em>
159     * attribute of the result element.
160     * </p>
161     * <p>
162     * Additional ConfigurationProviders can be added by configuring them in the <em>header</em>
163     * section.
164     * <pre>
165     * &lt;providers&gt;
166     *   &lt;provider config-tag="tag name" config-class="provider fully qualified class name"/&gt;
167     * &lt;/providers&gt;
168     * </pre>
169     * </p>
170     * <p>
171     * Additional variable resolvers can be added by configuring them in the <em>header</em>
172     * section.
173     * <pre>
174     * &lt;lookups&gt;
175     *   &lt;lookup config-prefix="prefix" config-class="StrLookup fully qualified class name"/&gt;
176     * &lt;/lookups&gt;
177     * </pre>
178     * </p>
179     * <p>
180     * All declared override configurations are directly added to the resulting
181     * combined configuration. If they are given names (using the
182     * <code>config-name</code> attribute), they can directly be accessed using
183     * the <code>getConfiguration(String)</code> method of
184     * <code>CombinedConfiguration</code>. The additional configurations are
185     * altogether added to another combined configuration, which uses a union
186     * combiner. Then this union configuration is added to the resulting combined
187     * configuration under the name defined by the <code>ADDITIONAL_NAME</code>
188     * constant.
189     * </p>
190     * <p>
191     * Implementation note: This class is not thread-safe. Especially the
192     * <code>getConfiguration()</code> methods should be called by a single thread
193     * only.
194     * </p>
195     *
196     * @since 1.3
197     * @author <a
198     * href="http://commons.apache.org/configuration/team-list.html">Commons
199     * Configuration team</a>
200     * @version $Id: DefaultConfigurationBuilder.java 727834 2008-12-18 22:16:32Z rgoers $
201     */
202    public class DefaultConfigurationBuilder extends XMLConfiguration implements
203            ConfigurationBuilder
204    {
205        /**
206         * Constant for the name of the additional configuration. If the
207         * configuration definition file contains an <code>additional</code>
208         * section, a special union configuration is created and added under this
209         * name to the resulting combined configuration.
210         */
211        public static final String ADDITIONAL_NAME = DefaultConfigurationBuilder.class
212                .getName()
213                + "/ADDITIONAL_CONFIG";
214    
215        /**
216         * Constant for the type of error events caused by optional configurations
217         * that cannot be loaded.
218         */
219        public static final int EVENT_ERR_LOAD_OPTIONAL = 51;
220    
221        /** Constant for the name of the configuration bean factory. */
222        static final String CONFIG_BEAN_FACTORY_NAME = DefaultConfigurationBuilder.class
223                .getName()
224                + ".CONFIG_BEAN_FACTORY_NAME";
225    
226        /** Constant for the reserved name attribute. */
227        static final String ATTR_NAME = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
228                + XMLBeanDeclaration.RESERVED_PREFIX
229                + "name"
230                + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
231    
232        /** Constant for the name of the at attribute. */
233        static final String ATTR_ATNAME = "at";
234    
235        /** Constant for the reserved at attribute. */
236        static final String ATTR_AT_RES = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
237                + XMLBeanDeclaration.RESERVED_PREFIX
238                + ATTR_ATNAME
239                + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
240    
241        /** Constant for the at attribute without the reserved prefix. */
242        static final String ATTR_AT = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
243                + ATTR_ATNAME + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
244    
245        /** Constant for the name of the optional attribute. */
246        static final String ATTR_OPTIONALNAME = "optional";
247    
248        /** Constant for the reserved optional attribute. */
249        static final String ATTR_OPTIONAL_RES = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
250                + XMLBeanDeclaration.RESERVED_PREFIX
251                + ATTR_OPTIONALNAME
252                + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
253    
254        /** Constant for the optional attribute without the reserved prefix. */
255        static final String ATTR_OPTIONAL = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
256                + ATTR_OPTIONALNAME + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
257    
258        /** Constant for the file name attribute. */
259        static final String ATTR_FILENAME = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
260                + "fileName" + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
261    
262        /** Constant for the forceCreate attribute. */
263        static final String ATTR_FORCECREATE = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
264                + XMLBeanDeclaration.RESERVED_PREFIX
265                + "forceCreate"
266                + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
267    
268        /**
269         * Constant for the tag attribute for providers.
270         */
271        static final String KEY_SYSTEM_PROPS = "[@systemProperties]";
272    
273        /** Constant for the name of the header section. */
274        static final String SEC_HEADER = "header";
275    
276        /** Constant for an expression that selects the union configurations. */
277        static final String KEY_UNION = "additional";
278    
279        /** An array with the names of top level configuration sections.*/
280        static final String[] CONFIG_SECTIONS = {
281            "additional", "override", SEC_HEADER
282        };
283    
284        /**
285         * Constant for an expression that selects override configurations in the
286         * override section.
287         */
288        static final String KEY_OVERRIDE = "override";
289    
290        /**
291         * Constant for the key that points to the list nodes definition of the
292         * override combiner.
293         */
294        static final String KEY_OVERRIDE_LIST = SEC_HEADER
295                + ".combiner.override.list-nodes.node";
296    
297        /**
298         * Constant for the key that points to the list nodes definition of the
299         * additional combiner.
300         */
301        static final String KEY_ADDITIONAL_LIST = SEC_HEADER
302                + ".combiner.additional.list-nodes.node";
303    
304        /**
305         * Constant for the key for defining providers in the configuration file.
306         */
307        static final String KEY_CONFIGURATION_PROVIDERS = SEC_HEADER
308                + ".providers.provider";
309    
310        /**
311         * Constant for the tag attribute for providers.
312         */
313        static final String KEY_PROVIDER_KEY = XMLBeanDeclaration.ATTR_PREFIX + "tag]";
314    
315        /**
316         * Constant for the key for defining variable resolvers
317         */
318        static final String KEY_CONFIGURATION_LOOKUPS = SEC_HEADER
319                + ".lookups.lookup";
320    
321        /**
322         * Constant for the prefix attribute for lookups.
323         */
324        static final String KEY_LOOKUP_KEY = XMLBeanDeclaration.ATTR_PREFIX + "prefix]";
325    
326        /**
327         * Constant for the key of the result declaration. This key can point to a
328         * bean declaration, which defines properties of the resulting combined
329         * configuration.
330         */
331        static final String KEY_RESULT = SEC_HEADER + ".result";
332    
333        /** Constant for the key of the combiner in the result declaration.*/
334        static final String KEY_COMBINER = KEY_RESULT + ".nodeCombiner";
335    
336        /** Constant for the XML file extension. */
337        static final String EXT_XML = ".xml";
338    
339        /** Constant for the provider for properties files. */
340        private static final ConfigurationProvider PROPERTIES_PROVIDER = new FileExtensionConfigurationProvider(
341                XMLPropertiesConfiguration.class, PropertiesConfiguration.class,
342                EXT_XML);
343    
344        /** Constant for the provider for XML files. */
345        private static final ConfigurationProvider XML_PROVIDER = new XMLConfigurationProvider();
346    
347        /** Constant for the provider for JNDI sources. */
348        private static final ConfigurationProvider JNDI_PROVIDER = new ConfigurationProvider(
349                JNDIConfiguration.class);
350    
351        /** Constant for the provider for system properties. */
352        private static final ConfigurationProvider SYSTEM_PROVIDER = new ConfigurationProvider(
353                SystemConfiguration.class);
354    
355        /** Constant for the provider for plist files. */
356        private static final ConfigurationProvider PLIST_PROVIDER = new FileExtensionConfigurationProvider(
357                "org.apache.commons.configuration.plist.XMLPropertyListConfiguration",
358                "org.apache.commons.configuration.plist.PropertyListConfiguration",
359                EXT_XML);
360    
361        /** Constant for the provider for configuration definition files.*/
362        private static final ConfigurationProvider BUILDER_PROVIDER = new ConfigurationBuilderProvider();
363    
364        /** An array with the names of the default tags. */
365        private static final String[] DEFAULT_TAGS =
366        {"properties", "xml", "hierarchicalXml", "jndi", "system", "plist", "configuration"};
367    
368        /** An array with the providers for the default tags. */
369        private static final ConfigurationProvider[] DEFAULT_PROVIDERS =
370        {PROPERTIES_PROVIDER, XML_PROVIDER, XML_PROVIDER, JNDI_PROVIDER,
371                SYSTEM_PROVIDER, PLIST_PROVIDER, BUILDER_PROVIDER};
372    
373        /**
374         * The serial version UID.
375         */
376        private static final long serialVersionUID = -3113777854714492123L;
377    
378        /** Stores the configuration that is currently constructed.*/
379        private CombinedConfiguration constructedConfiguration;
380    
381        /** Stores a map with the registered configuration providers. */
382        private Map providers;
383    
384        /** Stores the base path to the configuration sources to load. */
385        private String configurationBasePath;
386    
387        /**
388         * Creates a new instance of <code>DefaultConfigurationBuilder</code>. A
389         * configuration definition file is not yet loaded. Use the diverse setter
390         * methods provided by file based configurations to specify the
391         * configuration definition file.
392         */
393        public DefaultConfigurationBuilder()
394        {
395            super();
396            providers = new HashMap();
397            registerDefaultProviders();
398            registerBeanFactory();
399            setLogger(LogFactory.getLog(getClass()));
400            addErrorLogListener();  // log errors per default
401        }
402    
403        /**
404         * Creates a new instance of <code>DefaultConfigurationBuilder</code> and
405         * sets the specified configuration definition file.
406         *
407         * @param file the configuration definition file
408         */
409        public DefaultConfigurationBuilder(File file)
410        {
411            this();
412            setFile(file);
413        }
414    
415        /**
416         * Creates a new instance of <code>DefaultConfigurationBuilder</code> and
417         * sets the specified configuration definition file.
418         *
419         * @param fileName the name of the configuration definition file
420         * @throws ConfigurationException if an error occurs when the file is loaded
421         */
422        public DefaultConfigurationBuilder(String fileName)
423                throws ConfigurationException
424        {
425            this();
426            setFileName(fileName);
427        }
428    
429        /**
430         * Creates a new instance of <code>DefaultConfigurationBuilder</code> and
431         * sets the specified configuration definition file.
432         *
433         * @param url the URL to the configuration definition file
434         * @throws ConfigurationException if an error occurs when the file is loaded
435         */
436        public DefaultConfigurationBuilder(URL url) throws ConfigurationException
437        {
438            this();
439            setURL(url);
440        }
441    
442        /**
443         * Returns the base path for the configuration sources to load. This path is
444         * used to resolve relative paths in the configuration definition file.
445         *
446         * @return the base path for configuration sources
447         */
448        public String getConfigurationBasePath()
449        {
450            return (configurationBasePath != null) ? configurationBasePath
451                    : getBasePath();
452        }
453    
454        /**
455         * Sets the base path for the configuration sources to load. Normally a base
456         * path need not to be set because it is determined by the location of the
457         * configuration definition file to load. All relative paths in this file
458         * are resolved relative to this file. Setting a base path makes sense if
459         * such relative paths should be otherwise resolved, e.g. if the
460         * configuration file is loaded from the class path and all sub
461         * configurations it refers to are stored in a special config directory.
462         *
463         * @param configurationBasePath the new base path to set
464         */
465        public void setConfigurationBasePath(String configurationBasePath)
466        {
467            this.configurationBasePath = configurationBasePath;
468        }
469    
470        /**
471         * Adds a configuration provider for the specified tag. Whenever this tag is
472         * encountered in the configuration definition file this provider will be
473         * called to create the configuration object.
474         *
475         * @param tagName the name of the tag in the configuration definition file
476         * @param provider the provider for this tag
477         */
478        public void addConfigurationProvider(String tagName,
479                ConfigurationProvider provider)
480        {
481            if (tagName == null)
482            {
483                throw new IllegalArgumentException("Tag name must not be null!");
484            }
485            if (provider == null)
486            {
487                throw new IllegalArgumentException("Provider must not be null!");
488            }
489    
490            providers.put(tagName, provider);
491        }
492    
493        /**
494         * Removes the configuration provider for the specified tag name.
495         *
496         * @param tagName the tag name
497         * @return the removed configuration provider or <b>null</b> if none was
498         * registered for that tag
499         */
500        public ConfigurationProvider removeConfigurationProvider(String tagName)
501        {
502            return (ConfigurationProvider) providers.remove(tagName);
503        }
504    
505        /**
506         * Returns the configuration provider for the given tag.
507         *
508         * @param tagName the name of the tag
509         * @return the provider that was registered for this tag or <b>null</b> if
510         * there is none
511         */
512        public ConfigurationProvider providerForTag(String tagName)
513        {
514            return (ConfigurationProvider) providers.get(tagName);
515        }
516    
517        /**
518         * Returns the configuration provided by this builder. Loads and parses the
519         * configuration definition file and creates instances for the declared
520         * configurations.
521         *
522         * @return the configuration
523         * @throws ConfigurationException if an error occurs
524         */
525        public Configuration getConfiguration() throws ConfigurationException
526        {
527            return getConfiguration(true);
528        }
529    
530        /**
531         * Returns the configuration provided by this builder. If the boolean
532         * parameter is <b>true</b>, the configuration definition file will be
533         * loaded. It will then be parsed, and instances for the declared
534         * configurations will be created.
535         *
536         * @param load a flag whether the configuration definition file should be
537         * loaded; a value of <b>false</b> would make sense if the file has already
538         * been created or its content was manipulated using some of the property
539         * accessor methods
540         * @return the configuration
541         * @throws ConfigurationException if an error occurs
542         */
543        public CombinedConfiguration getConfiguration(boolean load)
544                throws ConfigurationException
545        {
546            if (load)
547            {
548                load();
549            }
550    
551            initSystemProperties();
552            registerConfiguredProviders();
553            registerConfiguredLookups();
554    
555            CombinedConfiguration result = createResultConfiguration();
556            constructedConfiguration = result;
557    
558            List overrides = fetchTopLevelOverrideConfigs();
559            overrides.addAll(fetchChildConfigs(KEY_OVERRIDE));
560            initCombinedConfiguration(result, overrides, KEY_OVERRIDE_LIST);
561    
562            List additionals = fetchChildConfigs(KEY_UNION);
563            if (!additionals.isEmpty())
564            {
565                CombinedConfiguration addConfig = new CombinedConfiguration(
566                        new UnionCombiner());
567                result.addConfiguration(addConfig, ADDITIONAL_NAME);
568                initCombinedConfiguration(addConfig, additionals,
569                        KEY_ADDITIONAL_LIST);
570            }
571    
572            return result;
573        }
574    
575        /**
576         * Creates the resulting combined configuration. This method is called by
577         * <code>getConfiguration()</code>. It checks whether the
578         * <code>header</code> section of the configuration definition file
579         * contains a <code>result</code> element. If this is the case, it will be
580         * used to initialize the properties of the newly created configuration
581         * object.
582         *
583         * @return the resulting configuration object
584         * @throws ConfigurationException if an error occurs
585         */
586        protected CombinedConfiguration createResultConfiguration()
587                throws ConfigurationException
588        {
589            XMLBeanDeclaration decl = new XMLBeanDeclaration(this, KEY_RESULT, true);
590            CombinedConfiguration result = (CombinedConfiguration) BeanHelper
591                    .createBean(decl, CombinedConfiguration.class);
592    
593            if (getMaxIndex(KEY_COMBINER) < 0)
594            {
595                // No combiner defined => set default
596                result.setNodeCombiner(new OverrideCombiner());
597            }
598    
599            return result;
600        }
601    
602        /**
603         * Initializes a combined configuration for the configurations of a specific
604         * section. This method is called for the override and for the additional
605         * section (if it exists).
606         *
607         * @param config the configuration to be initialized
608         * @param containedConfigs the list with the declaratinos of the contained
609         * configurations
610         * @param keyListNodes a list with the declaration of list nodes
611         * @throws ConfigurationException if an error occurs
612         */
613        protected void initCombinedConfiguration(CombinedConfiguration config,
614                List containedConfigs, String keyListNodes) throws ConfigurationException
615        {
616            List listNodes = getList(keyListNodes);
617            for (Iterator it = listNodes.iterator(); it.hasNext();)
618            {
619                config.getNodeCombiner().addListNode((String) it.next());
620            }
621    
622            for (Iterator it = containedConfigs.iterator(); it.hasNext();)
623            {
624                HierarchicalConfiguration conf = (HierarchicalConfiguration) it
625                        .next();
626                ConfigurationDeclaration decl = new ConfigurationDeclaration(this,
627                        conf);
628                AbstractConfiguration newConf = createConfigurationAt(decl);
629                if (newConf != null)
630                {
631                    config.addConfiguration(newConf, decl.getConfiguration()
632                            .getString(ATTR_NAME), decl.getAt());
633                }
634            }
635        }
636    
637        /**
638         * Registers the default configuration providers supported by this class.
639         * This method will be called during initialization. It registers
640         * configuration providers for the tags that are supported by default.
641         */
642        protected void registerDefaultProviders()
643        {
644            for (int i = 0; i < DEFAULT_TAGS.length; i++)
645            {
646                addConfigurationProvider(DEFAULT_TAGS[i], DEFAULT_PROVIDERS[i]);
647            }
648        }
649    
650        /**
651         * Registers providers defined in the configuration.
652         *
653         * @throws ConfigurationException if an error occurs
654         */
655        protected void registerConfiguredProviders() throws ConfigurationException
656        {
657            List nodes = configurationsAt(KEY_CONFIGURATION_PROVIDERS);
658            for (Iterator it = nodes.iterator(); it.hasNext();)
659            {
660                HierarchicalConfiguration config = (HierarchicalConfiguration) it.next();
661                XMLBeanDeclaration decl = new XMLBeanDeclaration(config);
662                String key = config.getString(KEY_PROVIDER_KEY);
663                addConfigurationProvider(key, (ConfigurationProvider) BeanHelper
664                        .createBean(decl));
665            }
666        }
667    
668        /**
669         * Registers StrLookups defined in the configuration.
670         *
671         * @throws ConfigurationException if an error occurs
672         */
673        protected void registerConfiguredLookups() throws ConfigurationException
674        {
675            List nodes = configurationsAt(KEY_CONFIGURATION_LOOKUPS);
676            for (Iterator it = nodes.iterator(); it.hasNext();)
677            {
678                HierarchicalConfiguration config = (HierarchicalConfiguration) it.next();
679                XMLBeanDeclaration decl = new XMLBeanDeclaration(config);
680                String key = config.getString(KEY_LOOKUP_KEY);
681                ConfigurationInterpolator.registerGlobalLookup(key, (StrLookup) BeanHelper.createBean(decl));
682            }
683        }
684    
685        /**
686         * If a property file is configured add the properties to the System properties.
687         * @throws ConfigurationException if an error occurs.
688         */
689        protected void initSystemProperties() throws ConfigurationException
690        {
691            String fileName = getString(KEY_SYSTEM_PROPS);
692            if (fileName != null)
693            {
694                try
695                {
696                   SystemConfiguration.setSystemProperties(fileName);
697                }
698                catch (Exception ex)
699                {
700                    throw new ConfigurationException("Error setting system properties from " + fileName, ex);
701                }
702    
703            }
704        }
705    
706        /**
707         * Performs interpolation. This method will not only take this configuration
708         * instance into account (which is the one that loaded the configuration
709         * definition file), but also the so far constructed combined configuration.
710         * So variables can be used that point to properties that are defined in
711         * configuration sources loaded by this builder.
712         *
713         * @param value the value to be interpolated
714         * @return the interpolated value
715         */
716        protected Object interpolate(Object value)
717        {
718            Object result = super.interpolate(value);
719            if (constructedConfiguration != null)
720            {
721                result = constructedConfiguration.interpolate(result);
722            }
723            return result;
724        }
725    
726        protected void fireError(int type, String propName, Object propValue,
727                Throwable ex)
728        {
729            // This method is only overridden to fix a mysterious MethodNotFound
730            // error in the test cases when run under a JDK 1.3.
731            super.fireError(type, propName, propValue, ex);
732        }
733    
734        /**
735         * Creates a configuration object from the specified configuration
736         * declaration.
737         *
738         * @param decl the configuration declaration
739         * @return the new configuration object
740         * @throws ConfigurationException if an error occurs
741         */
742        private AbstractConfiguration createConfigurationAt(
743                ConfigurationDeclaration decl) throws ConfigurationException
744        {
745            try
746            {
747                return (AbstractConfiguration) BeanHelper.createBean(decl);
748            }
749            catch (Exception ex)
750            {
751                // redirect to configuration exceptions
752                throw new ConfigurationException(ex);
753            }
754        }
755    
756        /**
757         * Returns a list with <code>SubnodeConfiguration</code> objects for the
758         * child nodes of the specified configuration node.
759         *
760         * @param node the start node
761         * @return a list with subnode configurations for the node's children
762         */
763        private List fetchChildConfigs(ConfigurationNode node)
764        {
765            List children = node.getChildren();
766            List result = new ArrayList(children.size());
767            for (Iterator it = children.iterator(); it.hasNext();)
768            {
769                result.add(createSubnodeConfiguration((Node) it.next()));
770            }
771            return result;
772        }
773    
774        /**
775         * Returns a list with <code>SubnodeConfiguration</code> objects for the
776         * child nodes of the node specified by the given key.
777         *
778         * @param key the key (must define exactly one node)
779         * @return a list with subnode configurations for the node's children
780         */
781        private List fetchChildConfigs(String key)
782        {
783            List nodes = fetchNodeList(key);
784            if (nodes.size() > 0)
785            {
786                return fetchChildConfigs((ConfigurationNode) nodes.get(0));
787            }
788            else
789            {
790                return Collections.EMPTY_LIST;
791            }
792        }
793    
794        /**
795         * Finds the override configurations that are defined as top level elements
796         * in the configuration definition file. This method will fetch the child
797         * elements of the root node and remove the nodes that represent other
798         * configuration sections. The remaining nodes are treated as definitions
799         * for override configurations.
800         *
801         * @return a list with subnode configurations for the top level override
802         * configurations
803         */
804        private List fetchTopLevelOverrideConfigs()
805        {
806            List configs = fetchChildConfigs(getRootNode());
807            for (Iterator it = configs.iterator(); it.hasNext();)
808            {
809                String nodeName = ((SubnodeConfiguration) it.next()).getRootNode()
810                        .getName();
811                for (int i = 0; i < CONFIG_SECTIONS.length; i++)
812                {
813                    if (CONFIG_SECTIONS[i].equals(nodeName))
814                    {
815                        it.remove();
816                        break;
817                    }
818                }
819            }
820            return configs;
821        }
822    
823        /**
824         * Registers the bean factory used by this class if necessary. This method
825         * is called by the constructor to ensure that the required bean factory is
826         * available.
827         */
828        private void registerBeanFactory()
829        {
830            synchronized (DefaultConfigurationBuilder.class)
831            {
832                if (!BeanHelper.registeredFactoryNames().contains(
833                        CONFIG_BEAN_FACTORY_NAME))
834                {
835                    BeanHelper.registerBeanFactory(CONFIG_BEAN_FACTORY_NAME,
836                            new ConfigurationBeanFactory());
837                }
838            }
839        }
840    
841        /**
842         * <p>
843         * A base class for creating and initializing configuration sources.
844         * </p>
845         * <p>
846         * Concrete sub classes of this base class are responsible for creating
847         * specific <code>Configuration</code> objects for the tags in the
848         * configuration definition file. The configuration factory will parse the
849         * definition file and try to find a matching
850         * <code>ConfigurationProvider</code> for each encountered tag. This
851         * provider is then asked to create a corresponding
852         * <code>Configuration</code> object. It is up to a concrete
853         * implementation how this object is created and initialized.
854         * </p>
855         * <p>
856         * Note that at the moment only configuration classes derived from
857         * <code>{@link AbstractConfiguration}</code> are supported.
858         * </p>
859         */
860        public static class ConfigurationProvider extends DefaultBeanFactory
861        {
862            /** Stores the class of the configuration to be created. */
863            private Class configurationClass;
864    
865            /** Stores the name of the configuration class to be created.*/
866            private String configurationClassName;
867    
868            /**
869             * Creates a new uninitialized instance of
870             * <code>ConfigurationProvider</code>.
871             */
872            public ConfigurationProvider()
873            {
874                this((Class) null);
875            }
876    
877            /**
878             * Creates a new instance of <code>ConfigurationProvider</code> and
879             * sets the class of the configuration created by this provider.
880             *
881             * @param configClass the configuration class
882             */
883            public ConfigurationProvider(Class configClass)
884            {
885                setConfigurationClass(configClass);
886            }
887    
888            /**
889             * Creates a new instance of <code>ConfigurationProvider</code> and
890             * sets the name of the class of the configuration created by this
891             * provider.
892             *
893             * @param configClassName the name of the configuration class
894             * @since 1.4
895             */
896            public ConfigurationProvider(String configClassName)
897            {
898                setConfigurationClassName(configClassName);
899            }
900    
901            /**
902             * Returns the class of the configuration returned by this provider.
903             *
904             * @return the class of the provided configuration
905             */
906            public Class getConfigurationClass()
907            {
908                return configurationClass;
909            }
910    
911            /**
912             * Sets the class of the configuration returned by this provider.
913             *
914             * @param configurationClass the configuration class
915             */
916            public void setConfigurationClass(Class configurationClass)
917            {
918                this.configurationClass = configurationClass;
919            }
920    
921            /**
922             * Returns the name of the configuration class returned by this
923             * provider.
924             *
925             * @return the configuration class name
926             * @since 1.4
927             */
928            public String getConfigurationClassName()
929            {
930                return configurationClassName;
931            }
932    
933            /**
934             * Sets the name of the configuration class returned by this provider.
935             *
936             * @param configurationClassName the name of the configuration class
937             * @since 1.4
938             */
939            public void setConfigurationClassName(String configurationClassName)
940            {
941                this.configurationClassName = configurationClassName;
942            }
943    
944            /**
945             * Returns the configuration. This method is called to fetch the
946             * configuration from the provider. This implementation will call the
947             * inherited <code>{@link
948             * org.apache.commons.configuration.beanutils.DefaultBeanFactory#createBean(Class, BeanDeclaration, Object)
949             * createBean()}</code> method to create a new instance of the
950             * configuration class.
951             *
952             * @param decl the bean declaration with initialization parameters for
953             * the configuration
954             * @return the new configuration object
955             * @throws Exception if an error occurs
956             */
957            public AbstractConfiguration getConfiguration(
958                    ConfigurationDeclaration decl) throws Exception
959            {
960                return (AbstractConfiguration) createBean(fetchConfigurationClass(),
961                        decl, null);
962            }
963    
964            /**
965             * Returns an uninitialized configuration of the represented type. This
966             * method will be called for optional configurations when the
967             * <code>getConfiguration()</code> method caused an error and the
968             * <code>forceCreate</code> attribute is set. A concrete sub class can
969             * here try to create an uninitialized, empty configuration, which may
970             * be possible if the error was created during initialization. This base
971             * implementation just returns <b>null</b>.
972             *
973             * @param decl the bean declaration with initialization parameters for
974             * the configuration
975             * @return the new configuration object
976             * @throws Exception if an error occurs
977             * @since 1.4
978             */
979            public AbstractConfiguration getEmptyConfiguration(
980                    ConfigurationDeclaration decl) throws Exception
981            {
982                return null;
983            }
984    
985            /**
986             * Returns the configuration class supported by this provider. If a
987             * class object was set, it is returned. Otherwise the method tries to
988             * resolve the class name.
989             *
990             * @return the class of the configuration to be created
991             * @since 1.4
992             */
993            protected synchronized Class fetchConfigurationClass() throws Exception
994            {
995                if (getConfigurationClass() == null)
996                {
997                    setConfigurationClass(loadClass(getConfigurationClassName()));
998                }
999                return getConfigurationClass();
1000            }
1001    
1002            /**
1003             * Loads the class with the specified name dynamically. If the class's
1004             * name is <b>null</b>, <b>null</b> will also be returned.
1005             *
1006             * @param className the name of the class to be loaded
1007             * @return the class object
1008             * @throws ClassNotFoundException if class loading fails
1009             * @since 1.4
1010             */
1011            protected Class loadClass(String className)
1012                    throws ClassNotFoundException
1013            {
1014                return (className != null) ? Class.forName(className, true,
1015                        getClass().getClassLoader()) : null;
1016            }
1017        }
1018    
1019        /**
1020         * <p>
1021         * A specialized <code>BeanDeclaration</code> implementation that
1022         * represents the declaration of a configuration source.
1023         * </p>
1024         * <p>
1025         * Instances of this class are able to extract all information about a
1026         * configuration source from the configuration definition file. The
1027         * declaration of a configuration source is very similar to a bean
1028         * declaration processed by <code>XMLBeanDeclaration</code>. There are
1029         * very few differences, e.g. some reserved attributes like
1030         * <code>optional</code> and <code>at</code> and the fact that a bean
1031         * factory is never needed.
1032         * </p>
1033         */
1034        public static class ConfigurationDeclaration extends XMLBeanDeclaration
1035        {
1036            /** Stores a reference to the associated configuration builder. */
1037            private DefaultConfigurationBuilder configurationBuilder;
1038    
1039            /**
1040             * Creates a new instance of <code>ConfigurationDeclaration</code> and
1041             * initializes it.
1042             *
1043             * @param builder the associated configuration builder
1044             * @param config the configuration this declaration is based onto
1045             */
1046            public ConfigurationDeclaration(DefaultConfigurationBuilder builder,
1047                    HierarchicalConfiguration config)
1048            {
1049                super(config);
1050                configurationBuilder = builder;
1051            }
1052    
1053            /**
1054             * Returns the associated configuration builder.
1055             *
1056             * @return the configuration builder
1057             */
1058            public DefaultConfigurationBuilder getConfigurationBuilder()
1059            {
1060                return configurationBuilder;
1061            }
1062    
1063            /**
1064             * Returns the value of the <code>at</code> attribute.
1065             *
1066             * @return the value of the <code>at</code> attribute (can be <b>null</b>)
1067             */
1068            public String getAt()
1069            {
1070                String result = this.getConfiguration().getString(ATTR_AT_RES);
1071                return (result == null) ? this.getConfiguration().getString(ATTR_AT)
1072                        : result;
1073            }
1074    
1075            /**
1076             * Returns a flag whether this is an optional configuration.
1077             *
1078             * @return a flag if this declaration points to an optional
1079             * configuration
1080             */
1081            public boolean isOptional()
1082            {
1083                Boolean value = this.getConfiguration().getBoolean(ATTR_OPTIONAL_RES,
1084                        null);
1085                if (value == null)
1086                {
1087                    value = this.getConfiguration().getBoolean(ATTR_OPTIONAL,
1088                            Boolean.FALSE);
1089                }
1090                return value.booleanValue();
1091            }
1092    
1093            /**
1094             * Returns a flag whether this configuration should always be created
1095             * and added to the resulting combined configuration. This flag is
1096             * evaluated only for optional configurations whose normal creation has
1097             * caused an error. If for such a configuration the
1098             * <code>forceCreate</code> attribute is set and the corresponding
1099             * configuration provider supports this mode, an empty configuration
1100             * will be created and added to the resulting combined configuration.
1101             *
1102             * @return the value of the <code>forceCreate</code> attribute
1103             * @since 1.4
1104             */
1105            public boolean isForceCreate()
1106            {
1107                return this.getConfiguration().getBoolean(ATTR_FORCECREATE, false);
1108            }
1109    
1110            /**
1111             * Returns the name of the bean factory. For configuration source
1112             * declarations always a reserved factory is used. This factory's name
1113             * is returned by this implementation.
1114             *
1115             * @return the name of the bean factory
1116             */
1117            public String getBeanFactoryName()
1118            {
1119                return CONFIG_BEAN_FACTORY_NAME;
1120            }
1121    
1122            /**
1123             * Returns the bean's class name. This implementation will always return
1124             * <b>null</b>.
1125             *
1126             * @return the name of the bean's class
1127             */
1128            public String getBeanClassName()
1129            {
1130                return null;
1131            }
1132    
1133            /**
1134             * Checks whether the given node is reserved. This method will take
1135             * further reserved attributes into account
1136             *
1137             * @param nd the node
1138             * @return a flag whether this node is reserved
1139             */
1140            protected boolean isReservedNode(ConfigurationNode nd)
1141            {
1142                if (super.isReservedNode(nd))
1143                {
1144                    return true;
1145                }
1146    
1147                return nd.isAttribute()
1148                        && ((ATTR_ATNAME.equals(nd.getName()) && nd.getParentNode()
1149                                .getAttributeCount(RESERVED_PREFIX + ATTR_ATNAME) == 0) || (ATTR_OPTIONALNAME
1150                                .equals(nd.getName()) && nd.getParentNode()
1151                                .getAttributeCount(RESERVED_PREFIX + ATTR_OPTIONALNAME) == 0));
1152            }
1153    
1154            /**
1155             * Performs interpolation. This implementation will delegate
1156             * interpolation to the configuration builder, which takes care that the
1157             * currently constructed configuration is taken into account, too.
1158             *
1159             * @param value the value to be interpolated
1160             * @return the interpolated value
1161             */
1162            protected Object interpolate(Object value)
1163            {
1164                return getConfigurationBuilder().interpolate(value);
1165            }
1166        }
1167    
1168        /**
1169         * A specialized <code>BeanFactory</code> implementation that handles
1170         * configuration declarations. This class will retrieve the correct
1171         * configuration provider and delegate the task of creating the
1172         * configuration to this object.
1173         */
1174        static class ConfigurationBeanFactory implements BeanFactory
1175        {
1176            /**
1177             * Creates an instance of a bean class. This implementation expects that
1178             * the passed in bean declaration is a declaration for a configuration.
1179             * It will determine the responsible configuration provider and delegate
1180             * the call to this instance. If creation of the configuration fails
1181             * and the <code>optional</code> attribute is set, the exception will
1182             * be ignored. If the <code>forceCreate</code> attribute is set, too,
1183             * the provider is asked to create an empty configuration. A return
1184             * value of <b>null</b> means that no configuration could be created.
1185             *
1186             * @param beanClass the bean class (will be ignored)
1187             * @param data the declaration
1188             * @param param an additional parameter (will be ignored)
1189             * @return the newly created configuration
1190             * @throws Exception if an error occurs
1191             */
1192            public Object createBean(Class beanClass, BeanDeclaration data,
1193                    Object param) throws Exception
1194            {
1195                ConfigurationDeclaration decl = (ConfigurationDeclaration) data;
1196                String tagName = decl.getNode().getName();
1197                ConfigurationProvider provider = decl.getConfigurationBuilder()
1198                        .providerForTag(tagName);
1199                if (provider == null)
1200                {
1201                    throw new ConfigurationRuntimeException(
1202                            "No ConfigurationProvider registered for tag "
1203                                    + tagName);
1204                }
1205    
1206                try
1207                {
1208                    return provider.getConfiguration(decl);
1209                }
1210                catch (Exception ex)
1211                {
1212                    // If this is an optional configuration, ignore the exception
1213                    if (!decl.isOptional())
1214                    {
1215                        throw ex;
1216                    }
1217                    else
1218                    {
1219                        // Notify registered error listeners
1220                        decl.getConfigurationBuilder().fireError(
1221                                EVENT_ERR_LOAD_OPTIONAL,
1222                                decl.getConfiguration().getString(ATTR_NAME), null,
1223                                ex);
1224    
1225                        if (decl.isForceCreate())
1226                        {
1227                            try
1228                            {
1229                                return provider.getEmptyConfiguration(decl);
1230                            }
1231                            catch (Exception ex2)
1232                            {
1233                                // Ignore exception, return null in this case
1234                                ;
1235                            }
1236                        }
1237                        return null;
1238                    }
1239                }
1240            }
1241    
1242            /**
1243             * Returns the default class for this bean factory.
1244             *
1245             * @return the default class
1246             */
1247            public Class getDefaultBeanClass()
1248            {
1249                // Here some valid class must be returned, otherwise BeanHelper
1250                // will complain that the bean's class cannot be determined
1251                return Configuration.class;
1252            }
1253        }
1254    
1255        /**
1256         * A specialized provider implementation that deals with file based
1257         * configurations. Ensures that the base path is correctly set and that the
1258         * load() method gets called.
1259         */
1260        public static class FileConfigurationProvider extends ConfigurationProvider
1261        {
1262            /**
1263             * Creates a new instance of <code>FileConfigurationProvider</code>.
1264             */
1265            public FileConfigurationProvider()
1266            {
1267                super();
1268            }
1269    
1270            /**
1271             * Creates a new instance of <code>FileConfigurationProvider</code>
1272             * and sets the configuration class.
1273             *
1274             * @param configClass the class for the configurations to be created
1275             */
1276            public FileConfigurationProvider(Class configClass)
1277            {
1278                super(configClass);
1279            }
1280    
1281            /**
1282             * Creates a new instance of <code>FileConfigurationProvider</code>
1283             * and sets the configuration class name.
1284             *
1285             * @param configClassName the name of the configuration to be created
1286             * @since 1.4
1287             */
1288            public FileConfigurationProvider(String configClassName)
1289            {
1290                super(configClassName);
1291            }
1292    
1293            /**
1294             * Creates the configuration. After that <code>load()</code> will be
1295             * called. If this configuration is marked as optional, exceptions will
1296             * be ignored.
1297             *
1298             * @param decl the declaration
1299             * @return the new configuration
1300             * @throws Exception if an error occurs
1301             */
1302            public AbstractConfiguration getConfiguration(
1303                    ConfigurationDeclaration decl) throws Exception
1304            {
1305                AbstractConfiguration result = getEmptyConfiguration(decl);
1306                ((FileConfiguration) result).load();
1307                return result;
1308            }
1309    
1310            /**
1311             * Returns an uninitialized file configuration. This method will be
1312             * called for optional configurations when the
1313             * <code>getConfiguration()</code> method caused an error and the
1314             * <code>forceCreate</code> attribute is set. It will create the
1315             * configuration of the represented type, but the <code>load()</code>
1316             * method won't be called. This way non-existing configuration files can
1317             * be handled gracefully: If loading a the file fails, an empty
1318             * configuration will be created that is already configured with the
1319             * correct file name.
1320             *
1321             * @param decl the bean declaration with initialization parameters for
1322             * the configuration
1323             * @return the new configuration object
1324             * @throws Exception if an error occurs
1325             * @since 1.4
1326             */
1327            public AbstractConfiguration getEmptyConfiguration(
1328                    ConfigurationDeclaration decl) throws Exception
1329            {
1330                return super.getConfiguration(decl);
1331            }
1332    
1333            /**
1334             * Initializes the bean instance. Ensures that the file configuration's
1335             * base path will be initialized with the base path of the factory so
1336             * that relative path names can be correctly resolved.
1337             *
1338             * @param bean the bean to be initialized
1339             * @param data the declaration
1340             * @throws Exception if an error occurs
1341             */
1342            protected void initBeanInstance(Object bean, BeanDeclaration data)
1343                    throws Exception
1344            {
1345                FileConfiguration config = (FileConfiguration) bean;
1346                config.setBasePath(((ConfigurationDeclaration) data)
1347                        .getConfigurationBuilder().getConfigurationBasePath());
1348                super.initBeanInstance(bean, data);
1349            }
1350        }
1351    
1352        /**
1353         * A specialized configuration provider for XML configurations. This
1354         * implementation acts like a <code>FileConfigurationProvider</code>, but
1355         * it will copy all entity IDs that have been registered for the
1356         * configuration builder to the new XML configuration before it is loaded.
1357         *
1358         * @since 1.6
1359         */
1360        public static class XMLConfigurationProvider extends FileConfigurationProvider
1361        {
1362            /**
1363             * Creates a new instance of <code>XMLConfigurationProvider</code>.
1364             */
1365            public XMLConfigurationProvider()
1366            {
1367                super(XMLConfiguration.class);
1368            }
1369    
1370            /**
1371             * Returns a new empty configuration instance. This implementation
1372             * performs some additional initialization specific to XML
1373             * configurations.
1374             *
1375             * @param decl the configuration declaration
1376             * @return the new configuration
1377             * @throws Exception if an error occurs
1378             */
1379            public AbstractConfiguration getEmptyConfiguration(
1380                    ConfigurationDeclaration decl) throws Exception
1381            {
1382                XMLConfiguration config = (XMLConfiguration) super
1383                        .getEmptyConfiguration(decl);
1384    
1385                // copy the registered entities
1386                DefaultConfigurationBuilder builder = decl
1387                        .getConfigurationBuilder();
1388                config.getRegisteredEntities().putAll(
1389                        builder.getRegisteredEntities());
1390                return config;
1391            }
1392        }
1393    
1394        /**
1395         * A specialized configuration provider for file based configurations that
1396         * can handle configuration sources whose concrete type depends on the
1397         * extension of the file to be loaded. One example is the
1398         * <code>properties</code> tag: if the file ends with ".xml" a
1399         * XMLPropertiesConfiguration object must be created, otherwise a
1400         * PropertiesConfiguration object.
1401         */
1402        static class FileExtensionConfigurationProvider extends
1403                FileConfigurationProvider
1404        {
1405            /**
1406             * Stores the class to be created when the file extension matches.
1407             */
1408            private Class matchingClass;
1409    
1410            /**
1411             * Stores the name of the class to be created when the file extension
1412             * matches.
1413             */
1414            private String matchingClassName;
1415    
1416            /**
1417             * Stores the class to be created when the file extension does not
1418             * match.
1419             */
1420            private Class defaultClass;
1421    
1422            /**
1423             * Stores the name of the class to be created when the file extension
1424             * does not match.
1425             */
1426            private String defaultClassName;
1427    
1428            /** Stores the file extension to be checked against. */
1429            private String fileExtension;
1430    
1431            /**
1432             * Creates a new instance of
1433             * <code>FileExtensionConfigurationProvider</code> and initializes it.
1434             *
1435             * @param matchingClass the class to be created when the file extension
1436             * matches
1437             * @param defaultClass the class to be created when the file extension
1438             * does not match
1439             * @param extension the file extension to be checked agains
1440             */
1441            public FileExtensionConfigurationProvider(Class matchingClass,
1442                    Class defaultClass, String extension)
1443            {
1444                this.matchingClass = matchingClass;
1445                this.defaultClass = defaultClass;
1446                fileExtension = extension;
1447            }
1448    
1449            /**
1450             * Creates a new instance of
1451             * <code>FileExtensionConfigurationProvider</code> and initializes it
1452             * with the names of the classes to be created.
1453             *
1454             * @param matchingClassName the name of the class to be created when the
1455             * file extension matches
1456             * @param defaultClassName the name of the class to be created when the
1457             * file extension does not match
1458             * @param extension the file extension to be checked against
1459             * @since 1.4
1460             */
1461            public FileExtensionConfigurationProvider(String matchingClassName,
1462                    String defaultClassName, String extension)
1463            {
1464                this.matchingClassName = matchingClassName;
1465                this.defaultClassName = defaultClassName;
1466                fileExtension = extension;
1467            }
1468    
1469            /**
1470             * Returns the matching class object, no matter whether it was defined
1471             * as a class or as a class name.
1472             *
1473             * @return the matching class object
1474             * @throws Exception if an error occurs
1475             * @since 1.4
1476             */
1477            protected synchronized Class fetchMatchingClass() throws Exception
1478            {
1479                if (matchingClass == null)
1480                {
1481                    matchingClass = loadClass(matchingClassName);
1482                }
1483                return matchingClass;
1484            }
1485    
1486            /**
1487             * Returns the default class object, no matter whether it was defined as
1488             * a class or as a class name.
1489             *
1490             * @return the default class object
1491             * @throws Exception if an error occurs
1492             * @since 1.4
1493             */
1494            protected synchronized Class fetchDefaultClass() throws Exception
1495            {
1496                if (defaultClass == null)
1497                {
1498                    defaultClass = loadClass(defaultClassName);
1499                }
1500                return defaultClass;
1501            }
1502    
1503            /**
1504             * Creates the configuration object. The class is determined by the file
1505             * name's extension.
1506             *
1507             * @param beanClass the class
1508             * @param data the bean declaration
1509             * @return the new bean
1510             * @throws Exception if an error occurs
1511             */
1512            protected Object createBeanInstance(Class beanClass,
1513                    BeanDeclaration data) throws Exception
1514            {
1515                String fileName = ((ConfigurationDeclaration) data)
1516                        .getConfiguration().getString(ATTR_FILENAME);
1517                if (fileName != null
1518                        && fileName.toLowerCase().trim().endsWith(fileExtension))
1519                {
1520                    return super.createBeanInstance(fetchMatchingClass(), data);
1521                }
1522                else
1523                {
1524                    return super.createBeanInstance(fetchDefaultClass(), data);
1525                }
1526            }
1527        }
1528    
1529        /**
1530         * A specialized configuration provider class that allows to include other
1531         * configuration definition files.
1532         */
1533        static class ConfigurationBuilderProvider extends ConfigurationProvider
1534        {
1535            /**
1536             * Creates a new instance of <code>ConfigurationBuilderProvider</code>.
1537             */
1538            public ConfigurationBuilderProvider()
1539            {
1540                super(DefaultConfigurationBuilder.class);
1541            }
1542    
1543            /**
1544             * Creates the configuration. First creates a configuration builder
1545             * object. Then returns the configuration created by this builder.
1546             *
1547             * @param decl the configuration declaration
1548             * @return the configuration
1549             * @exception Exception if an error occurs
1550             */
1551            public AbstractConfiguration getConfiguration(
1552                    ConfigurationDeclaration decl) throws Exception
1553            {
1554                DefaultConfigurationBuilder builder = (DefaultConfigurationBuilder) super
1555                        .getConfiguration(decl);
1556                return builder.getConfiguration(true);
1557            }
1558    
1559            /**
1560             * Returns an empty configuration in case of an optional configuration
1561             * could not be created. This implementation returns an empty combined
1562             * configuration.
1563             *
1564             * @param decl the configuration declaration
1565             * @return the configuration
1566             * @exception Exception if an error occurs
1567             * @since 1.4
1568             */
1569            public AbstractConfiguration getEmptyConfiguration(
1570                    ConfigurationDeclaration decl) throws Exception
1571            {
1572                return new CombinedConfiguration();
1573            }
1574        }
1575    }