You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by oh...@apache.org on 2006/02/26 19:57:51 UTC

svn commit: r381140 - in /jakarta/commons/proper/configuration/trunk: conf/testComplexInitialization.xml src/java/org/apache/commons/configuration/XMLConfigurationFactory.java src/test/org/apache/commons/configuration/TestXMLConfigurationFactory.java

Author: oheger
Date: Sun Feb 26 10:57:49 2006
New Revision: 381140

URL: http://svn.apache.org/viewcvs?rev=381140&view=rev
Log:
Added an alternative ConfigurationFactory implementation

Added:
    jakarta/commons/proper/configuration/trunk/conf/testComplexInitialization.xml   (with props)
    jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/XMLConfigurationFactory.java   (with props)
    jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestXMLConfigurationFactory.java   (with props)

Added: jakarta/commons/proper/configuration/trunk/conf/testComplexInitialization.xml
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/conf/testComplexInitialization.xml?rev=381140&view=auto
==============================================================================
--- jakarta/commons/proper/configuration/trunk/conf/testComplexInitialization.xml (added)
+++ jakarta/commons/proper/configuration/trunk/conf/testComplexInitialization.xml Sun Feb 26 10:57:49 2006
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="ISO-8859-1" ?>
+<!-- Test configuration definition file that demonstrates complex initialization -->
+<configuration>
+  <properties fileName="test.properties" throwExceptionOnMissing="true">
+    <reloadingStrategy config-class="org.apache.commons.configuration.reloading.FileChangedReloadingStrategy"
+      refreshDelay="10000"/>
+  </properties>
+  <xml fileName="test.xml">
+    <expressionEngine config-class="org.apache.commons.configuration.tree.DefaultExpressionEngine"
+      propertyDelimiter="/" indexStart="[" indexEnd="]"/>
+  </xml>
+</configuration>

Propchange: jakarta/commons/proper/configuration/trunk/conf/testComplexInitialization.xml
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jakarta/commons/proper/configuration/trunk/conf/testComplexInitialization.xml
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: jakarta/commons/proper/configuration/trunk/conf/testComplexInitialization.xml
------------------------------------------------------------------------------
    svn:mime-type = text/xml

