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 }