0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one or more
0003: * contributor license agreements. See the NOTICE file distributed with
0004: * this work for additional information regarding copyright ownership.
0005: * The ASF licenses this file to You under the Apache License, Version 2.0
0006: * (the "License"); you may not use this file except in compliance with
0007: * the License. You may obtain a copy of the License at
0008: *
0009: * http://www.apache.org/licenses/LICENSE-2.0
0010: *
0011: * Unless required by applicable law or agreed to in writing, software
0012: * distributed under the License is distributed on an "AS IS" BASIS,
0013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
0014: * See the License for the specific language governing permissions and
0015: * limitations under the License.
0016: */
0017: package org.apache.commons.configuration;
0018:
0019: import java.io.File;
0020: import java.net.URL;
0021: import java.util.ArrayList;
0022: import java.util.Collections;
0023: import java.util.HashMap;
0024: import java.util.Iterator;
0025: import java.util.List;
0026: import java.util.Map;
0027:
0028: import org.apache.commons.configuration.beanutils.BeanDeclaration;
0029: import org.apache.commons.configuration.beanutils.BeanFactory;
0030: import org.apache.commons.configuration.beanutils.BeanHelper;
0031: import org.apache.commons.configuration.beanutils.DefaultBeanFactory;
0032: import org.apache.commons.configuration.beanutils.XMLBeanDeclaration;
0033: import org.apache.commons.configuration.tree.ConfigurationNode;
0034: import org.apache.commons.configuration.tree.DefaultExpressionEngine;
0035: import org.apache.commons.configuration.tree.OverrideCombiner;
0036: import org.apache.commons.configuration.tree.UnionCombiner;
0037:
0038: /**
0039: * <p>
0040: * A factory class that creates a composite configuration from an XML based
0041: * <em>configuration definition file</em>.
0042: * </p>
0043: * <p>
0044: * This class provides an easy and flexible means for loading multiple
0045: * configuration sources and combining the results into a single configuration
0046: * object. The sources to be loaded are defined in an XML document that can
0047: * contain certain tags representing the different supported configuration
0048: * classes. If such a tag is found, the corresponding <code>Configuration</code>
0049: * class is instantiated and initialized using the classes of the
0050: * <code>beanutils</code> package (namely
0051: * <code>{@link org.apache.commons.configuration.beanutils.XMLBeanDeclaration XMLBeanDeclaration}</code>
0052: * will be used to extract the configuration's initialization parameters, which
0053: * allows for complex initialization szenarios).
0054: * </p>
0055: * <p>
0056: * It is also possible to add custom tags to the configuration definition file.
0057: * For this purpose register your own <code>ConfigurationProvider</code>
0058: * implementation for your tag using the <code>addConfigurationProvider()</code>
0059: * method. This provider will then be called when the corresponding custom tag
0060: * is detected. For the default configuration classes providers are already
0061: * registered.
0062: * </p>
0063: * <p>
0064: * The configuration definition file has the following basic structure:
0065: * </p>
0066: * <p>
0067: *
0068: * <pre>
0069: * <configuration>
0070: * <header>
0071: * <!-- Optional meta information about the composite configuration -->
0072: * </header>
0073: * <override>
0074: * <!-- Declarations for override configurations -->
0075: * </override>
0076: * <additional>
0077: * <!-- Declarations for union configurations -->
0078: * </additional>
0079: * </configuration>
0080: * </pre>
0081: *
0082: * </p>
0083: * <p>
0084: * The name of the root element (here <code>configuration</code>) is
0085: * arbitrary. There are two sections (both of them are optional) for declaring
0086: * <em>override</em> and <em>additional</em> configurations. Configurations
0087: * in the former section are evaluated in the order of their declaration, and
0088: * properties of configurations declared earlier hide those of configurations
0089: * declared later. Configurations in the latter section are combined to a union
0090: * configuration, i.e. all of their properties are added to a large hierarchical
0091: * configuration. Configuration declarations that occur as direct children of
0092: * the root element are treated as override declarations.
0093: * </p>
0094: * <p>
0095: * Each configuration declaration consists of a tag whose name is associated
0096: * with a <code>ConfigurationProvider</code>. This can be one of the
0097: * pre-defined tags like <code>properties</code>, or <code>xml</code>, or
0098: * a custom tag, for which a configuration provider was registered. Attributes
0099: * and sub elements with specific initialization parameters can be added. There
0100: * are some reserved attributes with a special meaning that can be used in every
0101: * configuration declaration:
0102: * </p>
0103: * <p>
0104: * <table border="1">
0105: * <tr>
0106: * <th>Attribute</th>
0107: * <th>Meaning</th>
0108: * </tr>
0109: * <tr>
0110: * <td valign="top"><code>config-name</code></td>
0111: * <td>Allows to specify a name for this configuration. This name can be used
0112: * to obtain a reference to the configuration from the resulting combined
0113: * configuration (see below).</td>
0114: * </tr>
0115: * <tr>
0116: * <td valign="top"><code>config-at</code></td>
0117: * <td>With this attribute an optional prefix can be specified for the
0118: * properties of the corresponding configuration.</td>
0119: * </tr>
0120: * <tr>
0121: * <td valign="top"><code>config-optional</code></td>
0122: * <td>Declares a configuration as optional. This means that errors that occur
0123: * when creating the configuration are silently ignored.</td>
0124: * </tr>
0125: * </table>
0126: * </p>
0127: * <p>
0128: * The optional <em>header</em> section can contain some meta data about the
0129: * created configuration itself. For instance, it is possible to set further
0130: * properties of the <code>NodeCombiner</code> objects used for constructing
0131: * the resulting configuration.
0132: * </p>
0133: * <p>
0134: * The configuration object returned by this builder is an instance of the
0135: * <code>{@link CombinedConfiguration}</code> class. The return value of the
0136: * <code>getConfiguration()</code> method can be casted to this type, and the
0137: * <code>getConfiguration(boolean)</code> method directly declares
0138: * <code>CombinedConfiguration</code> as return type. This allows for
0139: * convenient access to the configuration objects maintained by the combined
0140: * configuration (e.g. for updates of single configuration objects). It has also
0141: * the advantage that the properties stored in all declared configuration
0142: * objects are collected and transformed into a single hierarchical structure,
0143: * which can be accessed using different expression engines.
0144: * </p>
0145: * <p>
0146: * All declared override configurations are directly added to the resulting
0147: * combined configuration. If they are given names (using the
0148: * <code>config-name</code> attribute), they can directly be accessed using
0149: * the <code>getConfiguration(String)</code> method of
0150: * <code>CombinedConfiguration</code>. The additional configurations are
0151: * alltogether added to another combined configuration, which uses a union
0152: * combiner. Then this union configuration is added to the resulting combined
0153: * configuration under the name defined by the <code>ADDITIONAL_NAME</code>
0154: * constant.
0155: * </p>
0156: * <p>
0157: * Implementation note: This class is not thread-safe. Especially the
0158: * <code>getConfiguration()</code> methods should be called by a single thread
0159: * only.
0160: * </p>
0161: *
0162: * @since 1.3
0163: * @author <a
0164: * href="http://jakarta.apache.org/commons/configuration/team-list.html">Commons
0165: * Configuration team</a>
0166: * @version $Id: DefaultConfigurationBuilder.java 507219 2007-02-13 21:02:09Z oheger $
0167: */
0168: public class DefaultConfigurationBuilder extends XMLConfiguration
0169: implements ConfigurationBuilder {
0170: /**
0171: * Constant for the name of the additional configuration. If the
0172: * configuration definition file contains an <code>additional</code>
0173: * section, a special union configuration is created and added under this
0174: * name to the resulting combined configuration.
0175: */
0176: public static final String ADDITIONAL_NAME = DefaultConfigurationBuilder.class
0177: .getName()
0178: + "/ADDITIONAL_CONFIG";
0179:
0180: /** Constant for the name of the configuration bean factory. */
0181: static final String CONFIG_BEAN_FACTORY_NAME = DefaultConfigurationBuilder.class
0182: .getName()
0183: + ".CONFIG_BEAN_FACTORY_NAME";
0184:
0185: /** Constant for the reserved name attribute. */
0186: static final String ATTR_NAME = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
0187: + XMLBeanDeclaration.RESERVED_PREFIX
0188: + "name"
0189: + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
0190:
0191: /** Constant for the name of the at attribute. */
0192: static final String ATTR_ATNAME = "at";
0193:
0194: /** Constant for the reserved at attribute. */
0195: static final String ATTR_AT_RES = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
0196: + XMLBeanDeclaration.RESERVED_PREFIX
0197: + ATTR_ATNAME
0198: + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
0199:
0200: /** Constant for the at attribute without the reserved prefix. */
0201: static final String ATTR_AT = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
0202: + ATTR_ATNAME
0203: + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
0204:
0205: /** Constant for the name of the optional attribute. */
0206: static final String ATTR_OPTIONALNAME = "optional";
0207:
0208: /** Constant for the reserved optional attribute. */
0209: static final String ATTR_OPTIONAL_RES = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
0210: + XMLBeanDeclaration.RESERVED_PREFIX
0211: + ATTR_OPTIONALNAME
0212: + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
0213:
0214: /** Constant for the optional attribute without the reserved prefix. */
0215: static final String ATTR_OPTIONAL = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
0216: + ATTR_OPTIONALNAME
0217: + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
0218:
0219: /** Constant for the file name attribute. */
0220: static final String ATTR_FILENAME = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
0221: + "fileName"
0222: + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
0223:
0224: /** Constant for the forceCreate attribute. */
0225: static final String ATTR_FORCECREATE = DefaultExpressionEngine.DEFAULT_ATTRIBUTE_START
0226: + XMLBeanDeclaration.RESERVED_PREFIX
0227: + "forceCreate"
0228: + DefaultExpressionEngine.DEFAULT_ATTRIBUTE_END;
0229:
0230: /** Constant for the name of the header section. */
0231: static final String SEC_HEADER = "header";
0232:
0233: /** Constant for an expression that selects the union configurations. */
0234: static final String KEY_UNION = "additional";
0235:
0236: /** An array with the names of top level configuration sections.*/
0237: static final String[] CONFIG_SECTIONS = { "additional", "override",
0238: SEC_HEADER };
0239:
0240: /**
0241: * Constant for an expression that selects override configurations in the
0242: * override section.
0243: */
0244: static final String KEY_OVERRIDE = "override";
0245:
0246: /**
0247: * Constant for the key that points to the list nodes definition of the
0248: * override combiner.
0249: */
0250: static final String KEY_OVERRIDE_LIST = SEC_HEADER
0251: + ".combiner.override.list-nodes.node";
0252:
0253: /**
0254: * Constant for the key that points to the list nodes definition of the
0255: * additional combiner.
0256: */
0257: static final String KEY_ADDITIONAL_LIST = SEC_HEADER
0258: + ".combiner.additional.list-nodes.node";
0259:
0260: /**
0261: * Constant for the key of the result declaration. This key can point to a
0262: * bean declaration, which defines properties of the resulting combined
0263: * configuration.
0264: */
0265: static final String KEY_RESULT = SEC_HEADER + ".result";
0266:
0267: /** Constant for the key of the combiner in the result declaration.*/
0268: static final String KEY_COMBINER = KEY_RESULT + ".nodeCombiner";
0269:
0270: /** Constant for the XML file extension. */
0271: static final String EXT_XML = ".xml";
0272:
0273: /** Constant for the provider for properties files. */
0274: private static final ConfigurationProvider PROPERTIES_PROVIDER = new FileExtensionConfigurationProvider(
0275: XMLPropertiesConfiguration.class,
0276: PropertiesConfiguration.class, EXT_XML);
0277:
0278: /** Constant for the provider for XML files. */
0279: private static final ConfigurationProvider XML_PROVIDER = new FileConfigurationProvider(
0280: XMLConfiguration.class);
0281:
0282: /** Constant for the provider for JNDI sources. */
0283: private static final ConfigurationProvider JNDI_PROVIDER = new ConfigurationProvider(
0284: JNDIConfiguration.class);
0285:
0286: /** Constant for the provider for system properties. */
0287: private static final ConfigurationProvider SYSTEM_PROVIDER = new ConfigurationProvider(
0288: SystemConfiguration.class);
0289:
0290: /** Constant for the provider for plist files. */
0291: private static final ConfigurationProvider PLIST_PROVIDER = new FileExtensionConfigurationProvider(
0292: "org.apache.commons.configuration.plist.XMLPropertyListConfiguration",
0293: "org.apache.commons.configuration.plist.PropertyListConfiguration",
0294: EXT_XML);
0295:
0296: /** Constant for the provider for configuration definition files.*/
0297: private static final ConfigurationProvider BUILDER_PROVIDER = new ConfigurationBuilderProvider();
0298:
0299: /** An array with the names of the default tags. */
0300: private static final String[] DEFAULT_TAGS = { "properties", "xml",
0301: "hierarchicalXml", "jndi", "system", "plist",
0302: "configuration" };
0303:
0304: /** An array with the providers for the default tags. */
0305: private static final ConfigurationProvider[] DEFAULT_PROVIDERS = {
0306: PROPERTIES_PROVIDER, XML_PROVIDER, XML_PROVIDER,
0307: JNDI_PROVIDER, SYSTEM_PROVIDER, PLIST_PROVIDER,
0308: BUILDER_PROVIDER };
0309:
0310: /**
0311: * The serial version UID.
0312: */
0313: private static final long serialVersionUID = -3113777854714492123L;
0314:
0315: /** Stores the configuration that is currently constructed.*/
0316: private CombinedConfiguration constructedConfiguration;
0317:
0318: /** Stores a map with the registered configuration providers. */
0319: private Map providers;
0320:
0321: /** Stores the base path to the configuration sources to load. */
0322: private String configurationBasePath;
0323:
0324: /**
0325: * Creates a new instance of <code>DefaultConfigurationBuilder</code>. A
0326: * configuration definition file is not yet loaded. Use the diverse setter
0327: * methods provided by file based configurations to specify the
0328: * configuration definition file.
0329: */
0330: public DefaultConfigurationBuilder() {
0331: super ();
0332: providers = new HashMap();
0333: registerDefaultProviders();
0334: }
0335:
0336: /**
0337: * Creates a new instance of <code>DefaultConfigurationBuilder</code> and
0338: * sets the specified configuration definition file.
0339: *
0340: * @param file the configuration definition file
0341: */
0342: public DefaultConfigurationBuilder(File file) {
0343: this ();
0344: setFile(file);
0345: }
0346:
0347: /**
0348: * Creates a new instance of <code>DefaultConfigurationBuilder</code> and
0349: * sets the specified configuration definition file.
0350: *
0351: * @param fileName the name of the configuration definition file
0352: * @throws ConfigurationException if an error occurs when the file is loaded
0353: */
0354: public DefaultConfigurationBuilder(String fileName)
0355: throws ConfigurationException {
0356: this ();
0357: setFileName(fileName);
0358: }
0359:
0360: /**
0361: * Creates a new instance of <code>DefaultConfigurationBuilder</code> and
0362: * sets the specified configuration definition file.
0363: *
0364: * @param url the URL to the configuration definition file
0365: * @throws ConfigurationException if an error occurs when the file is loaded
0366: */
0367: public DefaultConfigurationBuilder(URL url)
0368: throws ConfigurationException {
0369: this ();
0370: setURL(url);
0371: }
0372:
0373: /**
0374: * Returns the base path for the configuration sources to load. This path is
0375: * used to resolve relative paths in the configuration definition file.
0376: *
0377: * @return the base path for configuration sources
0378: */
0379: public String getConfigurationBasePath() {
0380: return (configurationBasePath != null) ? configurationBasePath
0381: : getBasePath();
0382: }
0383:
0384: /**
0385: * Sets the base path for the configuration sources to load. Normally a base
0386: * path need not to be set because it is determined by the location of the
0387: * configuration definition file to load. All relative pathes in this file
0388: * are resolved relative to this file. Setting a base path makes sense if
0389: * such relative pathes should be otherwise resolved, e.g. if the
0390: * configuration file is loaded from the class path and all sub
0391: * configurations it refers to are stored in a special config directory.
0392: *
0393: * @param configurationBasePath the new base path to set
0394: */
0395: public void setConfigurationBasePath(String configurationBasePath) {
0396: this .configurationBasePath = configurationBasePath;
0397: }
0398:
0399: /**
0400: * Adds a configuration provider for the specified tag. Whenever this tag is
0401: * encountered in the configuration definition file this provider will be
0402: * called to create the configuration object.
0403: *
0404: * @param tagName the name of the tag in the configuration definition file
0405: * @param provider the provider for this tag
0406: */
0407: public void addConfigurationProvider(String tagName,
0408: ConfigurationProvider provider) {
0409: if (tagName == null) {
0410: throw new IllegalArgumentException(
0411: "Tag name must not be null!");
0412: }
0413: if (provider == null) {
0414: throw new IllegalArgumentException(
0415: "Provider must not be null!");
0416: }
0417:
0418: providers.put(tagName, provider);
0419: }
0420:
0421: /**
0422: * Removes the configuration provider for the specified tag name.
0423: *
0424: * @param tagName the tag name
0425: * @return the removed configuration provider or <b>null</b> if none was
0426: * registered for that tag
0427: */
0428: public ConfigurationProvider removeConfigurationProvider(
0429: String tagName) {
0430: return (ConfigurationProvider) providers.remove(tagName);
0431: }
0432:
0433: /**
0434: * Returns the configuration provider for the given tag.
0435: *
0436: * @param tagName the name of the tag
0437: * @return the provider that was registered for this tag or <b>null</b> if
0438: * there is none
0439: */
0440: public ConfigurationProvider providerForTag(String tagName) {
0441: return (ConfigurationProvider) providers.get(tagName);
0442: }
0443:
0444: /**
0445: * Returns the configuration provided by this builder. Loads and parses the
0446: * configuration definition file and creates instances for the declared
0447: * configurations.
0448: *
0449: * @return the configuration
0450: * @throws ConfigurationException if an error occurs
0451: */
0452: public Configuration getConfiguration()
0453: throws ConfigurationException {
0454: return getConfiguration(true);
0455: }
0456:
0457: /**
0458: * Returns the configuration provided by this builder. If the boolean
0459: * parameter is <b>true</b>, the configuration definition file will be
0460: * loaded. It will then be parsed, and instances for the declared
0461: * configurations will be created.
0462: *
0463: * @param load a flag whether the configuration definition file should be
0464: * loaded; a value of <b>false</b> would make sense if the file has already
0465: * been created or its content was manipulated using some of the property
0466: * accessor methods
0467: * @return the configuration
0468: * @throws ConfigurationException if an error occurs
0469: */
0470: public CombinedConfiguration getConfiguration(boolean load)
0471: throws ConfigurationException {
0472: if (load) {
0473: load();
0474: }
0475:
0476: CombinedConfiguration result = createResultConfiguration();
0477: constructedConfiguration = result;
0478:
0479: List overrides = fetchTopLevelOverrideConfigs();
0480: overrides.addAll(fetchChildConfigs(KEY_OVERRIDE));
0481: initCombinedConfiguration(result, overrides, KEY_OVERRIDE_LIST);
0482:
0483: List additionals = fetchChildConfigs(KEY_UNION);
0484: if (!additionals.isEmpty()) {
0485: CombinedConfiguration addConfig = new CombinedConfiguration(
0486: new UnionCombiner());
0487: result.addConfiguration(addConfig, ADDITIONAL_NAME);
0488: initCombinedConfiguration(addConfig, additionals,
0489: KEY_ADDITIONAL_LIST);
0490: }
0491:
0492: return result;
0493: }
0494:
0495: /**
0496: * Creates the resulting combined configuration. This method is called by
0497: * <code>getConfiguration()</code>. It checks whether the
0498: * <code>header</code> section of the configuration definition file
0499: * contains a <code>result</code> element. If this is the case, it will be
0500: * used to initialize the properties of the newly created configuration
0501: * object.
0502: *
0503: * @return the resulting configuration object
0504: * @throws ConfigurationException if an error occurs
0505: */
0506: protected CombinedConfiguration createResultConfiguration()
0507: throws ConfigurationException {
0508: XMLBeanDeclaration decl = new XMLBeanDeclaration(this ,
0509: KEY_RESULT, true);
0510: CombinedConfiguration result = (CombinedConfiguration) BeanHelper
0511: .createBean(decl, CombinedConfiguration.class);
0512:
0513: if (getMaxIndex(KEY_COMBINER) < 0) {
0514: // No combiner defined => set default
0515: result.setNodeCombiner(new OverrideCombiner());
0516: }
0517:
0518: return result;
0519: }
0520:
0521: /**
0522: * Initializes a combined configuration for the configurations of a specific
0523: * section. This method is called for the override and for the additional
0524: * section (if it exists).
0525: *
0526: * @param config the configuration to be initialized
0527: * @param containedConfigs the list with the declaratinos of the contained
0528: * configurations
0529: * @param keyListNodes a list with the declaration of list nodes
0530: * @throws ConfigurationException if an error occurs
0531: */
0532: protected void initCombinedConfiguration(
0533: CombinedConfiguration config, List containedConfigs,
0534: String keyListNodes) throws ConfigurationException {
0535: List listNodes = getList(keyListNodes);
0536: for (Iterator it = listNodes.iterator(); it.hasNext();) {
0537: config.getNodeCombiner().addListNode((String) it.next());
0538: }
0539:
0540: for (Iterator it = containedConfigs.iterator(); it.hasNext();) {
0541: HierarchicalConfiguration conf = (HierarchicalConfiguration) it
0542: .next();
0543: ConfigurationDeclaration decl = new ConfigurationDeclaration(
0544: this , conf);
0545: AbstractConfiguration newConf = createConfigurationAt(decl);
0546: if (newConf != null) {
0547: config.addConfiguration(newConf, decl
0548: .getConfiguration().getString(ATTR_NAME), decl
0549: .getAt());
0550: }
0551: }
0552: }
0553:
0554: /**
0555: * Registers the default configuration providers supported by this class.
0556: * This method will be called during initialization. It registers
0557: * configuration providers for the tags that are supported by default.
0558: */
0559: protected void registerDefaultProviders() {
0560: for (int i = 0; i < DEFAULT_TAGS.length; i++) {
0561: addConfigurationProvider(DEFAULT_TAGS[i],
0562: DEFAULT_PROVIDERS[i]);
0563: }
0564: }
0565:
0566: /**
0567: * Performs interpolation. This method will not only take this configuration
0568: * instance into account (which is the one that loaded the configuration
0569: * definition file), but also the so far constructed combined configuration.
0570: * So variables can be used that point to properties that are defined in
0571: * configuration sources loaded by this builder.
0572: *
0573: * @param value the value to be interpolated
0574: * @return the interpolated value
0575: */
0576: protected Object interpolate(Object value) {
0577: Object result = super .interpolate(value);
0578: if (constructedConfiguration != null) {
0579: result = constructedConfiguration.interpolate(result);
0580: }
0581: return result;
0582: }
0583:
0584: /**
0585: * Creates a configuration object from the specified configuration
0586: * declaration.
0587: *
0588: * @param decl the configuration declaration
0589: * @return the new configuration object
0590: * @throws ConfigurationException if an error occurs
0591: */
0592: private AbstractConfiguration createConfigurationAt(
0593: ConfigurationDeclaration decl)
0594: throws ConfigurationException {
0595: try {
0596: return (AbstractConfiguration) BeanHelper.createBean(decl);
0597: } catch (Exception ex) {
0598: // redirect to configuration exceptions
0599: throw new ConfigurationException(ex);
0600: }
0601: }
0602:
0603: /**
0604: * Returns a list with <code>SubnodeConfiguration</code> objects for the
0605: * child nodes of the specified configuration node.
0606: *
0607: * @param node the start node
0608: * @return a list with subnode configurations for the node's children
0609: */
0610: private List fetchChildConfigs(ConfigurationNode node) {
0611: List children = node.getChildren();
0612: List result = new ArrayList(children.size());
0613: for (Iterator it = children.iterator(); it.hasNext();) {
0614: result.add(createSubnodeConfiguration((Node) it.next()));
0615: }
0616: return result;
0617: }
0618:
0619: /**
0620: * Returns a list with <code>SubnodeConfiguration</code> objects for the
0621: * child nodes of the node specified by the given key.
0622: *
0623: * @param key the key (must define exactly one node)
0624: * @return a list with subnode configurations for the node's children
0625: */
0626: private List fetchChildConfigs(String key) {
0627: List nodes = fetchNodeList(key);
0628: if (nodes.size() > 0) {
0629: return fetchChildConfigs((ConfigurationNode) nodes.get(0));
0630: } else {
0631: return Collections.EMPTY_LIST;
0632: }
0633: }
0634:
0635: /**
0636: * Finds the override configurations that are defined as top level elements
0637: * in the configuration definition file. This method will fetch the child
0638: * elements of the root node and remove the nodes that represent other
0639: * configuration sections. The remaining nodes are treated as definitions
0640: * for override configurations.
0641: *
0642: * @return a list with subnode configurations for the top level override
0643: * configurations
0644: */
0645: private List fetchTopLevelOverrideConfigs() {
0646: List configs = fetchChildConfigs(getRootNode());
0647: for (Iterator it = configs.iterator(); it.hasNext();) {
0648: String nodeName = ((SubnodeConfiguration) it.next())
0649: .getRootNode().getName();
0650: for (int i = 0; i < CONFIG_SECTIONS.length; i++) {
0651: if (CONFIG_SECTIONS[i].equals(nodeName)) {
0652: it.remove();
0653: break;
0654: }
0655: }
0656: }
0657: return configs;
0658: }
0659:
0660: /**
0661: * <p>
0662: * A base class for creating and initializing configuration sources.
0663: * </p>
0664: * <p>
0665: * Concrete sub classes of this base class are responsible for creating
0666: * specific <code>Configuration</code> objects for the tags in the
0667: * configuration definition file. The configuration factory will parse the
0668: * definition file and try to find a matching
0669: * <code>ConfigurationProvider</code> for each encountered tag. This
0670: * provider is then asked to create a corresponding
0671: * <code>Configuration</code> object. It is up to a concrete
0672: * implementation how this object is created and initialized.
0673: * </p>
0674: * <p>
0675: * Note that at the moment only configuration classes derived from
0676: * <code>{@link AbstractConfiguration}</code> are supported.
0677: * </p>
0678: */
0679: public static class ConfigurationProvider extends
0680: DefaultBeanFactory {
0681: /** Stores the class of the configuration to be created. */
0682: private Class configurationClass;
0683:
0684: /** Stores the name of the configuration class to be created.*/
0685: private String configurationClassName;
0686:
0687: /**
0688: * Creates a new uninitialized instance of
0689: * <code>ConfigurationProvider</code>.
0690: */
0691: public ConfigurationProvider() {
0692: this ((Class) null);
0693: }
0694:
0695: /**
0696: * Creates a new instance of <code>ConfigurationProvider</code> and
0697: * sets the class of the configuration created by this provider.
0698: *
0699: * @param configClass the configuration class
0700: */
0701: public ConfigurationProvider(Class configClass) {
0702: setConfigurationClass(configClass);
0703: }
0704:
0705: /**
0706: * Creates a new instance of <code>ConfigurationProvider</code> and
0707: * sets the name of the class of the configuration created by this
0708: * provider.
0709: *
0710: * @param configClassName the name of the configuration class
0711: * @since 1.4
0712: */
0713: public ConfigurationProvider(String configClassName) {
0714: setConfigurationClassName(configClassName);
0715: }
0716:
0717: /**
0718: * Returns the class of the configuration returned by this provider.
0719: *
0720: * @return the class of the provided configuration
0721: */
0722: public Class getConfigurationClass() {
0723: return configurationClass;
0724: }
0725:
0726: /**
0727: * Sets the class of the configuration returned by this provider.
0728: *
0729: * @param configurationClass the configuration class
0730: */
0731: public void setConfigurationClass(Class configurationClass) {
0732: this .configurationClass = configurationClass;
0733: }
0734:
0735: /**
0736: * Returns the name of the configuration class returned by this
0737: * provider.
0738: *
0739: * @return the configuration class name
0740: * @since 1.4
0741: */
0742: public String getConfigurationClassName() {
0743: return configurationClassName;
0744: }
0745:
0746: /**
0747: * Sets the name of the configuration class returned by this provider.
0748: *
0749: * @param configurationClassName the name of the configuration class
0750: * @since 1.4
0751: */
0752: public void setConfigurationClassName(
0753: String configurationClassName) {
0754: this .configurationClassName = configurationClassName;
0755: }
0756:
0757: /**
0758: * Returns the configuration. This method is called to fetch the
0759: * configuration from the provider. This implementation will call the
0760: * inherited <code>{@link
0761: * org.apache.commons.configuration.beanutils.DefaultBeanFactory#createBean(Class, BeanDeclaration, Object)
0762: * createBean()}</code> method to create a new instance of the
0763: * configuration class.
0764: *
0765: * @param decl the bean declaration with initialization parameters for
0766: * the configuration
0767: * @return the new configuration object
0768: * @throws Exception if an error occurs
0769: */
0770: public AbstractConfiguration getConfiguration(
0771: ConfigurationDeclaration decl) throws Exception {
0772: return (AbstractConfiguration) createBean(
0773: fetchConfigurationClass(), decl, null);
0774: }
0775:
0776: /**
0777: * Returns an uninitialized configuration of the represented type. This
0778: * method will be called for optional configurations when the
0779: * <code>getConfiguration()</code> method caused an error and the
0780: * <code>forceCreate</code> attribute is set. A concrete sub class can
0781: * here try to create an uninitialized, empty configuration, which may
0782: * be possible if the error was created during initialization. This base
0783: * implementation just returns <b>null</b>.
0784: *
0785: * @param decl the bean declaration with initialization parameters for
0786: * the configuration
0787: * @return the new configuration object
0788: * @throws Exception if an error occurs
0789: * @since 1.4
0790: */
0791: public AbstractConfiguration getEmptyConfiguration(
0792: ConfigurationDeclaration decl) throws Exception {
0793: return null;
0794: }
0795:
0796: /**
0797: * Returns the configuration class supported by this provider. If a
0798: * class object was set, it is returned. Otherwise the method tries to
0799: * resolve the class name.
0800: *
0801: * @return the class of the configuration to be created
0802: * @since 1.4
0803: */
0804: protected synchronized Class fetchConfigurationClass()
0805: throws Exception {
0806: if (getConfigurationClass() == null) {
0807: setConfigurationClass(loadClass(getConfigurationClassName()));
0808: }
0809: return getConfigurationClass();
0810: }
0811:
0812: /**
0813: * Loads the class with the specified name dynamically. If the class's
0814: * name is <b>null</b>, <b>null</b> will also be returned.
0815: *
0816: * @param className the name of the class to be loaded
0817: * @return the class object
0818: * @throws ClassNotFoundException if class loading fails
0819: * @since 1.4
0820: */
0821: protected Class loadClass(String className)
0822: throws ClassNotFoundException {
0823: return (className != null) ? Class.forName(className, true,
0824: getClass().getClassLoader()) : null;
0825: }
0826: }
0827:
0828: /**
0829: * <p>
0830: * A specialized <code>BeanDeclaration</code> implementation that
0831: * represents the declaration of a configuration source.
0832: * </p>
0833: * <p>
0834: * Instances of this class are able to extract all information about a
0835: * configuration source from the configuration definition file. The
0836: * declaration of a configuration source is very similar to a bean
0837: * declaration processed by <code>XMLBeanDeclaration</code>. There are
0838: * very few differences, e.g. some reserved attributes like
0839: * <code>optional</code> and <code>at</code> and the fact that a bean
0840: * factory is never needed.
0841: * </p>
0842: */
0843: public static class ConfigurationDeclaration extends
0844: XMLBeanDeclaration {
0845: /** Stores a reference to the associated configuration builder. */
0846: private DefaultConfigurationBuilder configurationBuilder;
0847:
0848: /**
0849: * Creates a new instance of <code>ConfigurationDeclaration</code> and
0850: * initializes it.
0851: *
0852: * @param builder the associated configuration builder
0853: * @param config the configuration this declaration is based onto
0854: */
0855: public ConfigurationDeclaration(
0856: DefaultConfigurationBuilder builder,
0857: HierarchicalConfiguration config) {
0858: super (config);
0859: configurationBuilder = builder;
0860: }
0861:
0862: /**
0863: * Returns the associated configuration builder.
0864: *
0865: * @return the configuration builder
0866: */
0867: public DefaultConfigurationBuilder getConfigurationBuilder() {
0868: return configurationBuilder;
0869: }
0870:
0871: /**
0872: * Returns the value of the <code>at</code> attribute.
0873: *
0874: * @return the value of the <code>at</code> attribute (can be <b>null</b>)
0875: */
0876: public String getAt() {
0877: String result = this .getConfiguration().getString(
0878: ATTR_AT_RES);
0879: return (result == null) ? this .getConfiguration()
0880: .getString(ATTR_AT) : result;
0881: }
0882:
0883: /**
0884: * Returns a flag whether this is an optional configuration.
0885: *
0886: * @return a flag if this declaration points to an optional
0887: * configuration
0888: */
0889: public boolean isOptional() {
0890: Boolean value = this .getConfiguration().getBoolean(
0891: ATTR_OPTIONAL_RES, null);
0892: if (value == null) {
0893: value = this .getConfiguration().getBoolean(
0894: ATTR_OPTIONAL, Boolean.FALSE);
0895: }
0896: return value.booleanValue();
0897: }
0898:
0899: /**
0900: * Returns a flag whether this configuration should always be created
0901: * and added to the resulting combined configuration. This flag is
0902: * evaluated only for optional configurations whose normal creation has
0903: * caused an error. If for such a configuration the
0904: * <code>forceCreate</code> attribute is set and the corresponding
0905: * configuration provider supports this mode, an empty configuration
0906: * will be created and added to the resulting combined configuration.
0907: *
0908: * @return the value of the <code>forceCreate</code> attribute
0909: * @since 1.4
0910: */
0911: public boolean isForceCreate() {
0912: return this .getConfiguration().getBoolean(ATTR_FORCECREATE,
0913: false);
0914: }
0915:
0916: /**
0917: * Returns the name of the bean factory. For configuration source
0918: * declarations always a reserved factory is used. This factory's name
0919: * is returned by this implementation.
0920: *
0921: * @return the name of the bean factory
0922: */
0923: public String getBeanFactoryName() {
0924: return CONFIG_BEAN_FACTORY_NAME;
0925: }
0926:
0927: /**
0928: * Returns the bean's class name. This implementation will always return
0929: * <b>null</b>.
0930: *
0931: * @return the name of the bean's class
0932: */
0933: public String getBeanClassName() {
0934: return null;
0935: }
0936:
0937: /**
0938: * Checks whether the given node is reserved. This method will take
0939: * further reserved attributes into account
0940: *
0941: * @param nd the node
0942: * @return a flag whether this node is reserved
0943: */
0944: protected boolean isReservedNode(ConfigurationNode nd) {
0945: if (super .isReservedNode(nd)) {
0946: return true;
0947: }
0948:
0949: return nd.isAttribute()
0950: && ((ATTR_ATNAME.equals(nd.getName()) && nd
0951: .getParentNode().getAttributeCount(
0952: RESERVED_PREFIX + ATTR_ATNAME) == 0) || (ATTR_OPTIONALNAME
0953: .equals(nd.getName()) && nd
0954: .getParentNode()
0955: .getAttributeCount(
0956: RESERVED_PREFIX + ATTR_OPTIONALNAME) == 0));
0957: }
0958:
0959: /**
0960: * Performs interpolation. This implementation will delegate
0961: * interpolation to the configuration builder, which takes care that the
0962: * currently constructed configuration is taken into account, too.
0963: *
0964: * @param value the value to be interpolated
0965: * @return the interpolated value
0966: */
0967: protected Object interpolate(Object value) {
0968: return getConfigurationBuilder().interpolate(value);
0969: }
0970: }
0971:
0972: /**
0973: * A specialized <code>BeanFactory</code> implementation that handles
0974: * configuration declarations. This class will retrieve the correct
0975: * configuration provider and delegate the task of creating the
0976: * configuration to this object.
0977: */
0978: static class ConfigurationBeanFactory implements BeanFactory {
0979: /**
0980: * Creates an instance of a bean class. This implementation expects that
0981: * the passed in bean declaration is a declaration for a configuration.
0982: * It will determine the responsible configuration provider and delegate
0983: * the call to this instance. If creation of the configuration fails
0984: * and the <code>optional</code> attribute is set, the exception will
0985: * be ignored. If the <code>forceCreate</code> attribute is set, too,
0986: * the provider is asked to create an empty configuration. A return
0987: * value of <b>null</b> means that no configuration could be created.
0988: *
0989: * @param beanClass the bean class (will be ignored)
0990: * @param data the declaration
0991: * @param param an additional parameter (will be ignored)
0992: * @return the newly created configuration
0993: * @throws Exception if an error occurs
0994: */
0995: public Object createBean(Class beanClass, BeanDeclaration data,
0996: Object param) throws Exception {
0997: ConfigurationDeclaration decl = (ConfigurationDeclaration) data;
0998: String tagName = decl.getNode().getName();
0999: ConfigurationProvider provider = decl
1000: .getConfigurationBuilder().providerForTag(tagName);
1001: if (provider == null) {
1002: throw new ConfigurationRuntimeException(
1003: "No ConfigurationProvider registered for tag "
1004: + tagName);
1005: }
1006:
1007: try {
1008: return provider.getConfiguration(decl);
1009: } catch (Exception ex) {
1010: // If this is an optional configuration, ignore the exception
1011: if (!decl.isOptional()) {
1012: throw ex;
1013: } else {
1014: if (decl.isForceCreate()) {
1015: try {
1016: return provider.getEmptyConfiguration(decl);
1017: } catch (Exception ex2) {
1018: // Ignore exception, return null in this case
1019: ;
1020: }
1021: }
1022: return null;
1023: }
1024: }
1025: }
1026:
1027: /**
1028: * Returns the default class for this bean factory.
1029: *
1030: * @return the default class
1031: */
1032: public Class getDefaultBeanClass() {
1033: // Here some valid class must be returned, otherwise BeanHelper
1034: // will complain that the bean's class cannot be determined
1035: return Configuration.class;
1036: }
1037: }
1038:
1039: /**
1040: * A specialized provider implementation that deals with file based
1041: * configurations. Ensures that the base path is correctly set and that the
1042: * load() method gets called.
1043: */
1044: public static class FileConfigurationProvider extends
1045: ConfigurationProvider {
1046: /**
1047: * Creates a new instance of <code>FileConfigurationProvider</code>.
1048: */
1049: public FileConfigurationProvider() {
1050: super ();
1051: }
1052:
1053: /**
1054: * Creates a new instance of <code>FileConfigurationProvider</code>
1055: * and sets the configuration class.
1056: *
1057: * @param configClass the class for the configurations to be created
1058: */
1059: public FileConfigurationProvider(Class configClass) {
1060: super (configClass);
1061: }
1062:
1063: /**
1064: * Creates a new instance of <code>FileConfigurationProvider</code>
1065: * and sets the configuration class name.
1066: *
1067: * @param configClassName the name of the configuration to be created
1068: * @since 1.4
1069: */
1070: public FileConfigurationProvider(String configClassName) {
1071: super (configClassName);
1072: }
1073:
1074: /**
1075: * Creates the configuration. After that <code>load()</code> will be
1076: * called. If this configuration is marked as optional, exceptions will
1077: * be ignored.
1078: *
1079: * @param decl the declaration
1080: * @return the new configuration
1081: * @throws Exception if an error occurs
1082: */
1083: public AbstractConfiguration getConfiguration(
1084: ConfigurationDeclaration decl) throws Exception {
1085: AbstractConfiguration result = getEmptyConfiguration(decl);
1086: ((FileConfiguration) result).load();
1087: return result;
1088: }
1089:
1090: /**
1091: * Returns an uninitialized file configuration. This method will be
1092: * called for optional configurations when the
1093: * <code>getConfiguration()</code> method caused an error and the
1094: * <code>forceCreate</code> attribute is set. It will create the
1095: * configuration of the represented type, but the <code>load()</code>
1096: * method won't be called. This way non-existing configuration files can
1097: * be handled gracefully: If loading a the file fails, an empty
1098: * configuration will be created that is already configured with the
1099: * correct file name.
1100: *
1101: * @param decl the bean declaration with initialization parameters for
1102: * the configuration
1103: * @return the new configuration object
1104: * @throws Exception if an error occurs
1105: * @since 1.4
1106: */
1107: public AbstractConfiguration getEmptyConfiguration(
1108: ConfigurationDeclaration decl) throws Exception {
1109: return super .getConfiguration(decl);
1110: }
1111:
1112: /**
1113: * Initializes the bean instance. Ensures that the file configuration's
1114: * base path will be initialized with the base path of the factory so
1115: * that relative path names can be correctly resolved.
1116: *
1117: * @param bean the bean to be initialized
1118: * @param data the declaration
1119: * @throws Exception if an error occurs
1120: */
1121: protected void initBeanInstance(Object bean,
1122: BeanDeclaration data) throws Exception {
1123: FileConfiguration config = (FileConfiguration) bean;
1124: config.setBasePath(((ConfigurationDeclaration) data)
1125: .getConfigurationBuilder()
1126: .getConfigurationBasePath());
1127: super .initBeanInstance(bean, data);
1128: }
1129: }
1130:
1131: /**
1132: * A specialized configuration provider for file based configurations that
1133: * can handle configuration sources whose concrete type depends on the
1134: * extension of the file to be loaded. One example is the
1135: * <code>properties</code> tag: if the file ends with ".xml" a
1136: * XMLPropertiesConfiguration object must be created, otherwise a
1137: * PropertiesConfiguration object.
1138: */
1139: static class FileExtensionConfigurationProvider extends
1140: FileConfigurationProvider {
1141: /**
1142: * Stores the class to be created when the file extension matches.
1143: */
1144: private Class matchingClass;
1145:
1146: /**
1147: * Stores the name of the class to be created when the file extension
1148: * matches.
1149: */
1150: private String matchingClassName;
1151:
1152: /**
1153: * Stores the class to be created when the file extension does not
1154: * match.
1155: */
1156: private Class defaultClass;
1157:
1158: /**
1159: * Stores the name of the class to be created when the file extension
1160: * does not match.
1161: */
1162: private String defaultClassName;
1163:
1164: /** Stores the file extension to be checked against. */
1165: private String fileExtension;
1166:
1167: /**
1168: * Creates a new instance of
1169: * <code>FileExtensionConfigurationProvider</code> and initializes it.
1170: *
1171: * @param matchingClass the class to be created when the file extension
1172: * matches
1173: * @param defaultClass the class to be created when the file extension
1174: * does not match
1175: * @param extension the file extension to be checked agains
1176: */
1177: public FileExtensionConfigurationProvider(Class matchingClass,
1178: Class defaultClass, String extension) {
1179: this .matchingClass = matchingClass;
1180: this .defaultClass = defaultClass;
1181: fileExtension = extension;
1182: }
1183:
1184: /**
1185: * Creates a new instance of
1186: * <code>FileExtensionConfigurationProvider</code> and initializes it
1187: * with the names of the classes to be created.
1188: *
1189: * @param matchingClassName the name of the class to be created when the
1190: * file extension matches
1191: * @param defaultClassName the name of the class to be created when the
1192: * file extension does not match
1193: * @param extension the file extension to be checked agains
1194: * @since 1.4
1195: */
1196: public FileExtensionConfigurationProvider(
1197: String matchingClassName, String defaultClassName,
1198: String extension) {
1199: this .matchingClassName = matchingClassName;
1200: this .defaultClassName = defaultClassName;
1201: fileExtension = extension;
1202: }
1203:
1204: /**
1205: * Returns the matching class object, no matter whether it was defined
1206: * as a class or as a class name.
1207: *
1208: * @return the matching class object
1209: * @throws Exception if an error occurs
1210: * @since 1.4
1211: */
1212: protected synchronized Class fetchMatchingClass()
1213: throws Exception {
1214: if (matchingClass == null) {
1215: matchingClass = loadClass(matchingClassName);
1216: }
1217: return matchingClass;
1218: }
1219:
1220: /**
1221: * Returns the default class object, no matter whether it was defined as
1222: * a class or as a class name.
1223: *
1224: * @return the default class object
1225: * @throws Exception if an error occurs
1226: * @since 1.4
1227: */
1228: protected synchronized Class fetchDefaultClass()
1229: throws Exception {
1230: if (defaultClass == null) {
1231: defaultClass = loadClass(defaultClassName);
1232: }
1233: return defaultClass;
1234: }
1235:
1236: /**
1237: * Creates the configuration object. The class is determined by the file
1238: * name's extension.
1239: *
1240: * @param beanClass the class
1241: * @param data the bean declaration
1242: * @return the new bean
1243: * @throws Exception if an error occurs
1244: */
1245: protected Object createBeanInstance(Class beanClass,
1246: BeanDeclaration data) throws Exception {
1247: String fileName = ((ConfigurationDeclaration) data)
1248: .getConfiguration().getString(ATTR_FILENAME);
1249: if (fileName != null
1250: && fileName.toLowerCase().trim().endsWith(
1251: fileExtension)) {
1252: return super .createBeanInstance(fetchMatchingClass(),
1253: data);
1254: } else {
1255: return super .createBeanInstance(fetchDefaultClass(),
1256: data);
1257: }
1258: }
1259: }
1260:
1261: /**
1262: * A specialized configuration provider class that allows to include other
1263: * configuration definition files.
1264: */
1265: static class ConfigurationBuilderProvider extends
1266: ConfigurationProvider {
1267: /**
1268: * Creates a new instance of <code>ConfigurationBuilderProvider</code>.
1269: */
1270: public ConfigurationBuilderProvider() {
1271: super (DefaultConfigurationBuilder.class);
1272: }
1273:
1274: /**
1275: * Creates the configuration. First creates a configuration builder
1276: * object. Then returns the configuration created by this builder.
1277: *
1278: * @param decl the configuration declaration
1279: * @return the configuration
1280: * @exception Exception if an error occurs
1281: */
1282: public AbstractConfiguration getConfiguration(
1283: ConfigurationDeclaration decl) throws Exception {
1284: DefaultConfigurationBuilder builder = (DefaultConfigurationBuilder) super
1285: .getConfiguration(decl);
1286: return builder.getConfiguration(true);
1287: }
1288:
1289: /**
1290: * Returns an empty configuration in case of an optional configuration
1291: * could not be created. This implementation returns an empty combined
1292: * configuration.
1293: *
1294: * @param decl the configuration declaration
1295: * @return the configuration
1296: * @exception Exception if an error occurs
1297: * @since 1.4
1298: */
1299: public AbstractConfiguration getEmptyConfiguration(
1300: ConfigurationDeclaration decl) throws Exception {
1301: return new CombinedConfiguration();
1302: }
1303: }
1304:
1305: static {
1306: // register the configuration bean factory
1307: BeanHelper.registerBeanFactory(CONFIG_BEAN_FACTORY_NAME,
1308: new ConfigurationBeanFactory());
1309: }
1310: }
|