Added: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/XMLConfigurationFactory.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/XMLConfigurationFactory.java?rev=381140&view=auto
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/XMLConfigurationFactory.java (added)
+++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/XMLConfigurationFactory.java Sun Feb 26 10:57:49 2006
@@ -0,0 +1,851 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.net.URL;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.commons.configuration.beanutils.BeanDeclaration;
+import org.apache.commons.configuration.beanutils.BeanFactory;
+import org.apache.commons.configuration.beanutils.BeanHelper;
+import org.apache.commons.configuration.beanutils.DefaultBeanFactory;
+import org.apache.commons.configuration.beanutils.XMLBeanDeclaration;
+import org.apache.commons.configuration.plist.PropertyListConfiguration;
+import org.apache.commons.configuration.plist.XMLPropertyListConfiguration;
+import org.apache.commons.configuration.tree.ConfigurationNode;
+import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
+
+/**
+ * <p>
+ * A factory class that creates a composite configuration from an XML based
+ * <em>configuration definition file</em>.
+ * </p>
+ * <p>
+ * This class provides an easy and flexible means for loading multiple
+ * configuration sources and combining the results into a single configuration
+ * object. The sources to be loaded are defined in an XML document that can
+ * contain certain tags representing the different supported configuration
+ * classes. If such a tag is found, the corresponding <code>Configuration</code>
+ * class is instantiated and initialized using the classes of the
+ * <code>beanutils</code> package (namely
+ * <code>{@link org.apache.commons.configuration.beanutils.XMLBeanDeclaration XMLBeanDeclaration}</code>
+ * will be used to extract the configuration's initialization parameters, which
+ * allows for complex initialization szenarios).
+ * </p>
+ * <p>
+ * It is also possible to add custom tags to the configuration definition file.
+ * For this purpose register your own <code>ConfigurationProvider</code>
+ * implementation for your tag using the <code>addConfigurationProvider()</code>
+ * method. This provider will then be called when the corresponding custom tag
+ * is detected. For the default configuration classes providers are already
+ * registered.
+ * </p>
+ *
+ * @since 1.3
+ * @author Oliver Heger
+ * @version $Id$
+ */
+public class XMLConfigurationFactory extends XMLConfiguration
+{
+    /** Constant for the expression engine used by this factory. */
+    static final XPathExpressionEngine EXPRESSION_ENGINE = new XPathExpressionEngine();
+
+    /** Constant for the name of the configuration bean factory. */
+    static final String CONFIG_BEAN_FACTORY_NAME = XMLConfigurationFactory.class
+            .getName()
+            + ".CONFIG_BEAN_FACTORY_NAME";
+
+    /** Constant for the reserved at attribute. */
+    static final String ATTR_AT = "at";
+
+    /** Constant for the reserved optional attribute. */
+    static final String ATTR_OPTIONAL = "optional";
+
+    /** Constant for the file name attribute. */
+    static final String ATTR_FILENAME = "fileName";
+
+    /** Constant for an expression that selects the union configurations. */
+    static final String KEY_UNION = "/additional/*";
+
+    /** Constant for an expression that selects override configurations. */
+    static final String KEY_OVERRIDE1 = "/*[local-name() != 'additional' and local-name() != 'override']";
+
+    /**
+     * Constant for an expression that selects override configurations in the
+     * override section.
+     */
+    static final String KEY_OVERRIDE2 = "/override/*";
+
+    /** Constant for the XML file extension. */
+    static final String EXT_XML = ".xml";
+
+    /** Constant for the provider for properties files. */
+    private static final ConfigurationProvider PROPERTIES_PROVIDER = new FileExtensionConfigurationProvider(
+            XMLPropertiesConfiguration.class, PropertiesConfiguration.class,
+            EXT_XML);
+
+    /** Constant for the provider for XML files. */
+    private static final ConfigurationProvider XML_PROVIDER = new FileConfigurationProvider(
+            XMLConfiguration.class);
+
+    /** Constant for the provider for JNDI sources. */
+    private static final ConfigurationProvider JNDI_PROVIDER = new ConfigurationProvider(
+            JNDIConfiguration.class);
+
+    /** Constant for the provider for system properties. */
+    private static final ConfigurationProvider SYSTEM_PROVIDER = new ConfigurationProvider(
+            SystemConfiguration.class);
+
+    /** Constant for the provider for plist files. */
+    private static final ConfigurationProvider PLIST_PROVIDER = new FileExtensionConfigurationProvider(
+            XMLPropertyListConfiguration.class,
+            PropertyListConfiguration.class, EXT_XML);
+
+    /** An array with the names of the default tags. */
+    private static final String[] DEFAULT_TAGS =
+    { "properties", "xml", "hierarchicalXml", "jndi", "system", "plist"};
+
+    /** An array with the providers for the default tags. */
+    private static final ConfigurationProvider[] DEFAULT_PROVIDERS =
+    { PROPERTIES_PROVIDER, XML_PROVIDER, XML_PROVIDER, JNDI_PROVIDER,
+            SYSTEM_PROVIDER, PLIST_PROVIDER};
+
+    /** Stores a map with the registered configuration providers. */
+    private Map providers;
+
+    /** Stores the base path to the configuration sources to load. */
+    private String configurationBasePath;
+
+    /**
+     * Creates a new instance of <code>XMLConfigurationFactory</code>. A
+     * configuration definition file is not yet loaded. Use the diverse setter
+     * methods provided by file based configurations to specify the
+     * configuration definition file.
+     */
+    public XMLConfigurationFactory()
+    {
+        super();
+        providers = new HashMap();
+        setExpressionEngine(EXPRESSION_ENGINE);
+        registerDefaultProviders();
+    }
+
+    /**
+     * Creates a new instance of <code>XMLConfigurationFactory</code> and sets
+     * the specified configuration definition file.
+     *
+     * @param file the configuration definition file
+     */
+    public XMLConfigurationFactory(File file)
+    {
+        this();
+        setFile(file);
+    }
+
+    /**
+     * Creates a new instance of <code>XMLConfigurationFactory</code> and sets
+     * the specified configuration definition file.
+     *
+     * @param fileName the name of the configuration definition file
+     * @throws ConfigurationException if an error occurs when the file is loaded
+     */
+    public XMLConfigurationFactory(String fileName)
+            throws ConfigurationException
+    {
+        this();
+        setFileName(fileName);
+    }
+
+    /**
+     * Creates a new instance of <code>XMLConfigurationFactory</code> and sets
+     * the specified configuration definition file.
+     *
+     * @param url the URL to the configuration definition file
+     * @throws ConfigurationException if an error occurs when the file is loaded
+     */
+    public XMLConfigurationFactory(URL url) throws ConfigurationException
+    {
+        this();
+        setURL(url);
+    }
+
+    /**
+     * Returns the base path for the configuration sources to load. This path is
+     * used to resolve relative paths in the configuration definition file.
+     *
+     * @return the base path for configuration sources
+     */
+    public String getConfigurationBasePath()
+    {
+        return (configurationBasePath != null) ? configurationBasePath
+                : getBasePath();
+    }
+
+    /**
+     * Sets the base path for the configuration sources to load. Normally a base
+     * path need not to be set because it is determined by the location of the
+     * configuration definition file to load. All relative pathes in this file
+     * are resolved relative to this file. Setting a base path makes sense if
+     * such relative pathes should be otherwise resolved, e.g. if the
+     * configuration file is loaded from the class path and all sub
+     * configurations it refers to are stored in a special config directory.
+     *
+     * @param configurationBasePath the new base path to set
+     */
+    public void setConfigurationBasePath(String configurationBasePath)
+    {
+        this.configurationBasePath = configurationBasePath;
+    }
+
+    /**
+     * Adds a configuration provider for the specified tag. Whenever this tag is
+     * encountered in the configuration definition file this provider will be
+     * called to create the configuration object.
+     *
+     * @param tagName the name of the tag in the configuration definition file
+     * @param provider the provider for this tag
+     */
+    public void addConfigurationProvider(String tagName,
+            ConfigurationProvider provider)
+    {
+        if (tagName == null)
+        {
+            throw new IllegalArgumentException("Tag name must not be null!");
+        }
+        if (provider == null)
+        {
+            throw new IllegalArgumentException("Provider must not be null!");
+        }
+
+        providers.put(tagName, provider);
+    }
+
+    /**
+     * Removes the configuration provider for the specified tag name.
+     *
+     * @param tagName the tag name
+     * @return the removed configuration provider or <b>null</b> if none was
+     * registered for that tag
+     */
+    public ConfigurationProvider removeConfigurationProvider(String tagName)
+    {
+        return (ConfigurationProvider) providers.remove(tagName);
+    }
+
+    /**
+     * Returns the configuration provider for the given tag.
+     *
+     * @param tagName the name of the tag
+     * @return the provider that was registered for this tag or <b>null</b> if
+     * there is none
+     */
+    public ConfigurationProvider providerForTag(String tagName)
+    {
+        return (ConfigurationProvider) providers.get(tagName);
+    }
+
+    /**
+     * Returns the configuration provided by this factory. Loads and parses the
+     * configuration definition file and creates instances for the declared
+     * configurations.
+     *
+     * @return the configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    public Configuration getConfiguration() throws ConfigurationException
+    {
+        return getConfiguration(true);
+    }
+
+    /**
+     * Returns the configuration provided by this factory. If the boolean
+     * parameter is <b>true</b>, the configuration definition file will be
+     * loaded. It will then be parsed, and instances for the declared
+     * configurations will be created.
+     *
+     * @param load a flag whether the configuration definition file should be
+     * loaded; a value of <b>false</b> would make sense if the file has already
+     * been created or its content was manipulated using some of the property
+     * accessor methods
+     * @return the configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    public Configuration getConfiguration(boolean load)
+            throws ConfigurationException
+    {
+        if (load)
+        {
+            load();
+        }
+
+        List overrides = configurationsAt(KEY_OVERRIDE1);
+        overrides.addAll(configurationsAt(KEY_OVERRIDE2));
+        CompositeConfiguration result = createOverrideConfiguration(overrides);
+        List additionals = configurationsAt(KEY_UNION);
+        if (!additionals.isEmpty())
+        {
+            result.addConfiguration(createUnionConfiguration(additionals));
+        }
+
+        return result;
+    }
+
+    /**
+     * Creates a composite configuration for the passed in configuration
+     * declarations.
+     *
+     * @param subs a list with sub configurations that contain configuration
+     * declarations for override configurations
+     * @return the composite configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    protected CompositeConfiguration createOverrideConfiguration(List subs)
+            throws ConfigurationException
+    {
+        CompositeConfiguration cc = new CompositeConfiguration();
+
+        for (Iterator it = subs.iterator(); it.hasNext();)
+        {
+            cc
+                    .addConfiguration(createConfigurationAt((HierarchicalConfiguration) it
+                            .next()));
+        }
+
+        return cc;
+    }
+
+    /**
+     * Creates a union configuration for the passed in configuration
+     * declarations. This method will create configuration objects for the
+     * passed in descriptions and combine them into a single union
+     * configuration.
+     *
+     * @param subs a list with sub configurations that contain configuration
+     * declarations
+     * @return the union configuration
+     * @throws ConfigurationException if an error occurs
+     */
+    protected HierarchicalConfiguration createUnionConfiguration(List subs)
+            throws ConfigurationException
+    {
+        HierarchicalConfiguration union = new HierarchicalConfiguration();
+
+        for (Iterator it = subs.iterator(); it.hasNext();)
+        {
+            HierarchicalConfiguration conf = (HierarchicalConfiguration) it
+                    .next();
+            ConfigurationDeclaration decl = new ConfigurationDeclaration(this,
+                    conf);
+            union.addNodes(decl.getAt(), convertToHierarchical(
+                    createConfigurationAt(decl)).getRoot().getChildren());
+        }
+
+        return union;
+    }
+
+    /**
+     * Converts the passed in configuration to a hierarchical one. If the
+     * configuration is already hierarchical, it is directly returned. Otherwise
+     * all properties are copied into a new hierarchical configuration.
+     *
+     * @param conf the configuration to convert
+     * @return the new hierarchical configuration
+     */
+    protected HierarchicalConfiguration convertToHierarchical(Configuration conf)
+    {
+        if (conf instanceof HierarchicalConfiguration)
+        {
+            return (HierarchicalConfiguration) conf;
+        }
+        else
+        {
+            HierarchicalConfiguration hc = new HierarchicalConfiguration();
+            ConfigurationUtils.copy(conf, hc);
+            return hc;
+        }
+    }
+
+    /**
+     * Registers the default configuration providers supported by this class.
+     * This method will be called during initialization. It registers
+     * configuration providers for the tags that are supported by default.
+     */
+    protected void registerDefaultProviders()
+    {
+        for (int i = 0; i < DEFAULT_TAGS.length; i++)
+        {
+            addConfigurationProvider(DEFAULT_TAGS[i], DEFAULT_PROVIDERS[i]);
+        }
+    }
+
+    /**
+     * Creates a configuration object from the specified configuration
+     * declaration.
+     *
+     * @param decl the configuration declaration
+     * @return the new configuration object
+     * @throws ConfigurationException if an error occurs
+     */
+    private Configuration createConfigurationAt(ConfigurationDeclaration decl)
+            throws ConfigurationException
+    {
+        try
+        {
+            return (Configuration) BeanHelper.createBean(decl);
+        }
+        catch (Exception ex)
+        {
+            // redirect to configuration exceptions
+            throw new ConfigurationException(ex);
+        }
+    }
+
+    /**
+     * Creates a configuration object from the specified sub configuration.
+     *
+     * @param sub the sub configuration
+     * @return the new configuration object
+     * @throws ConfigurationException if an error occurs
+     */
+    private Configuration createConfigurationAt(HierarchicalConfiguration sub)
+            throws ConfigurationException
+    {
+        return createConfigurationAt(new ConfigurationDeclaration(this, sub));
+    }
+
+    /**
+     * <p>
+     * A base class for creating and initializing configuration sources.
+     * </p>
+     * <p>
+     * Concrete sub classes of this base class are responsible for creating
+     * specific <code>Configuration</code> objects for the tags in the
+     * configuration definition file. The configuration factory will parse the
+     * definition file and try to find a matching
+     * <code>ConfigurationProvider</code> for each encountered tag. This
+     * provider is then asked to create a corresponding
+     * <code>Configuration</code> object. It is up to a concrete
+     * implementation how this object is created and initialized.
+     * </p>
+     */
+    public static class ConfigurationProvider extends DefaultBeanFactory
+    {
+        /** Stores the class of the configuration to be created. */
+        private Class configurationClass;
+
+        /**
+         * Creates a new uninitialized instance of
+         * <code>ConfigurationProvider</code>.
+         */
+        public ConfigurationProvider()
+        {
+            this(null);
+        }
+
+        /**
+         * Creates a new instance of <code>ConfigurationProvider</code> and
+         * sets the class of the configuration created by this provider.
+         *
+         * @param configClass the configuration class
+         */
+        public ConfigurationProvider(Class configClass)
+        {
+            setConfigurationClass(configClass);
+        }
+
+        /**
+         * Returns the class of the configuration returned by this provider.
+         *
+         * @return the class of the provided configuration
+         */
+        public Class getConfigurationClass()
+        {
+            return configurationClass;
+        }
+
+        /**
+         * Sets the class of the configuration returned by this provider.
+         *
+         * @param configurationClass the configuration class
+         */
+        public void setConfigurationClass(Class configurationClass)
+        {
+            this.configurationClass = configurationClass;
+        }
+
+        /**
+         * Returns the configuration. This method is called to fetch the
+         * configuration from the provider. This implementation will call the
+         * inherited
+         * <code>{@link org.apache.commons.configuration.beanutils.DefaultBeanFactory#createBean(Class, BeanDeclaration, Object) createBean()}</code>
+         * method to create a new instance of the configuration class.
+         *
+         * @param decl the bean declaration with initialization parameters for
+         * the configuration
+         * @return the new configuration object
+         * @throws Exception if an error occurs
+         */
+        public Configuration getConfiguration(ConfigurationDeclaration decl)
+                throws Exception
+        {
+            return (Configuration) createBean(getConfigurationClass(), decl,
+                    null);
+        }
+    }
+
+    /**
+     * <p>
+     * A specialized <code>BeanDeclaration</code> implementation that
+     * represents the declaration of a configuration source.
+     * </p>
+     * <p>
+     * Instances of this class are able to extract all information about a
+     * configuration source from the configuration definition file. The
+     * declaration of a configuration source is very similar to a bean
+     * declaration processed by <code>XMLBeanDeclaration</code>. There are
+     * very few differences, e.g. the two reserved attributes
+     * <code>optional</code> and <code>at</code> and the fact that a bean
+     * factory is never needed.
+     * </p>
+     */
+    protected static class ConfigurationDeclaration extends XMLBeanDeclaration
+    {
+        /** Stores a reference to the associated configuration factory. */
+        private XMLConfigurationFactory configurationFactory;
+
+        /**
+         * Creates a new instance of <code>ConfigurationDeclaration</code> and
+         * initializes it.
+         *
+         * @param factory the associated configuration factory
+         * @param config the configuration this declaration is based onto
+         */
+        public ConfigurationDeclaration(XMLConfigurationFactory factory,
+                HierarchicalConfiguration config)
+        {
+            super(config);
+            configurationFactory = factory;
+        }
+
+        /**
+         * Returns the associated configuration factory.
+         *
+         * @return the configuration factory
+         */
+        public XMLConfigurationFactory getConfigurationFactory()
+        {
+            return configurationFactory;
+        }
+
+        /**
+         * Returns the value of the <code>at</code> attribute.
+         *
+         * @return the value of the <code>at</code> attribute (can be <b>null</b>)
+         */
+        public String getAt()
+        {
+            return attributeValueStr(ATTR_AT);
+        }
+
+        /**
+         * Returns a flag whether this is an optional configuration.
+         *
+         * @return a flag if this declaration points to an optional
+         * configuration
+         */
+        public boolean isOptional()
+        {
+            Object value = attributeValue(ATTR_OPTIONAL);
+            try
+            {
+                return (value != null) ? PropertyConverter.toBoolean(value)
+                        .booleanValue() : false;
+            }
+            catch (ConversionException cex)
+            {
+                throw new ConfigurationRuntimeException(
+                        "optional attribute does not have a valid boolean value",
+                        cex);
+            }
+        }
+
+        /**
+         * Returns the name of the bean factory. For configuration source
+         * declarations always a reserved factory is used. This factory's name
+         * is returned by this implementation.
+         *
+         * @return the name of the bean factory
+         */
+        public String getBeanFactoryName()
+        {
+            return CONFIG_BEAN_FACTORY_NAME;
+        }
+
+        /**
+         * Returns the bean's class name. This implementation will always return
+         * <b>null</b>.
+         *
+         * @return the name of the bean's class
+         */
+        public String getBeanClassName()
+        {
+            return null;
+        }
+
+        /**
+         * Returns the value of the specified attribute. This can be useful for
+         * certain <code>ConfigurationProvider</code> implementations.
+         *
+         * @param attrName the attribute's name
+         * @return the attribute's value (or <b>null</b> if it does not exist)
+         */
+        public Object attributeValue(String attrName)
+        {
+            return super.attributeValue(attrName);
+        }
+
+        /**
+         * Returns the string value of the specified attribute.
+         *
+         * @param attrName the attribute's name
+         * @return the attribute's value (or <b>null</b> if it does not exist)
+         */
+        public String attributeValueStr(String attrName)
+        {
+            return super.attributeValueStr(attrName);
+        }
+
+        /**
+         * Checks whether the given node is reserved. This method will take
+         * further reserved attributes into account
+         *
+         * @param nd the node
+         * @return a flag whether this node is reserved
+         */
+        protected boolean isReservedNode(ConfigurationNode nd)
+        {
+            if (super.isReservedNode(nd))
+            {
+                return true;
+            }
+
+            return nd.isAttribute()
+                    && (ATTR_AT.equals(nd.getName()) || ATTR_OPTIONAL.equals(nd
+                            .getName()));
+        }
+    }
+
+    /**
+     * A specialized <code>BeanFactory</code> implementation that handles
+     * configuration declarations. This class will retrieve the correct
+     * configuration provider and delegate the task of creating the
+     * configuration to this object.
+     */
+    static class ConfigurationBeanFactory implements BeanFactory
+    {
+        /**
+         * Creates an instance of a bean class. This implementation expects that
+         * the passed in bean declaration is a declaration for a configuration.
+         * It will determine the responsible configuration provider and delegate
+         * the call to this instance.
+         *
+         * @param beanClass the bean class (will be ignored)
+         * @param data the declaration
+         * @param param an additional parameter (will be ignored)
+         * @return the newly created configuration
+         * @throws Exception if an error occurs
+         */
+        public Object createBean(Class beanClass, BeanDeclaration data,
+                Object param) throws Exception
+        {
+            ConfigurationDeclaration decl = (ConfigurationDeclaration) data;
+            String tagName = decl.getNode().getName();
+            ConfigurationProvider provider = decl.getConfigurationFactory()
+                    .providerForTag(tagName);
+            if (provider == null)
+            {
+                throw new ConfigurationRuntimeException(
+                        "No ConfigurationProvider registered for tag "
+                                + tagName);
+            }
+
+            return provider.getConfiguration(decl);
+        }
+
+        /**
+         * Returns the default class for this bean factory.
+         *
+         * @return the default class
+         */
+        public Class getDefaultBeanClass()
+        {
+            // Here some valid class must be returned, otherwise BeanHelper
+            // will complain that the bean's class cannot be determined
+            return Configuration.class;
+        }
+    }
+
+    /**
+     * A specialized provider implementation that deals with file based
+     * configurations. Ensures that the base path is correctly set and that the
+     * load() method gets called.
+     */
+    static class FileConfigurationProvider extends ConfigurationProvider
+    {
+        /**
+         * Creates a new instance of <code>FileConfigurationProvider</code>.
+         */
+        public FileConfigurationProvider()
+        {
+            super();
+        }
+
+        /**
+         * Creates a new instance of <code>FileConfigurationProvider</code>
+         * and sets the configuration class.
+         *
+         * @param configClass the class for the configurations to be created
+         */
+        public FileConfigurationProvider(Class configClass)
+        {
+            super(configClass);
+        }
+
+        /**
+         * Creates the configuration. After that <code>load()</code> will be
+         * called. If this configuration is marked as optional, exceptions will
+         * be ignored.
+         *
+         * @param decl the declaration
+         * @return the new configuration
+         * @throws Exception if an error occurs
+         */
+        public Configuration getConfiguration(ConfigurationDeclaration decl)
+                throws Exception
+        {
+            FileConfiguration config = (FileConfiguration) super
+                    .getConfiguration(decl);
+            try
+            {
+                config.load();
+            }
+            catch (ConfigurationException cex)
+            {
+                if (!decl.isOptional())
+                {
+                    throw cex;
+                }
+            }
+            return config;
+        }
+
+        /**
+         * Initializes the bean instance. Ensures that the file configuration's
+         * base path will be initialized with the base path of the factory so
+         * that relative path names can be correctly resolved.
+         *
+         * @param bean the bean to be initialized
+         * @param data the declaration
+         * @throws Exception if an error occurs
+         */
+        protected void initBeanInstance(Object bean, BeanDeclaration data)
+                throws Exception
+        {
+            FileConfiguration config = (FileConfiguration) bean;
+            config.setBasePath(((ConfigurationDeclaration) data)
+                    .getConfigurationFactory().getConfigurationBasePath());
+            super.initBeanInstance(bean, data);
+        }
+    }
+
+    /**
+     * A specialized configuration provider for file based configurations that
+     * can handle configuration sources whose concrete type depends on the
+     * extension of the file to be loaded. One example is the
+     * <code>properties</code> tag: if the file ends with ".xml" a
+     * XMLPropertiesConfiguration object must be created, otherwise a
+     * PropertiesConfiguration object.
+     */
+    static class FileExtensionConfigurationProvider extends
+            FileConfigurationProvider
+    {
+        /** Stores the class to be created when the file extension matches. */
+        private Class matchingClass;
+
+        /**
+         * Stores the class to be created when the file extension does not
+         * match.
+         */
+        private Class defaultClass;
+
+        /** Stores the file extension to be checked against. */
+        private String fileExtension;
+
+        /**
+         * Creates a new instance of
+         * <code>FileExtensionConfigurationProvider</code> and initializes it.
+         *
+         * @param matchingClass the class to be created when the file extension
+         * matches
+         * @param defaultClass the class to be created when the file extension
+         * does not match
+         * @param extension the file extension to be checked agains
+         */
+        public FileExtensionConfigurationProvider(Class matchingClass,
+                Class defaultClass, String extension)
+        {
+            this.matchingClass = matchingClass;
+            this.defaultClass = defaultClass;
+            fileExtension = extension;
+        }
+
+        /**
+         * Creates the configuration object. The class is determined by the file
+         * name's extension.
+         *
+         * @param beanClass the class
+         * @param data the bean declaration
+         * @return the new bean
+         * @throws Exception if an error occurs
+         */
+        protected Object createBeanInstance(Class beanClass,
+                BeanDeclaration data) throws Exception
+        {
+            String fileName = ((ConfigurationDeclaration) data)
+                    .attributeValueStr(ATTR_FILENAME);
+            if (fileName != null
+                    && fileName.toLowerCase().trim().endsWith(fileExtension))
+            {
+                return super.createBeanInstance(matchingClass, data);
+            }
+            else
+            {
+                return super.createBeanInstance(defaultClass, data);
+            }
+        }
+    }
+
+    static
+    {
+        // register the configuration bean factory
+        BeanHelper.registerBeanFactory(CONFIG_BEAN_FACTORY_NAME,
+                new ConfigurationBeanFactory());
+    }
+}

