1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17 package org.apache.commons.configuration;
18
19 import java.io.File;
20 import java.net.URL;
21 import java.util.ArrayList;
22 import java.util.Collections;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
27
28 import org.apache.commons.configuration.beanutils.BeanDeclaration;
29 import org.apache.commons.configuration.beanutils.BeanFactory;
30 import org.apache.commons.configuration.beanutils.BeanHelper;
31 import org.apache.commons.configuration.beanutils.DefaultBeanFactory;
32 import org.apache.commons.configuration.beanutils.XMLBeanDeclaration;
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
38 /***
39 * <p>
40 * A factory class that creates a composite configuration from an XML based
41 * <em>configuration definition file</em>.
42 * </p>
43 * <p>
44 * This class provides an easy and flexible means for loading multiple
45 * configuration sources and combining the results into a single configuration
46 * object. The sources to be loaded are defined in an XML document that can
47 * contain certain tags representing the different supported configuration
48 * classes. If such a tag is found, the corresponding <code>Configuration</code>
49 * class is instantiated and initialized using the classes of the
50 * <code>beanutils</code> package (namely
51 * <code>{@link org.apache.commons.configuration.beanutils.XMLBeanDeclaration XMLBeanDeclaration}</code>
52 * will be used to extract the configuration's initialization parameters, which
53 * allows for complex initialization szenarios).
54 * </p>
55 * <p>
56 * It is also possible to add custom tags to the configuration definition file.
57 * For this purpose register your own <code>ConfigurationProvider</code>
58 * implementation for your tag using the <code>addConfigurationProvider()</code>
59 * method. This provider will then be called when the corresponding custom tag
60 * is detected. For the default configuration classes providers are already
61 * registered.
62 * </p>
63 * <p>
64 * The configuration definition file has the following basic structure:
65 * </p>
66 * <p>
67 *
68 * <pre>
69 * <configuration>
70 * <header>
71 * <!-- Optional meta information about the composite configuration -->
72 * </header>
73 * <override>
74 * <!-- Declarations for override configurations -->
75 * </override>
76 * <additional>
77 * <!-- Declarations for union configurations -->
78 * </additional>
79 * </configuration>
80 * </pre>
81 *
82 * </p>
83 * <p>
84 * The name of the root element (here <code>configuration</code>) is
85 * arbitrary. There are two sections (both of them are optional) for declaring
86 * <em>override</em> and <em>additional</em> configurations. Configurations
87 * in the former section are evaluated in the order of their declaration, and
88 * properties of configurations declared earlier hide those of configurations
89 * declared later. Configurations in the latter section are combined to a union
90 * configuration, i.e. all of their properties are added to a large hierarchical
91 * configuration. Configuration declarations that occur as direct children of
92 * the root element are treated as override declarations.
93 * </p>
94 * <p>
95 * Each configuration declaration consists of a tag whose name is associated
96 * with a <code>ConfigurationProvider</code>. This can be one of the
97 * pre-defined tags like <code>properties</code>, or <code>xml</code>, or
98 * a custom tag, for which a configuration provider was registered. Attributes
99 * and sub elements with specific initialization parameters can be added. There
100 * are some reserved attributes with a special meaning that can be used in every
101 * configuration declaration:
102 * </p>
103 * <p>
104 * <table border="1">
105 * <tr>
106 * <th>Attribute</th>
107 * <th>Meaning</th>
108 * </tr>
109 * <tr>
110 * <td valign="top"><code>config-name</code></td>
111 * <td>Allows to specify a name for this configuration. This name can be used
112 * to obtain a reference to the configuration from the resulting combined
113 * configuration (see below).</td>
114 * </tr>
115 * <tr>
116 * <td valign="top"><code>config-at</code></td>
117 * <td>With this attribute an optional prefix can be specified for the
118 * properties of the corresponding configuration.</td>
119 * </tr>
120 * <tr>
121 * <td valign="top"><code>config-optional</code></td>
122 * <td>Declares a configuration as optional. This means that errors that occur
123 * when creating the configuration are silently ignored.</td>
124 * </tr>
125 * </table>
126 * </p>
127 * <p>
128 * The optional <em>header</em> section can contain some meta data about the
129 * created configuration itself. For instance, it is possible to set further
130 * properties of the <code>NodeCombiner</code> objects used for constructing
131 * the resulting configuration.
132 * </p>
133 * <p>
134 * The configuration object returned by this builder is an instance of the
135 * <code>{@link CombinedConfiguration}</code> class. The return value of the
136 * <code>getConfiguration()</code> method can be casted to this type, and the
137 * <code>getConfiguration(boolean)</code> method directly declares
138 * <code>CombinedConfiguration</code> as return type. This allows for
139 * convenient access to the configuration objects maintained by the combined
140 * configuration (e.g. for updates of single configuration objects). It has also
141 * the advantage that the properties stored in all declared configuration
142 * objects are collected and transformed into a single hierarchical structure,
143 * which can be accessed using different expression engines.
144 * </p>
145 * <p>
146 * All declared override configurations are directly added to the resulting
147 * combined configuration. If they are given names (using the
148 * <code>config-name</code> attribute), they can directly be accessed using
149 * the <code>getConfiguration(String)</code> method of
150 * <code>CombinedConfiguration</code>. The additional configurations are
151 * alltogether added to another combined configuration, which uses a union
152 * combiner. Then this union configuration is added to the resulting combined
153 * configuration under the name defined by the <code>ADDITIONAL_NAME</code>
154 * constant.
155 * </p>
156 * <p>
157 * Implementation note: This class is not thread-safe. Especially the
158 * <code>getConfiguration()</code> methods should be called by a single thread
159 * only.
160 * </p>
161 *
162 * @since 1.3
163 * @author <a
164 * href="http://jakarta.apache.org/commons/configuration/team-list.html">Commons
165 * Configuration team</a>
166 * @version $Id: DefaultConfigurationBuilder.java 507219 2007-02-13 21:02:09Z oheger $
167 */
168 public class DefaultConfigurationBuilder extends XMLConfiguration implements
169 ConfigurationBuilder
170 {
171 /***
172 * Constant for the name of the additional configuration. If the
173 * configuration definition file contains an <code>additional</code>
174 * section, a special union configuration is created and added under this
175 * name to the resulting combined configuration.
176 */
177 public static final String ADDITIONAL_NAME = DefaultConfigurationBuilder.class
178 .getName()
179 + "/ADDITIONAL_CONFIG";
180
181 /*** Constant for the name of the configuration bean factory. */
182 static final String CONFIG_BEAN_FACTORY_NAME = DefaultConfigurationBuilder.class
183 .getName()
184 + ".CONFIG_BEAN_FACTORY_NAME";
185
186 /*** Constant for the reserved name attribute. */
187 static final String ATTR_NAME = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
188 + XMLBeanDeclaration.RESERVED_PREFIX
189 + "name"
190 + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
191
192 /*** Constant for the name of the at attribute. */
193 static final String ATTR_ATNAME = "at";
194
195 /*** Constant for the reserved at attribute. */
196 static final String ATTR_AT_RES = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
197 + XMLBeanDeclaration.RESERVED_PREFIX
198 + ATTR_ATNAME
199 + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
200
201 /*** Constant for the at attribute without the reserved prefix. */
202 static final String ATTR_AT = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
203 + ATTR_ATNAME + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
204
205 /*** Constant for the name of the optional attribute. */
206 static final String ATTR_OPTIONALNAME = "optional";
207
208 /*** Constant for the reserved optional attribute. */
209 static final String ATTR_OPTIONAL_RES = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
210 + XMLBeanDeclaration.RESERVED_PREFIX
211 + ATTR_OPTIONALNAME
212 + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
213
214 /*** Constant for the optional attribute without the reserved prefix. */
215 static final String ATTR_OPTIONAL = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
216 + ATTR_OPTIONALNAME + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
217
218 /*** Constant for the file name attribute. */
219 static final String ATTR_FILENAME = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
220 + "fileName" + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
221
222 /*** Constant for the forceCreate attribute. */
223 static final String ATTR_FORCECREATE = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
224 + XMLBeanDeclaration.RESERVED_PREFIX
225 + "forceCreate"
226 + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
227
228 /*** Constant for the name of the header section. */
229 static final String SEC_HEADER = "header";
230
231 /*** Constant for an expression that selects the union configurations. */
232 static final String KEY_UNION = "additional";
233
234 /*** An array with the names of top level configuration sections.*/
235 static final String[] CONFIG_SECTIONS = {
236 "additional", "override", SEC_HEADER
237 };
238
239 /***
240 * Constant for an expression that selects override configurations in the
241 * override section.
242 */
243 static final String KEY_OVERRIDE = "override";
244
245 /***
246 * Constant for the key that points to the list nodes definition of the
247 * override combiner.
248 */
249 static final String KEY_OVERRIDE_LIST = SEC_HEADER
250 + ".combiner.override.list-nodes.node";
251
252 /***
253 * Constant for the key that points to the list nodes definition of the
254 * additional combiner.
255 */
256 static final String KEY_ADDITIONAL_LIST = SEC_HEADER
257 + ".combiner.additional.list-nodes.node";
258
259 /***
260 * Constant for the key of the result declaration. This key can point to a
261 * bean declaration, which defines properties of the resulting combined
262 * configuration.
263 */
264 static final String KEY_RESULT = SEC_HEADER + ".result";
265
266 /*** Constant for the key of the combiner in the result declaration.*/
267 static final String KEY_COMBINER = KEY_RESULT + ".nodeCombiner";
268
269 /*** Constant for the XML file extension. */
270 static final String EXT_XML = ".xml";
271
272 /*** Constant for the provider for properties files. */
273 private static final ConfigurationProvider PROPERTIES_PROVIDER = new FileExtensionConfigurationProvider(
274 XMLPropertiesConfiguration.class, PropertiesConfiguration.class,
275 EXT_XML);
276
277 /*** Constant for the provider for XML files. */
278 private static final ConfigurationProvider XML_PROVIDER = new FileConfigurationProvider(
279 XMLConfiguration.class);
280
281 /*** Constant for the provider for JNDI sources. */
282 private static final ConfigurationProvider JNDI_PROVIDER = new ConfigurationProvider(
283 JNDIConfiguration.class);
284
285 /*** Constant for the provider for system properties. */
286 private static final ConfigurationProvider SYSTEM_PROVIDER = new ConfigurationProvider(
287 SystemConfiguration.class);
288
289 /*** Constant for the provider for plist files. */
290 private static final ConfigurationProvider PLIST_PROVIDER = new FileExtensionConfigurationProvider(
291 "org.apache.commons.configuration.plist.XMLPropertyListConfiguration",
292 "org.apache.commons.configuration.plist.PropertyListConfiguration",
293 EXT_XML);
294
295 /*** Constant for the provider for configuration definition files.*/
296 private static final ConfigurationProvider BUILDER_PROVIDER = new ConfigurationBuilderProvider();
297
298 /*** An array with the names of the default tags. */
299 private static final String[] DEFAULT_TAGS =
300 {"properties", "xml", "hierarchicalXml", "jndi", "system", "plist", "configuration"};
301
302 /*** An array with the providers for the default tags. */
303 private static final ConfigurationProvider[] DEFAULT_PROVIDERS =
304 {PROPERTIES_PROVIDER, XML_PROVIDER, XML_PROVIDER, JNDI_PROVIDER,
305 SYSTEM_PROVIDER, PLIST_PROVIDER, BUILDER_PROVIDER};
306
307 /***
308 * The serial version UID.
309 */
310 private static final long serialVersionUID = -3113777854714492123L;
311
312 /*** Stores the configuration that is currently constructed.*/
313 private CombinedConfiguration constructedConfiguration;
314
315 /*** Stores a map with the registered configuration providers. */
316 private Map providers;
317
318 /*** Stores the base path to the configuration sources to load. */
319 private String configurationBasePath;
320
321 /***
322 * Creates a new instance of <code>DefaultConfigurationBuilder</code>. A
323 * configuration definition file is not yet loaded. Use the diverse setter
324 * methods provided by file based configurations to specify the
325 * configuration definition file.
326 */
327 public DefaultConfigurationBuilder()
328 {
329 super();
330 providers = new HashMap();
331 registerDefaultProviders();
332 }
333
334 /***
335 * Creates a new instance of <code>DefaultConfigurationBuilder</code> and
336 * sets the specified configuration definition file.
337 *
338 * @param file the configuration definition file
339 */
340 public DefaultConfigurationBuilder(File file)
341 {
342 this();
343 setFile(file);
344 }
345
346 /***
347 * Creates a new instance of <code>DefaultConfigurationBuilder</code> and
348 * sets the specified configuration definition file.
349 *
350 * @param fileName the name of the configuration definition file
351 * @throws ConfigurationException if an error occurs when the file is loaded
352 */
353 public DefaultConfigurationBuilder(String fileName)
354 throws ConfigurationException
355 {
356 this();
357 setFileName(fileName);
358 }
359
360 /***
361 * Creates a new instance of <code>DefaultConfigurationBuilder</code> and
362 * sets the specified configuration definition file.
363 *
364 * @param url the URL to the configuration definition file
365 * @throws ConfigurationException if an error occurs when the file is loaded
366 */
367 public DefaultConfigurationBuilder(URL url) throws ConfigurationException
368 {
369 this();
370 setURL(url);
371 }
372
373 /***
374 * Returns the base path for the configuration sources to load. This path is
375 * used to resolve relative paths in the configuration definition file.
376 *
377 * @return the base path for configuration sources
378 */
379 public String getConfigurationBasePath()
380 {
381 return (configurationBasePath != null) ? configurationBasePath
382 : getBasePath();
383 }
384
385 /***
386 * Sets the base path for the configuration sources to load. Normally a base
387 * path need not to be set because it is determined by the location of the
388 * configuration definition file to load. All relative pathes in this file
389 * are resolved relative to this file. Setting a base path makes sense if
390 * such relative pathes should be otherwise resolved, e.g. if the
391 * configuration file is loaded from the class path and all sub
392 * configurations it refers to are stored in a special config directory.
393 *
394 * @param configurationBasePath the new base path to set
395 */
396 public void setConfigurationBasePath(String configurationBasePath)
397 {
398 this.configurationBasePath = configurationBasePath;
399 }
400
401 /***
402 * Adds a configuration provider for the specified tag. Whenever this tag is
403 * encountered in the configuration definition file this provider will be
404 * called to create the configuration object.
405 *
406 * @param tagName the name of the tag in the configuration definition file
407 * @param provider the provider for this tag
408 */
409 public void addConfigurationProvider(String tagName,
410 ConfigurationProvider provider)
411 {
412 if (tagName == null)
413 {
414 throw new IllegalArgumentException("Tag name must not be null!");
415 }
416 if (provider == null)
417 {
418 throw new IllegalArgumentException("Provider must not be null!");
419 }
420
421 providers.put(tagName, provider);
422 }
423
424 /***
425 * Removes the configuration provider for the specified tag name.
426 *
427 * @param tagName the tag name
428 * @return the removed configuration provider or <b>null</b> if none was
429 * registered for that tag
430 */
431 public ConfigurationProvider removeConfigurationProvider(String tagName)
432 {
433 return (ConfigurationProvider) providers.remove(tagName);
434 }
435
436 /***
437 * Returns the configuration provider for the given tag.
438 *
439 * @param tagName the name of the tag
440 * @return the provider that was registered for this tag or <b>null</b> if
441 * there is none
442 */
443 public ConfigurationProvider providerForTag(String tagName)
444 {
445 return (ConfigurationProvider) providers.get(tagName);
446 }
447
448 /***
449 * Returns the configuration provided by this builder. Loads and parses the
450 * configuration definition file and creates instances for the declared
451 * configurations.
452 *
453 * @return the configuration
454 * @throws ConfigurationException if an error occurs
455 */
456 public Configuration getConfiguration() throws ConfigurationException
457 {
458 return getConfiguration(true);
459 }
460
461 /***
462 * Returns the configuration provided by this builder. If the boolean
463 * parameter is <b>true</b>, the configuration definition file will be
464 * loaded. It will then be parsed, and instances for the declared
465 * configurations will be created.
466 *
467 * @param load a flag whether the configuration definition file should be
468 * loaded; a value of <b>false</b> would make sense if the file has already
469 * been created or its content was manipulated using some of the property
470 * accessor methods
471 * @return the configuration
472 * @throws ConfigurationException if an error occurs
473 */
474 public CombinedConfiguration getConfiguration(boolean load)
475 throws ConfigurationException
476 {
477 if (load)
478 {
479 load();
480 }
481
482 CombinedConfiguration result = createResultConfiguration();
483 constructedConfiguration = result;
484
485 List overrides = fetchTopLevelOverrideConfigs();
486 overrides.addAll(fetchChildConfigs(KEY_OVERRIDE));
487 initCombinedConfiguration(result, overrides, KEY_OVERRIDE_LIST);
488
489 List additionals = fetchChildConfigs(KEY_UNION);
490 if (!additionals.isEmpty())
491 {
492 CombinedConfiguration addConfig = new CombinedConfiguration(
493 new UnionCombiner());
494 result.addConfiguration(addConfig, ADDITIONAL_NAME);
495 initCombinedConfiguration(addConfig, additionals,
496 KEY_ADDITIONAL_LIST);
497 }
498
499 return result;
500 }
501
502 /***
503 * Creates the resulting combined configuration. This method is called by
504 * <code>getConfiguration()</code>. It checks whether the
505 * <code>header</code> section of the configuration definition file
506 * contains a <code>result</code> element. If this is the case, it will be
507 * used to initialize the properties of the newly created configuration
508 * object.
509 *
510 * @return the resulting configuration object
511 * @throws ConfigurationException if an error occurs
512 */
513 protected CombinedConfiguration createResultConfiguration()
514 throws ConfigurationException
515 {
516 XMLBeanDeclaration decl = new XMLBeanDeclaration(this, KEY_RESULT, true);
517 CombinedConfiguration result = (CombinedConfiguration) BeanHelper
518 .createBean(decl, CombinedConfiguration.class);
519
520 if (getMaxIndex(KEY_COMBINER) < 0)
521 {
522
523 result.setNodeCombiner(new OverrideCombiner());
524 }
525
526 return result;
527 }
528
529 /***
530 * Initializes a combined configuration for the configurations of a specific
531 * section. This method is called for the override and for the additional
532 * section (if it exists).
533 *
534 * @param config the configuration to be initialized
535 * @param containedConfigs the list with the declaratinos of the contained
536 * configurations
537 * @param keyListNodes a list with the declaration of list nodes
538 * @throws ConfigurationException if an error occurs
539 */
540 protected void initCombinedConfiguration(CombinedConfiguration config,
541 List containedConfigs, String keyListNodes) throws ConfigurationException
542 {
543 List listNodes = getList(keyListNodes);
544 for (Iterator it = listNodes.iterator(); it.hasNext();)
545 {
546 config.getNodeCombiner().addListNode((String) it.next());
547 }
548
549 for (Iterator it = containedConfigs.iterator(); it.hasNext();)
550 {
551 HierarchicalConfiguration conf = (HierarchicalConfiguration) it
552 .next();
553 ConfigurationDeclaration decl = new ConfigurationDeclaration(this,
554 conf);
555 AbstractConfiguration newConf = createConfigurationAt(decl);
556 if (newConf != null)
557 {
558 config.addConfiguration(newConf, decl.getConfiguration()
559 .getString(ATTR_NAME), decl.getAt());
560 }
561 }
562 }
563
564 /***
565 * Registers the default configuration providers supported by this class.
566 * This method will be called during initialization. It registers
567 * configuration providers for the tags that are supported by default.
568 */
569 protected void registerDefaultProviders()
570 {
571 for (int i = 0; i < DEFAULT_TAGS.length; i++)
572 {
573 addConfigurationProvider(DEFAULT_TAGS[i], DEFAULT_PROVIDERS[i]);
574 }
575 }
576
577 /***
578 * Performs interpolation. This method will not only take this configuration
579 * instance into account (which is the one that loaded the configuration
580 * definition file), but also the so far constructed combined configuration.
581 * So variables can be used that point to properties that are defined in
582 * configuration sources loaded by this builder.
583 *
584 * @param value the value to be interpolated
585 * @return the interpolated value
586 */
587 protected Object interpolate(Object value)
588 {
589 Object result = super.interpolate(value);
590 if (constructedConfiguration != null)
591 {
592 result = constructedConfiguration.interpolate(result);
593 }
594 return result;
595 }
596
597 /***
598 * Creates a configuration object from the specified configuration
599 * declaration.
600 *
601 * @param decl the configuration declaration
602 * @return the new configuration object
603 * @throws ConfigurationException if an error occurs
604 */
605 private AbstractConfiguration createConfigurationAt(
606 ConfigurationDeclaration decl) throws ConfigurationException
607 {
608 try
609 {
610 return (AbstractConfiguration) BeanHelper.createBean(decl);
611 }
612 catch (Exception ex)
613 {
614
615 throw new ConfigurationException(ex);
616 }
617 }
618
619 /***
620 * Returns a list with <code>SubnodeConfiguration</code> objects for the
621 * child nodes of the specified configuration node.
622 *
623 * @param node the start node
624 * @return a list with subnode configurations for the node's children
625 */
626 private List fetchChildConfigs(ConfigurationNode node)
627 {
628 List children = node.getChildren();
629 List result = new ArrayList(children.size());
630 for (Iterator it = children.iterator(); it.hasNext();)
631 {
632 result.add(createSubnodeConfiguration((Node) it.next()));
633 }
634 return result;
635 }
636
637 /***
638 * Returns a list with <code>SubnodeConfiguration</code> objects for the
639 * child nodes of the node specified by the given key.
640 *
641 * @param key the key (must define exactly one node)
642 * @return a list with subnode configurations for the node's children
643 */
644 private List fetchChildConfigs(String key)
645 {
646 List nodes = fetchNodeList(key);
647 if (nodes.size() > 0)
648 {
649 return fetchChildConfigs((ConfigurationNode) nodes.get(0));
650 }
651 else
652 {
653 return Collections.EMPTY_LIST;
654 }
655 }
656
657 /***
658 * Finds the override configurations that are defined as top level elements
659 * in the configuration definition file. This method will fetch the child
660 * elements of the root node and remove the nodes that represent other
661 * configuration sections. The remaining nodes are treated as definitions
662 * for override configurations.
663 *
664 * @return a list with subnode configurations for the top level override
665 * configurations
666 */
667 private List fetchTopLevelOverrideConfigs()
668 {
669 List configs = fetchChildConfigs(getRootNode());
670 for (Iterator it = configs.iterator(); it.hasNext();)
671 {
672 String nodeName = ((SubnodeConfiguration) it.next()).getRootNode()
673 .getName();
674 for (int i = 0; i < CONFIG_SECTIONS.length; i++)
675 {
676 if (CONFIG_SECTIONS[i].equals(nodeName))
677 {
678 it.remove();
679 break;
680 }
681 }
682 }
683 return configs;
684 }
685
686 /***
687 * <p>
688 * A base class for creating and initializing configuration sources.
689 * </p>
690 * <p>
691 * Concrete sub classes of this base class are responsible for creating
692 * specific <code>Configuration</code> objects for the tags in the
693 * configuration definition file. The configuration factory will parse the
694 * definition file and try to find a matching
695 * <code>ConfigurationProvider</code> for each encountered tag. This
696 * provider is then asked to create a corresponding
697 * <code>Configuration</code> object. It is up to a concrete
698 * implementation how this object is created and initialized.
699 * </p>
700 * <p>
701 * Note that at the moment only configuration classes derived from
702 * <code>{@link AbstractConfiguration}</code> are supported.
703 * </p>
704 */
705 public static class ConfigurationProvider extends DefaultBeanFactory
706 {
707 /*** Stores the class of the configuration to be created. */
708 private Class configurationClass;
709
710 /*** Stores the name of the configuration class to be created.*/
711 private String configurationClassName;
712
713 /***
714 * Creates a new uninitialized instance of
715 * <code>ConfigurationProvider</code>.
716 */
717 public ConfigurationProvider()
718 {
719 this((Class) null);
720 }
721
722 /***
723 * Creates a new instance of <code>ConfigurationProvider</code> and
724 * sets the class of the configuration created by this provider.
725 *
726 * @param configClass the configuration class
727 */
728 public ConfigurationProvider(Class configClass)
729 {
730 setConfigurationClass(configClass);
731 }
732
733 /***
734 * Creates a new instance of <code>ConfigurationProvider</code> and
735 * sets the name of the class of the configuration created by this
736 * provider.
737 *
738 * @param configClassName the name of the configuration class
739 * @since 1.4
740 */
741 public ConfigurationProvider(String configClassName)
742 {
743 setConfigurationClassName(configClassName);
744 }
745
746 /***
747 * Returns the class of the configuration returned by this provider.
748 *
749 * @return the class of the provided configuration
750 */
751 public Class getConfigurationClass()
752 {
753 return configurationClass;
754 }
755
756 /***
757 * Sets the class of the configuration returned by this provider.
758 *
759 * @param configurationClass the configuration class
760 */
761 public void setConfigurationClass(Class configurationClass)
762 {
763 this.configurationClass = configurationClass;
764 }
765
766 /***
767 * Returns the name of the configuration class returned by this
768 * provider.
769 *
770 * @return the configuration class name
771 * @since 1.4
772 */
773 public String getConfigurationClassName()
774 {
775 return configurationClassName;
776 }
777
778 /***
779 * Sets the name of the configuration class returned by this provider.
780 *
781 * @param configurationClassName the name of the configuration class
782 * @since 1.4
783 */
784 public void setConfigurationClassName(String configurationClassName)
785 {
786 this.configurationClassName = configurationClassName;
787 }
788
789 /***
790 * Returns the configuration. This method is called to fetch the
791 * configuration from the provider. This implementation will call the
792 * inherited <code>{@link
793 * org.apache.commons.configuration.beanutils.DefaultBeanFactory#createBean(Class, BeanDeclaration, Object)
794 * createBean()}</code> method to create a new instance of the
795 * configuration class.
796 *
797 * @param decl the bean declaration with initialization parameters for
798 * the configuration
799 * @return the new configuration object
800 * @throws Exception if an error occurs
801 */
802 public AbstractConfiguration getConfiguration(
803 ConfigurationDeclaration decl) throws Exception
804 {
805 return (AbstractConfiguration) createBean(fetchConfigurationClass(),
806 decl, null);
807 }
808
809 /***
810 * Returns an uninitialized configuration of the represented type. This
811 * method will be called for optional configurations when the
812 * <code>getConfiguration()</code> method caused an error and the
813 * <code>forceCreate</code> attribute is set. A concrete sub class can
814 * here try to create an uninitialized, empty configuration, which may
815 * be possible if the error was created during initialization. This base
816 * implementation just returns <b>null</b>.
817 *
818 * @param decl the bean declaration with initialization parameters for
819 * the configuration
820 * @return the new configuration object
821 * @throws Exception if an error occurs
822 * @since 1.4
823 */
824 public AbstractConfiguration getEmptyConfiguration(
825 ConfigurationDeclaration decl) throws Exception
826 {
827 return null;
828 }
829
830 /***
831 * Returns the configuration class supported by this provider. If a
832 * class object was set, it is returned. Otherwise the method tries to
833 * resolve the class name.
834 *
835 * @return the class of the configuration to be created
836 * @since 1.4
837 */
838 protected synchronized Class fetchConfigurationClass() throws Exception
839 {
840 if (getConfigurationClass() == null)
841 {
842 setConfigurationClass(loadClass(getConfigurationClassName()));
843 }
844 return getConfigurationClass();
845 }
846
847 /***
848 * Loads the class with the specified name dynamically. If the class's
849 * name is <b>null</b>, <b>null</b> will also be returned.
850 *
851 * @param className the name of the class to be loaded
852 * @return the class object
853 * @throws ClassNotFoundException if class loading fails
854 * @since 1.4
855 */
856 protected Class loadClass(String className)
857 throws ClassNotFoundException
858 {
859 return (className != null) ? Class.forName(className, true,
860 getClass().getClassLoader()) : null;
861 }
862 }
863
864 /***
865 * <p>
866 * A specialized <code>BeanDeclaration</code> implementation that
867 * represents the declaration of a configuration source.
868 * </p>
869 * <p>
870 * Instances of this class are able to extract all information about a
871 * configuration source from the configuration definition file. The
872 * declaration of a configuration source is very similar to a bean
873 * declaration processed by <code>XMLBeanDeclaration</code>. There are
874 * very few differences, e.g. some reserved attributes like
875 * <code>optional</code> and <code>at</code> and the fact that a bean
876 * factory is never needed.
877 * </p>
878 */
879 public static class ConfigurationDeclaration extends XMLBeanDeclaration
880 {
881 /*** Stores a reference to the associated configuration builder. */
882 private DefaultConfigurationBuilder configurationBuilder;
883
884 /***
885 * Creates a new instance of <code>ConfigurationDeclaration</code> and
886 * initializes it.
887 *
888 * @param builder the associated configuration builder
889 * @param config the configuration this declaration is based onto
890 */
891 public ConfigurationDeclaration(DefaultConfigurationBuilder builder,
892 HierarchicalConfiguration config)
893 {
894 super(config);
895 configurationBuilder = builder;
896 }
897
898 /***
899 * Returns the associated configuration builder.
900 *
901 * @return the configuration builder
902 */
903 public DefaultConfigurationBuilder getConfigurationBuilder()
904 {
905 return configurationBuilder;
906 }
907
908 /***
909 * Returns the value of the <code>at</code> attribute.
910 *
911 * @return the value of the <code>at</code> attribute (can be <b>null</b>)
912 */
913 public String getAt()
914 {
915 String result = this.getConfiguration().getString(ATTR_AT_RES);
916 return (result == null) ? this.getConfiguration().getString(ATTR_AT)
917 : result;
918 }
919
920 /***
921 * Returns a flag whether this is an optional configuration.
922 *
923 * @return a flag if this declaration points to an optional
924 * configuration
925 */
926 public boolean isOptional()
927 {
928 Boolean value = this.getConfiguration().getBoolean(ATTR_OPTIONAL_RES,
929 null);
930 if (value == null)
931 {
932 value = this.getConfiguration().getBoolean(ATTR_OPTIONAL,
933 Boolean.FALSE);
934 }
935 return value.booleanValue();
936 }
937
938 /***
939 * Returns a flag whether this configuration should always be created
940 * and added to the resulting combined configuration. This flag is
941 * evaluated only for optional configurations whose normal creation has
942 * caused an error. If for such a configuration the
943 * <code>forceCreate</code> attribute is set and the corresponding
944 * configuration provider supports this mode, an empty configuration
945 * will be created and added to the resulting combined configuration.
946 *
947 * @return the value of the <code>forceCreate</code> attribute
948 * @since 1.4
949 */
950 public boolean isForceCreate()
951 {
952 return this.getConfiguration().getBoolean(ATTR_FORCECREATE, false);
953 }
954
955 /***
956 * Returns the name of the bean factory. For configuration source
957 * declarations always a reserved factory is used. This factory's name
958 * is returned by this implementation.
959 *
960 * @return the name of the bean factory
961 */
962 public String getBeanFactoryName()
963 {
964 return CONFIG_BEAN_FACTORY_NAME;
965 }
966
967 /***
968 * Returns the bean's class name. This implementation will always return
969 * <b>null</b>.
970 *
971 * @return the name of the bean's class
972 */
973 public String getBeanClassName()
974 {
975 return null;
976 }
977
978 /***
979 * Checks whether the given node is reserved. This method will take
980 * further reserved attributes into account
981 *
982 * @param nd the node
983 * @return a flag whether this node is reserved
984 */
985 protected boolean isReservedNode(ConfigurationNode nd)
986 {
987 if (super.isReservedNode(nd))
988 {
989 return true;
990 }
991
992 return nd.isAttribute()
993 && ((ATTR_ATNAME.equals(nd.getName()) && nd.getParentNode()
994 .getAttributeCount(RESERVED_PREFIX + ATTR_ATNAME) == 0) || (ATTR_OPTIONALNAME
995 .equals(nd.getName()) && nd.getParentNode()
996 .getAttributeCount(RESERVED_PREFIX + ATTR_OPTIONALNAME) == 0));
997 }
998
999 /***
1000 * Performs interpolation. This implementation will delegate
1001 * interpolation to the configuration builder, which takes care that the
1002 * currently constructed configuration is taken into account, too.
1003 *
1004 * @param value the value to be interpolated
1005 * @return the interpolated value
1006 */
1007 protected Object interpolate(Object value)
1008 {
1009 return getConfigurationBuilder().interpolate(value);
1010 }
1011 }
1012
1013 /***
1014 * A specialized <code>BeanFactory</code> implementation that handles
1015 * configuration declarations. This class will retrieve the correct
1016 * configuration provider and delegate the task of creating the
1017 * configuration to this object.
1018 */
1019 static class ConfigurationBeanFactory implements BeanFactory
1020 {
1021 /***
1022 * Creates an instance of a bean class. This implementation expects that
1023 * the passed in bean declaration is a declaration for a configuration.
1024 * It will determine the responsible configuration provider and delegate
1025 * the call to this instance. If creation of the configuration fails
1026 * and the <code>optional</code> attribute is set, the exception will
1027 * be ignored. If the <code>forceCreate</code> attribute is set, too,
1028 * the provider is asked to create an empty configuration. A return
1029 * value of <b>null</b> means that no configuration could be created.
1030 *
1031 * @param beanClass the bean class (will be ignored)
1032 * @param data the declaration
1033 * @param param an additional parameter (will be ignored)
1034 * @return the newly created configuration
1035 * @throws Exception if an error occurs
1036 */
1037 public Object createBean(Class beanClass, BeanDeclaration data,
1038 Object param) throws Exception
1039 {
1040 ConfigurationDeclaration decl = (ConfigurationDeclaration) data;
1041 String tagName = decl.getNode().getName();
1042 ConfigurationProvider provider = decl.getConfigurationBuilder()
1043 .providerForTag(tagName);
1044 if (provider == null)
1045 {
1046 throw new ConfigurationRuntimeException(
1047 "No ConfigurationProvider registered for tag "
1048 + tagName);
1049 }
1050
1051 try
1052 {
1053 return provider.getConfiguration(decl);
1054 }
1055 catch (Exception ex)
1056 {
1057
1058 if (!decl.isOptional())
1059 {
1060 throw ex;
1061 }
1062 else
1063 {
1064 if (decl.isForceCreate())
1065 {
1066 try
1067 {
1068 return provider.getEmptyConfiguration(decl);
1069 }
1070 catch (Exception ex2)
1071 {
1072
1073 ;
1074 }
1075 }
1076 return null;
1077 }
1078 }
1079 }
1080
1081 /***
1082 * Returns the default class for this bean factory.
1083 *
1084 * @return the default class
1085 */
1086 public Class getDefaultBeanClass()
1087 {
1088
1089
1090 return Configuration.class;
1091 }
1092 }
1093
1094 /***
1095 * A specialized provider implementation that deals with file based
1096 * configurations. Ensures that the base path is correctly set and that the
1097 * load() method gets called.
1098 */
1099 public static class FileConfigurationProvider extends ConfigurationProvider
1100 {
1101 /***
1102 * Creates a new instance of <code>FileConfigurationProvider</code>.
1103 */
1104 public FileConfigurationProvider()
1105 {
1106 super();
1107 }
1108
1109 /***
1110 * Creates a new instance of <code>FileConfigurationProvider</code>
1111 * and sets the configuration class.
1112 *
1113 * @param configClass the class for the configurations to be created
1114 */
1115 public FileConfigurationProvider(Class configClass)
1116 {
1117 super(configClass);
1118 }
1119
1120 /***
1121 * Creates a new instance of <code>FileConfigurationProvider</code>
1122 * and sets the configuration class name.
1123 *
1124 * @param configClassName the name of the configuration to be created
1125 * @since 1.4
1126 */
1127 public FileConfigurationProvider(String configClassName)
1128 {
1129 super(configClassName);
1130 }
1131
1132 /***
1133 * Creates the configuration. After that <code>load()</code> will be
1134 * called. If this configuration is marked as optional, exceptions will
1135 * be ignored.
1136 *
1137 * @param decl the declaration
1138 * @return the new configuration
1139 * @throws Exception if an error occurs
1140 */
1141 public AbstractConfiguration getConfiguration(
1142 ConfigurationDeclaration decl) throws Exception
1143 {
1144 AbstractConfiguration result = getEmptyConfiguration(decl);
1145 ((FileConfiguration) result).load();
1146 return result;
1147 }
1148
1149 /***
1150 * Returns an uninitialized file configuration. This method will be
1151 * called for optional configurations when the
1152 * <code>getConfiguration()</code> method caused an error and the
1153 * <code>forceCreate</code> attribute is set. It will create the
1154 * configuration of the represented type, but the <code>load()</code>
1155 * method won't be called. This way non-existing configuration files can
1156 * be handled gracefully: If loading a the file fails, an empty
1157 * configuration will be created that is already configured with the
1158 * correct file name.
1159 *
1160 * @param decl the bean declaration with initialization parameters for
1161 * the configuration
1162 * @return the new configuration object
1163 * @throws Exception if an error occurs
1164 * @since 1.4
1165 */
1166 public AbstractConfiguration getEmptyConfiguration(
1167 ConfigurationDeclaration decl) throws Exception
1168 {
1169 return super.getConfiguration(decl);
1170 }
1171
1172 /***
1173 * Initializes the bean instance. Ensures that the file configuration's
1174 * base path will be initialized with the base path of the factory so
1175 * that relative path names can be correctly resolved.
1176 *
1177 * @param bean the bean to be initialized
1178 * @param data the declaration
1179 * @throws Exception if an error occurs
1180 */
1181 protected void initBeanInstance(Object bean, BeanDeclaration data)
1182 throws Exception
1183 {
1184 FileConfiguration config = (FileConfiguration) bean;
1185 config.setBasePath(((ConfigurationDeclaration) data)
1186 .getConfigurationBuilder().getConfigurationBasePath());
1187 super.initBeanInstance(bean, data);
1188 }
1189 }
1190
1191 /***
1192 * A specialized configuration provider for file based configurations that
1193 * can handle configuration sources whose concrete type depends on the
1194 * extension of the file to be loaded. One example is the
1195 * <code>properties</code> tag: if the file ends with ".xml" a
1196 * XMLPropertiesConfiguration object must be created, otherwise a
1197 * PropertiesConfiguration object.
1198 */
1199 static class FileExtensionConfigurationProvider extends
1200 FileConfigurationProvider
1201 {
1202 /***
1203 * Stores the class to be created when the file extension matches.
1204 */
1205 private Class matchingClass;
1206
1207 /***
1208 * Stores the name of the class to be created when the file extension
1209 * matches.
1210 */
1211 private String matchingClassName;
1212
1213 /***
1214 * Stores the class to be created when the file extension does not
1215 * match.
1216 */
1217 private Class defaultClass;
1218
1219 /***
1220 * Stores the name of the class to be created when the file extension
1221 * does not match.
1222 */
1223 private String defaultClassName;
1224
1225 /*** Stores the file extension to be checked against. */
1226 private String fileExtension;
1227
1228 /***
1229 * Creates a new instance of
1230 * <code>FileExtensionConfigurationProvider</code> and initializes it.
1231 *
1232 * @param matchingClass the class to be created when the file extension
1233 * matches
1234 * @param defaultClass the class to be created when the file extension
1235 * does not match
1236 * @param extension the file extension to be checked agains
1237 */
1238 public FileExtensionConfigurationProvider(Class matchingClass,
1239 Class defaultClass, String extension)
1240 {
1241 this.matchingClass = matchingClass;
1242 this.defaultClass = defaultClass;
1243 fileExtension = extension;
1244 }
1245
1246 /***
1247 * Creates a new instance of
1248 * <code>FileExtensionConfigurationProvider</code> and initializes it
1249 * with the names of the classes to be created.
1250 *
1251 * @param matchingClassName the name of the class to be created when the
1252 * file extension matches
1253 * @param defaultClassName the name of the class to be created when the
1254 * file extension does not match
1255 * @param extension the file extension to be checked agains
1256 * @since 1.4
1257 */
1258 public FileExtensionConfigurationProvider(String matchingClassName,
1259 String defaultClassName, String extension)
1260 {
1261 this.matchingClassName = matchingClassName;
1262 this.defaultClassName = defaultClassName;
1263 fileExtension = extension;
1264 }
1265
1266 /***
1267 * Returns the matching class object, no matter whether it was defined
1268 * as a class or as a class name.
1269 *
1270 * @return the matching class object
1271 * @throws Exception if an error occurs
1272 * @since 1.4
1273 */
1274 protected synchronized Class fetchMatchingClass() throws Exception
1275 {
1276 if (matchingClass == null)
1277 {
1278 matchingClass = loadClass(matchingClassName);
1279 }
1280 return matchingClass;
1281 }
1282
1283 /***
1284 * Returns the default class object, no matter whether it was defined as
1285 * a class or as a class name.
1286 *
1287 * @return the default class object
1288 * @throws Exception if an error occurs
1289 * @since 1.4
1290 */
1291 protected synchronized Class fetchDefaultClass() throws Exception
1292 {
1293 if (defaultClass == null)
1294 {
1295 defaultClass = loadClass(defaultClassName);
1296 }
1297 return defaultClass;
1298 }
1299
1300 /***
1301 * Creates the configuration object. The class is determined by the file
1302 * name's extension.
1303 *
1304 * @param beanClass the class
1305 * @param data the bean declaration
1306 * @return the new bean
1307 * @throws Exception if an error occurs
1308 */
1309 protected Object createBeanInstance(Class beanClass,
1310 BeanDeclaration data) throws Exception
1311 {
1312 String fileName = ((ConfigurationDeclaration) data)
1313 .getConfiguration().getString(ATTR_FILENAME);
1314 if (fileName != null
1315 && fileName.toLowerCase().trim().endsWith(fileExtension))
1316 {
1317 return super.createBeanInstance(fetchMatchingClass(), data);
1318 }
1319 else
1320 {
1321 return super.createBeanInstance(fetchDefaultClass(), data);
1322 }
1323 }
1324 }
1325
1326 /***
1327 * A specialized configuration provider class that allows to include other
1328 * configuration definition files.
1329 */
1330 static class ConfigurationBuilderProvider extends ConfigurationProvider
1331 {
1332 /***
1333 * Creates a new instance of <code>ConfigurationBuilderProvider</code>.
1334 */
1335 public ConfigurationBuilderProvider()
1336 {
1337 super(DefaultConfigurationBuilder.class);
1338 }
1339
1340 /***
1341 * Creates the configuration. First creates a configuration builder
1342 * object. Then returns the configuration created by this builder.
1343 *
1344 * @param decl the configuration declaration
1345 * @return the configuration
1346 * @exception Exception if an error occurs
1347 */
1348 public AbstractConfiguration getConfiguration(
1349 ConfigurationDeclaration decl) throws Exception
1350 {
1351 DefaultConfigurationBuilder builder = (DefaultConfigurationBuilder) super
1352 .getConfiguration(decl);
1353 return builder.getConfiguration(true);
1354 }
1355
1356 /***
1357 * Returns an empty configuration in case of an optional configuration
1358 * could not be created. This implementation returns an empty combined
1359 * configuration.
1360 *
1361 * @param decl the configuration declaration
1362 * @return the configuration
1363 * @exception Exception if an error occurs
1364 * @since 1.4
1365 */
1366 public AbstractConfiguration getEmptyConfiguration(
1367 ConfigurationDeclaration decl) throws Exception
1368 {
1369 return new CombinedConfiguration();
1370 }
1371 }
1372
1373 static
1374 {
1375
1376 BeanHelper.registerBeanFactory(CONFIG_BEAN_FACTORY_NAME,
1377 new ConfigurationBeanFactory());
1378 }
1379 }