Propchange: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/XMLConfigurationFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/XMLConfigurationFactory.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/XMLConfigurationFactory.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain

Added: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestXMLConfigurationFactory.java
URL: http://svn.apache.org/viewcvs/jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestXMLConfigurationFactory.java?rev=381140&view=auto
==============================================================================
--- jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestXMLConfigurationFactory.java (added)
+++ jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestXMLConfigurationFactory.java Sun Feb 26 10:57:49 2006
@@ -0,0 +1,433 @@
+/*
+ * Copyright 2006 The Apache Software Foundation.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License")
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.commons.configuration;
+
+import java.io.File;
+import java.util.Collection;
+
+import org.apache.commons.configuration.beanutils.BeanHelper;
+import org.apache.commons.configuration.beanutils.XMLBeanDeclaration;
+import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
+import org.apache.commons.configuration.tree.DefaultConfigurationNode;
+
+import junit.framework.TestCase;
+
+/**
+ * Test class for XMLConfigurationFactory.
+ *
+ * @author Oliver Heger
+ * @version $Id$
+ */
+public class TestXMLConfigurationFactory extends TestCase
+{
+    /** Test configuration definition file. */
+    private static final File TEST_FILE = new File(
+            "conf/testDigesterConfiguration.xml");
+
+    private static final File ADDITIONAL_FILE = new File(
+            "conf/testDigesterConfiguration2.xml");
+
+    private static final File OPTIONAL_FILE = new File(
+            "conf/testDigesterOptionalConfiguration.xml");
+
+    private static final File OPTIONALEX_FILE = new File(
+            "conf/testDigesterOptionalConfigurationEx.xml");
+
+    private static final File MULTI_FILE = new File(
+            "conf/testDigesterConfiguration3.xml");
+
+    private static final File INIT_FILE = new File(
+            "conf/testComplexInitialization.xml");
+
+    /** Stores the object to be tested. */
+    XMLConfigurationFactory factory;
+
+    protected void setUp() throws Exception
+    {
+        super.setUp();
+        System
+                .setProperty("java.naming.factory.initial",
+                        "org.apache.commons.configuration.MockStaticMemoryInitialContextFactory");
+        factory = new XMLConfigurationFactory();
+    }
+
+    /**
+     * Tests the isReservedNode() method of ConfigurationDeclaration.
+     */
+    public void testConfigurationDeclarationIsReserved()
+    {
+        factory = new XMLConfigurationFactory();
+        XMLConfigurationFactory.ConfigurationDeclaration decl = new XMLConfigurationFactory.ConfigurationDeclaration(
+                factory, factory);
+        DefaultConfigurationNode nd = new DefaultConfigurationNode();
+        nd.setAttribute(true);
+        nd.setName("at");
+        assertTrue("Attribute at not recognized", decl.isReservedNode(nd));
+        nd.setName("optional");
+        assertTrue("Attribute optional not recognized", decl.isReservedNode(nd));
+        nd.setName(XMLBeanDeclaration.ATTR_BEAN_CLASS);
+        assertTrue("Inherited attribute not recognized", decl
+                .isReservedNode(nd));
+        nd.setName("different");
+        assertFalse("Wrong reserved attribute", decl.isReservedNode(nd));
+        nd.setAttribute(false);
+        nd.setName("at");
+        assertFalse("Node type not evaluated", decl.isReservedNode(nd));
+    }
+
+    /**
+     * Tests access to certain reserved attributes of a
+     * ConfigurationDeclaration.
+     */
+    public void testConfigurationDeclarationGetAttributes()
+    {
+        factory = new XMLConfigurationFactory();
+        factory.addProperty("/ xml/fileName", "test.xml");
+        XMLConfigurationFactory.ConfigurationDeclaration decl = new XMLConfigurationFactory.ConfigurationDeclaration(
+                factory, factory.configurationAt("xml"));
+        assertNull("Found an at attribute", decl.getAt());
+        assertFalse("Found an optional attribute", decl.isOptional());
+        factory.addProperty("/xml @at", "test1");
+        assertEquals("Wrong value of at attribute", "test1", decl.getAt());
+        factory.addProperty("/xml @optional", "true");
+        assertTrue("Wrong value of optional attribute", decl.isOptional());
+        factory.setProperty("/xml/@optional", "invalid value");
+        try
+        {
+            decl.isOptional();
+            fail("Invalid optional attribute was not detected!");
+        }
+        catch (ConfigurationRuntimeException crex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests adding a new configuration provider.
+     */
+    public void testAddConfigurationProvider()
+    {
+        XMLConfigurationFactory.ConfigurationProvider provider = new XMLConfigurationFactory.ConfigurationProvider();
+        assertNull("Provider already registered", factory
+                .providerForTag("test"));
+        factory.addConfigurationProvider("test", provider);
+        assertSame("Provider not registered", provider, factory
+                .providerForTag("test"));
+    }
+
+    /**
+     * Tries to register a null configuration provider. This should cause an
+     * exception.
+     */
+    public void testAddConfigurationProviderNull()
+    {
+        try
+        {
+            factory.addConfigurationProvider("test", null);
+            fail("Could register null provider");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tries to register a configuration provider for a null tag. This should
+     * cause an exception to be thrown.
+     */
+    public void testAddConfigurationProviderNullTag()
+    {
+        try
+        {
+            factory.addConfigurationProvider(null,
+                    new XMLConfigurationFactory.ConfigurationProvider());
+            fail("Could register provider for null tag!");
+        }
+        catch (IllegalArgumentException iex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests removing configuration providers.
+     */
+    public void testRemoveConfigurationProvider()
+    {
+        assertNull("Removing unknown provider", factory
+                .removeConfigurationProvider("test"));
+        assertNull("Removing provider for null tag", factory
+                .removeConfigurationProvider(null));
+        XMLConfigurationFactory.ConfigurationProvider provider = new XMLConfigurationFactory.ConfigurationProvider();
+        factory.addConfigurationProvider("test", provider);
+        assertSame("Failed to remove provider", provider, factory
+                .removeConfigurationProvider("test"));
+        assertNull("Provider still registered", factory.providerForTag("test"));
+    }
+
+    /**
+     * Tests creating a configuration object from a configuration declaration.
+     */
+    public void testConfigurationBeanFactoryCreateBean()
+    {
+        factory.addConfigurationProvider("test",
+                new XMLConfigurationFactory.ConfigurationProvider(
+                        PropertiesConfiguration.class));
+        factory.addProperty("/ test@throwExceptionOnMissing", "true");
+        XMLConfigurationFactory.ConfigurationDeclaration decl = new XMLConfigurationFactory.ConfigurationDeclaration(
+                factory, factory.configurationAt("test"));
+        PropertiesConfiguration conf = (PropertiesConfiguration) BeanHelper
+                .createBean(decl);
+        assertTrue("Property was not initialized", conf
+                .isThrowExceptionOnMissing());
+    }
+
+    /**
+     * Tests creating a configuration object from an unknown tag. This should
+     * cause an exception.
+     */
+    public void testConfigurationBeanFactoryCreateUnknownTag()
+    {
+        factory.addProperty("/ test@throwExceptionOnMissing", "true");
+        XMLConfigurationFactory.ConfigurationDeclaration decl = new XMLConfigurationFactory.ConfigurationDeclaration(
+                factory, factory.configurationAt("test"));
+        try
+        {
+            BeanHelper.createBean(decl);
+            fail("Could create configuration from unknown tag!");
+        }
+        catch (ConfigurationRuntimeException crex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests loading a simple configuration definition file.
+     */
+    public void testLoadConfiguration() throws ConfigurationException
+    {
+        factory.setFile(TEST_FILE);
+        checkConfiguration();
+    }
+
+    /**
+     * Tests the file constructor.
+     */
+    public void testLoadConfigurationFromFile() throws ConfigurationException
+    {
+        factory = new XMLConfigurationFactory(TEST_FILE);
+        checkConfiguration();
+    }
+
+    /**
+     * Tests the file name constructor.
+     */
+    public void testLoadConfigurationFromFileName()
+            throws ConfigurationException
+    {
+        factory = new XMLConfigurationFactory(TEST_FILE.getAbsolutePath());
+        checkConfiguration();
+    }
+
+    /**
+     * Tests the URL constructor.
+     */
+    public void testLoadConfigurationFromURL() throws Exception
+    {
+        factory = new XMLConfigurationFactory(TEST_FILE.toURL());
+        checkConfiguration();
+    }
+
+    /**
+     * Tests if the configuration was correctly created by the factory.
+     */
+    private void checkConfiguration() throws ConfigurationException
+    {
+        CompositeConfiguration compositeConfiguration = (CompositeConfiguration) factory
+                .getConfiguration();
+
+        assertEquals("Number of configurations", 4, compositeConfiguration
+                .getNumberOfConfigurations());
+        assertEquals(PropertiesConfiguration.class, compositeConfiguration
+                .getConfiguration(0).getClass());
+        assertEquals(XMLPropertiesConfiguration.class, compositeConfiguration
+                .getConfiguration(1).getClass());
+        assertEquals(XMLConfiguration.class, compositeConfiguration
+                .getConfiguration(2).getClass());
+
+        // check the first configuration
+        PropertiesConfiguration pc = (PropertiesConfiguration) compositeConfiguration
+                .getConfiguration(0);
+        assertNotNull("Make sure we have a fileName: " + pc.getFileName(), pc
+                .getFileName());
+
+        // check some properties
+        assertTrue("Make sure we have loaded our key", compositeConfiguration
+                .getBoolean("test.boolean"));
+        assertEquals("I'm complex!", compositeConfiguration
+                .getProperty("element2.subelement.subsubelement"));
+        assertEquals("property in the XMLPropertiesConfiguration", "value1",
+                compositeConfiguration.getProperty("key1"));
+    }
+
+    /**
+     * Tests loading a configuration definition file with an additional section.
+     */
+    public void testLoadAdditional() throws ConfigurationException
+    {
+        factory.setFile(ADDITIONAL_FILE);
+        CompositeConfiguration compositeConfiguration = (CompositeConfiguration) factory
+                .getConfiguration();
+        assertEquals("Verify how many configs", 3, compositeConfiguration
+                .getNumberOfConfigurations());
+
+        // Test if union was constructed correctly
+        Object prop = compositeConfiguration.getProperty("tables.table.name");
+        assertTrue(prop instanceof Collection);
+        assertEquals(3, ((Collection) prop).size());
+        assertEquals("users", compositeConfiguration
+                .getProperty("tables.table(0).name"));
+        assertEquals("documents", compositeConfiguration
+                .getProperty("tables.table(1).name"));
+        assertEquals("tasks", compositeConfiguration
+                .getProperty("tables.table(2).name"));
+
+        prop = compositeConfiguration
+                .getProperty("tables.table.fields.field.name");
+        assertTrue(prop instanceof Collection);
+        assertEquals(17, ((Collection) prop).size());
+
+        assertEquals("smtp.mydomain.org", compositeConfiguration
+                .getString("mail.host.smtp"));
+        assertEquals("pop3.mydomain.org", compositeConfiguration
+                .getString("mail.host.pop"));
+
+        // This was overriden
+        assertEquals("masterOfPost", compositeConfiguration
+                .getString("mail.account.user"));
+        assertEquals("topsecret", compositeConfiguration
+                .getString("mail.account.psswd"));
+
+        // This was overriden, too, but not in additional section
+        assertEquals("enhanced factory", compositeConfiguration
+                .getString("test.configuration"));
+    }
+
+    /**
+     * Tests loading a definition file that contains optional configurations.
+     */
+    public void testLoadOptional() throws Exception
+    {
+        factory.setURL(OPTIONAL_FILE.toURL());
+        Configuration config = factory.getConfiguration();
+        assertTrue(config.getBoolean("test.boolean"));
+        assertEquals("value", config.getProperty("element"));
+    }
+
+    /**
+     * Tests loading a definition file with optional and non optional
+     * configuration sources. One non optional does not exist, so this should
+     * cause an exception.
+     */
+    public void testLoadOptionalWithException()
+    {
+        factory.setFile(OPTIONALEX_FILE);
+        try
+        {
+            factory.getConfiguration();
+            fail("Non existing source did not cause an exception!");
+        }
+        catch (ConfigurationException cex)
+        {
+            // ok
+        }
+    }
+
+    /**
+     * Tests loading a definition file with multiple different sources.
+     */
+    public void testLoadDifferentSources() throws ConfigurationException
+    {
+        factory.setFile(MULTI_FILE);
+        Configuration config = factory.getConfiguration();
+        assertFalse(config.isEmpty());
+        assertTrue(config instanceof CompositeConfiguration);
+        CompositeConfiguration cc = (CompositeConfiguration) config;
+        assertTrue(cc.getNumberOfConfigurations() == 2);
+
+        assertNotNull(config
+                .getProperty("tables.table(0).fields.field(2).name"));
+        assertNotNull(config.getProperty("element2.subelement.subsubelement"));
+        assertEquals("value", config.getProperty("element3"));
+        assertEquals("foo", config.getProperty("element3[@name]"));
+        assertNotNull(config.getProperty("mail.account.user"));
+
+        // test JNDIConfiguration
+        assertNotNull(config.getProperty("test.onlyinjndi"));
+        assertTrue(config.getBoolean("test.onlyinjndi"));
+
+        Configuration subset = config.subset("test");
+        assertNotNull(subset.getProperty("onlyinjndi"));
+        assertTrue(subset.getBoolean("onlyinjndi"));
+
+        // test SystemConfiguration
+        assertNotNull(config.getProperty("java.version"));
+        assertEquals(System.getProperty("java.version"), config
+                .getString("java.version"));
+    }
+
+    /**
+     * Tests if the base path is correctly evaluated.
+     */
+    public void testSetConfigurationBasePath() throws ConfigurationException
+    {
+        factory.addProperty("/ properties@fileName", "test.properties");
+        File deepDir = new File("conf/config/deep");
+        factory.setConfigurationBasePath(deepDir.getAbsolutePath());
+
+        Configuration config = factory.getConfiguration(false);
+        assertEquals("Wrong property value", "somevalue", config
+                .getString("somekey"));
+    }
+
+    /**
+     * Tests reading a configuration definition file that contains complex
+     * initialization of properties of the declared configuration sources.
+     */
+    public void testComplexInitialization() throws ConfigurationException
+    {
+        factory.setFile(INIT_FILE);
+        CompositeConfiguration cc = (CompositeConfiguration) factory
+                .getConfiguration();
+
+        PropertiesConfiguration c1 = (PropertiesConfiguration) cc
+                .getConfiguration(0);
+        assertTrue(
+                "Reloading strategy was not set",
+                c1.getReloadingStrategy() instanceof FileChangedReloadingStrategy);
+        assertEquals("Refresh delay was not set", 10000,
+                ((FileChangedReloadingStrategy) c1.getReloadingStrategy())
+                        .getRefreshDelay());
+
+        assertEquals("Property not found", "I'm complex!", cc
+                .getString("element2/subelement/subsubelement"));
+        assertEquals("List index not found", "two", cc
+                .getString("list[0]/item[1]"));
+    }
+}

Propchange: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestXMLConfigurationFactory.java
------------------------------------------------------------------------------
    svn:eol-style = native

Propchange: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestXMLConfigurationFactory.java
------------------------------------------------------------------------------
    svn:keywords = Date Author Id Revision HeadURL

Propchange: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestXMLConfigurationFactory.java
------------------------------------------------------------------------------
    svn:mime-type = text/plain



---------------------------------------------------------------------
To unsubscribe, e-mail: commons-dev-unsubscribe@jakarta.apache.org
For additional commands, e-mail: commons-dev-help@jakarta.apache.org