You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@commons.apache.org by ep...@apache.org on 2003/11/26 12:03:15 UTC

cvs commit: jakarta-commons-sandbox/configuration/src/test/org/apache/commons/configuration TestConfigurationXMLDocument.java

epugh       2003/11/26 03:03:15

  Modified:    configuration/src/java/org/apache/commons/configuration
                        CompositeConfiguration.java
                        ConfigurationFactory.java
               configuration/xdocs examples.xml overview.xml
               configuration/conf testDigesterConfiguration2.xml
  Added:       configuration/src/java/org/apache/commons/configuration
                        ConfigurationXMLDocument.java
               configuration/conf testConfigurationXMLDocument.xml
                        testDigesterCreateObject.xml
               configuration/src/test/org/apache/commons/configuration
                        TestConfigurationXMLDocument.java
  Log:
  - new class ConfigurationXMLDocument including test case
  - Removed className attribute in configuration definition file for
  ConfigurationFactory
  - New element <hierarchicalDom4j> in configuration definition file
  - Updates and enhancements of documentation
  
  Revision  Changes    Path
  1.17      +6 -2      jakarta-commons-sandbox/configuration/src/java/org/apache/commons/configuration/CompositeConfiguration.java
  
  Index: CompositeConfiguration.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons-sandbox/configuration/src/java/org/apache/commons/configuration/CompositeConfiguration.java,v
  retrieving revision 1.16
  retrieving revision 1.17
  diff -u -r1.16 -r1.17
  --- CompositeConfiguration.java	12 Oct 2003 09:32:30 -0000	1.16
  +++ CompositeConfiguration.java	26 Nov 2003 11:03:14 -0000	1.17
  @@ -272,6 +272,8 @@
       {
           CompositeConfiguration subsetCompositeConfiguration =
               new CompositeConfiguration();
  +        Configuration subConf = null;
  +        int count = 0;
           for (ListIterator i = configList.listIterator(); i.hasNext();)
           {
               Configuration config = (Configuration) i.next();
  @@ -279,9 +281,11 @@
               if (subset != null)
               {
                   subsetCompositeConfiguration.addConfiguration(subset);
  +                subConf = subset;
  +                count++;
               }
           }
  -        return subsetCompositeConfiguration;
  +        return (count == 1) ? subConf : subsetCompositeConfiguration;
       }
       /**
       * Get a float associated with the given configuration key.
  
  
  
  1.11      +11 -18    jakarta-commons-sandbox/configuration/src/java/org/apache/commons/configuration/ConfigurationFactory.java
  
  Index: ConfigurationFactory.java
  ===================================================================
  RCS file: /home/cvs/jakarta-commons-sandbox/configuration/src/java/org/apache/commons/configuration/ConfigurationFactory.java,v
  retrieving revision 1.10
  retrieving revision 1.11
  diff -u -r1.10 -r1.11
  --- ConfigurationFactory.java	11 Nov 2003 15:02:07 -0000	1.10
  +++ ConfigurationFactory.java	26 Nov 2003 11:03:14 -0000	1.11
  @@ -291,6 +291,12 @@
               additional);
           setupDigesterInstance(
               digester,
  +            matchString + "hierarchicalDom4j",
  +            new BasePathConfigurationFactory(HierarchicalDOM4JConfiguration.class),
  +            METH_LOAD,
  +            additional);
  +        setupDigesterInstance(
  +            digester,
               matchString + "jndi",
               new JNDIConfigurationFactory(),
               null,
  @@ -412,17 +418,13 @@
   
       /**
        * A base class for digester factory classes. This base class maintains
  -     * a default class for the objects to be created. It also supports a
  -     * <code>className</code> attribute for specifying a different class.
  +     * a default class for the objects to be created.
        * There will be sub classes for specific configuration implementations.
        */
       public class DigesterConfigurationFactory
           extends AbstractObjectCreationFactory
           implements ObjectCreationFactory
       {
  -        /** Constant for the className attribute.*/
  -        protected static final String ATTR_CLASSNAME = "className";
  -
           /** Actual class to use. */
           private Class clazz;
   
  @@ -436,23 +438,14 @@
           }
   
           /**
  -         * Creates an instance of the specified class. If the passed in
  -         * attributes contain a <code>className</code> attribute, the value of
  -         * this attribute is interpreted as the full qualified class name of
  -         * the class to be instantiated. Otherwise the default class is used.
  -         * @param attribs the attributes
  +         * Creates an instance of the specified class.
  +         * @param attribs the attributes (ignored)
            * @return the new object
            * @exception Exception if object creation fails
            */
           public Object createObject(Attributes attribs) throws Exception
           {
  -            Class actCls;
  -            
  -            int idx = attribs.getIndex(ATTR_CLASSNAME);
  -            actCls = (idx < 0) ? clazz :
  -            Class.forName(attribs.getValue(idx));
  -
  -            return actCls.newInstance();
  +            return clazz.newInstance();
           }
       }
   
  
  
  
  1.1                  jakarta-commons-sandbox/configuration/src/java/org/apache/commons/configuration/ConfigurationXMLDocument.java
  
  Index: ConfigurationXMLDocument.java
  ===================================================================
  package org.apache.commons.configuration;
  
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2002-2003 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowledgement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowledgement may appear in the software itself,
   *    if and wherever such third-party acknowledgements normally appear.
   *
   * 4. The names "The Jakarta Project", "Commons", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Software Foundation.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  import java.io.IOException;
  import java.io.Writer;
  import java.util.NoSuchElementException;
  
  import org.apache.commons.digester.Digester;
  import org.dom4j.Document;
  import org.dom4j.DocumentException;
  import org.dom4j.io.DOMWriter;
  import org.dom4j.io.OutputFormat;
  import org.dom4j.io.SAXReader;
  import org.dom4j.io.XMLWriter;
  import org.xml.sax.SAXException;
  
  /**
   * <p>A helper class that supports XML-like processing for configuration
   * objects.</p>
   * <p>This class provides a set of methods that all have something to do with
   * treating a <code>Configuration</code> object as a XML document. So a
   * configuration can be transformed into a <code>Document</code> (either
   * dom4j or w3c), saved as an XML file or passed to Digester.</p>
   * <p><strong>Implementation note:</strong> This class is not thread safe.</p>
   *
   * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
   * @version $Id: ConfigurationXMLDocument.java,v 1.1 2003/11/26 11:03:14 epugh Exp $
   */
  public class ConfigurationXMLDocument
  {
      /** Constant for the class element.*/
      protected static final String ELEM_CLASS = "config/class";
  
      /** Constant for the property element.*/
      protected static final String ELEM_PROPERTY = "config/class/property";
  
      /** Constant for the name attribute.*/
      protected static final String ATTR_NAME = "name";
  
      /** Constant for the value attribute.*/
      protected static final String ATTR_VALUE = "value";
  
      /** Stores the configuration object this object operates on.*/
      private Configuration configuration;
  
      /**
       * Creates a new instance of <code>ConfigurationXMLDocument</code>
       * and sets the configuration object to be processed.
       * @param config the configuration object
       */
      public ConfigurationXMLDocument(Configuration config)
      {
          setConfiguration(config);
      }
  
      /**
       * Returns the <code>Configuration</code> object for this document.
       * @return the <code>Configuration</code> object
       */
      public Configuration getConfiguration()
      {
          return configuration;
      }
  
      /**
       * Sets the <code>Configuration</code> object this document operates on.
       * @param configuration the <code>Configuration</code> object
       */
      public void setConfiguration(Configuration configuration)
      {
          this.configuration = configuration;
      }
  
      /**
       * Returns a <code>XMLReader</code> object for the specified configuration
       * object. This reader can then be used to perform XML-like processing on
       * the configuration.
       * @param config the configuration object
       * @return a XMLReader for this configuration
       */
      public static ConfigurationXMLReader createXMLReader(Configuration config)
      {
          if (config instanceof HierarchicalConfiguration)
          {
              return new HierarchicalConfigurationXMLReader(
                  (HierarchicalConfiguration) config);
          } /* if */
          else
          {
              return new BaseConfigurationXMLReader(config);
          } /* else */
      }
  
      /**
       * Returns a <code>XMLReader</code> object for the actual configuration
       * object.
       * @return a XMLReader for the actual configuration
       */
      public ConfigurationXMLReader createXMLReader()
      {
          return createXMLReader((String) null);
      }
  
      /**
       * Returns a <code>ConfigurationXMLReader</code> object for the subset
       * configuration specified by the given prefix. If no properties are found
       * under this prefix, a <code>NoSuchElementException</code>
       * exception will be thrown.
       * @param prefix the prefix of the configuration keys that belong to the
       * subset; can be <b>null</b>, then the whole configuration is affected
       * @return a XMLReader for the specified subset configuration
       */
      public ConfigurationXMLReader createXMLReader(String prefix)
      {
          return createXMLReader(configForKey(prefix));
      }
  
      /**
       * Transforms the wrapped configuration into a dom4j document.
       * @param prefix a prefix for the keys to process; can be <b>null</b>,
       * then all keys in the configuration will be added to the document
       * @param rootName the name of the root element in the document; can be
       * <b>null</b>, then a default name will be used
       * @return the document
       * @throws DocumentException if an error occurs
       */
      public Document getDocument(String prefix, String rootName)
          throws DocumentException
      {
          ConfigurationXMLReader xmlReader = createXMLReader(prefix);
          if (rootName != null)
          {
              xmlReader.setRootName(rootName);
          } /* if */
  
          SAXReader reader = new SAXReader(xmlReader);
          return reader.read(getClass().getName());
      }
  
      /**
       * Transforms the wrapped configuration into a dom4j document. The root
       * element will be given a default name.
       * @param prefix a prefix for the keys to process; can be <b>null</b>,
       * then all keys in the configuration will be added to the document
       * @return the document
       * @throws DocumentException if an error occurs
       */
      public Document getDocument(String prefix) throws DocumentException
      {
          return getDocument(prefix, null);
      }
  
      /**
       * Transforms the wrapped configuration into a dom4j document. The root
       * element will be given a default name.
       * @return the document
       * @throws DocumentException if an error occurs
       */
      public Document getDocument() throws DocumentException
      {
          return getDocument(null, null);
      }
  
      /**
       * Transforms the wrapped configuration into a w3c document.
       * @param prefix a prefix for the keys to process; can be <b>null</b>,
       * then all keys in the configuration will be added to the document
       * @param rootName the name of the root element in the document; can be
       * <b>null</b>, then a default name will be used
       * @return the document
       * @throws DocumentException if an error occurs
       */
      public org.w3c.dom.Document getW3cDocument(String prefix, String rootName)
          throws DocumentException
      {
          return toW3cDocument(getDocument(prefix, rootName));
      }
  
      /**
       * Transforms the wrapped configuration into a w3c document. The root
       * element will be given a default name.
       * @param prefix a prefix for the keys to process; can be <b>null</b>,
       * then all keys in the configuration will be added to the document
       * @return the document
       * @throws DocumentException if an error occurs
       */
      public org.w3c.dom.Document getW3cDocument(String prefix)
          throws DocumentException
      {
          return getW3cDocument(prefix, null);
      }
  
      /**
       * Transforms the wrapped configuration into a w3c document. The root
       * element will be given a default name.
       * @return the document
       * @throws DocumentException if an error occurs
       */
      public org.w3c.dom.Document getW3cDocument() throws DocumentException
      {
          return getW3cDocument(null, null);
      }
  
      /**
       * Converts a dom4j document into a w3c document.
       * @param doc the dom4j document
       * @return the w3c document
       * @throws DocumentException if an error occurs
       */
      static org.w3c.dom.Document toW3cDocument(Document doc)
          throws DocumentException
      {
          return new DOMWriter().write(doc);
      }
  
      /**
       * Helper method for constructing a subset if necessary. Depending on
       * the passed in key this method either returns the wrapped configuration
       * or the specified subset of it.
       * @param key the key
       * @return the configuration for that key
       */
      private Configuration configForKey(String key)
      {
          Configuration conf = (key == null)
              ? getConfiguration()
              : getConfiguration().subset(key);
              
          // undefined?
          if(conf == null || (conf instanceof CompositeConfiguration
          && ((CompositeConfiguration) conf).getNumberOfConfigurations() < 2))
          {
              throw new NoSuchElementException("No subset with key " + key);
          }  /* if */
          
          return conf;
      }
  
      /**
       * <p>Creates and initializes an object specified in the configuration
       * using Digester.</p>
       * <p>This method first constructs a subset configuration with the keys
       * starting with the given prefix. It then transforms this subset into a
       * XML document and let that be processed by Digester. The result of this
       * processing is returned.</p>
       * <p>The method is intended to be used for creating simple objects that
       * are specified somewhere in the configuration in a standard way. The
       * following fragment shows how a configuration file must look like to be
       * understood by the default Digester rule set used by this method:</p>
       * <p><pre>
       * ...
       *   &lt;class name="mypackage.MyClass"/&gt;
       *   &lt;args&gt;
       *     &lt;property name="myFirstProperty" value="myFirstValue"/&gt;
       *     &lt;property name="MySecondProperty" value="mySecondValue"/&gt;
       *     ...
       *   &lt;/args&gt;
       * ...
       * </pre></p>
       * @param prefix the prefix of the keys that are passed to Digester; can
       * be <b>null</b>, then the whole configuration will be processed
       * @return the result of the Digester processing
       * @throws IOException if an IOException occurs
       * @throws SAXException if a SAXException occurs
       */
      public Object callDigester(String prefix) throws IOException, SAXException
      {
          Digester digester = getDefaultDigester(prefix);
          return digester.parse(getClass().getName());
      }
  
      /**
       * Returns a default Digester instance. This instance is used for the
       * simple object creation feature.
       * @param prefix the prefix of the keys to be processed; can be
       * <b>null</b>, then the whole configuration is meant
       * @return the default Digester instance
       */
      protected Digester getDefaultDigester(String prefix)
      {
          Digester digester = createDefaultDigester(prefix);
          setupDefaultDigester(digester);
  
          return digester;
      }
  
      /**
       * Creates the default Digester instance for the given prefix. This method
       * is called by <code>getDefaultDigester()</code>.
       * @param prefix the prefix of the keys to be processed; can be
       * <b>null</b>, then the whole configuration is meant
       * @return the default Digester instance
       */
      protected Digester createDefaultDigester(String prefix)
      {
          return new Digester(createXMLReader(prefix));
      }
  
      /**
       * Initializes the default digester instance used for simple object
       * creation. Here all needed properties and rules can be set. This base
       * implementation sets default rules for object creation as explained in
       * the comment for the <code>callDigester()</code> methods.
       * @param digester the digester instance to be initialized
       */
      protected void setupDefaultDigester(Digester digester)
      {
          digester.addObjectCreate(ELEM_CLASS, ATTR_NAME, Object.class);
          digester.addSetProperty(ELEM_PROPERTY, ATTR_NAME, ATTR_VALUE);
      }
  
      /**
       * Writes a configuration (or parts of it) to the given writer.
       * @param out the output writer
       * @param prefix the prefix of the subset to write; if <b>null</b>, the
       * whole configuration is written
       * @param root the name of the root element of the resulting document;
       * <b>null</b> for a default name
       * @param pretty flag for the pretty print mode
       * @throws IOException if an IO error occurs
       * @throws DocumentException if there is an error during processing
       */
      public void write(Writer out, String prefix, String root, boolean pretty)
          throws IOException, DocumentException
      {
          OutputFormat format =
              (pretty)
                  ? OutputFormat.createPrettyPrint()
                  : OutputFormat.createCompactFormat();
  
          XMLWriter writer = new XMLWriter(out, format);
          writer.write(getDocument(prefix, root));
      }
  
      /**
       * Writes a configuration (or parts of it) to the given writer. 
       * This overloaded version always uses pretty print mode.
       * @param out the output writer
       * @param prefix the prefix of the subset to write; if <b>null</b>, the
       * whole configuration is written
       * @param root the name of the root element of the resulting document;
       * <b>null</b> for a default name
       * @throws IOException if an IO error occurs
       * @throws DocumentException if there is an error during processing
       */
      public void write(Writer out, String prefix, String root)
          throws IOException, DocumentException
      {
          write(out, prefix, root, true);
      }
  
      /**
       * Writes a configuration (or parts of it) to the given writer.
       * The resulting document's root element will be given a default name.
       * @param out the output writer
       * @param prefix the prefix of the subset to write; if <b>null</b>, the
       * whole configuration is written
       * @param pretty flag for the pretty print mode
       * @throws IOException if an IO error occurs
       * @throws DocumentException if there is an error during processing
       */
      public void write(Writer out, String prefix, boolean pretty)
          throws IOException, DocumentException
      {
          write(out, prefix, null, pretty);
      }
  
      /**
       * Writes a configuration (or parts of it) to the given writer.
       * The resulting document's root element will be given a default name.
       * This overloaded version always uses pretty print mode.
       * @param out the output writer
       * @param prefix the prefix of the subset to write; if <b>null</b>, the
       * whole configuration is written
       * @throws IOException if an IO error occurs
       * @throws DocumentException if there is an error during processing
       */
      public void write(Writer out, String prefix)
          throws IOException, DocumentException
      {
          write(out, prefix, true);
      }
  
      /**
       * Writes the wrapped configuration to the given writer.
       * The resulting document's root element will be given a default name.
       * @param out the output writer
       * @param pretty flag for the pretty print mode
       * @throws IOException if an IO error occurs
       * @throws DocumentException if there is an error during processing
       */
      public void write(Writer out, boolean pretty)
          throws IOException, DocumentException
      {
          write(out, null, null, pretty);
      }
  
      /**
       * Writes the wrapped configuration to the given writer.
       * The resulting document's root element will be given a default name.
       * This overloaded version always uses pretty print mode.
       * @param out the output writer
       * @throws IOException if an IO error occurs
       * @throws DocumentException if there is an error during processing
       */
      public void write(Writer out) throws IOException, DocumentException
      {
          write(out, true);
      }
  }
  
  
  
  1.2       +544 -9    jakarta-commons-sandbox/configuration/xdocs/examples.xml
  
  Index: examples.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-commons-sandbox/configuration/xdocs/examples.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- examples.xml	15 Nov 2003 15:06:01 -0000	1.1
  +++ examples.xml	26 Nov 2003 11:03:14 -0000	1.2
  @@ -428,14 +428,13 @@
   <configuration>
     <properties fileName="usergui.properties"/>
     <dom4j fileName="gui.xml"/>
  -  <dom4j className="org.apache.commons.configuration.HierarchicalDOM4JConfiguration"
  -   fileName="tables.xml"/>
  +  <hierarchicalDom4j fileName="tables.xml"/>
   </configuration>
   ]]>
   			</source>
   			<p>
  -				Note the additional <code>className</code> attribute of the last
  -				<code>dom4j</code> element. This attribute tells the configuration
  +				Note that one <code>dom4j</code> element was replaced by a
  +				<code>hierarchicalDom4j</code> element. This element tells the configuration
   				factory that not the default class for processing XML documents
   				should be used, but the class <code>HierarchicalDOM4JConfiguration</code>.
   				As the name implies this class is capable of saving the
  @@ -507,7 +506,7 @@
   				and do not have a complex structure the default XML configuration
   				class is suitable. If documents are more complex and their structure
   				is important, the hierarchy aware class should be used, which is
  -				enabled by an additional <code>className</code> attribute as
  +				selected by the <code>hierarchicalDom4j</code> element as
   				shown in the example configuration definition file above.
   			</p>
   		</subsection>
  @@ -605,10 +604,8 @@
     </override>
     
     <additional>
  -    <dom4j className="org.apache.commons.configuration.HierarchicalDOM4JConfiguration"
  -     fileName="tables.xml"/>
  -    <dom4j className="org.apache.commons.configuration.HierarchicalDOM4JConfiguration"
  -     fileName="tasktables.xml" at="tables"/>
  +    <hierarchicalDom4j fileName="tables.xml"/>
  +    <hierarchicalDom4j fileName="tasktables.xml" at="tables"/>
     </additional>
   </configuration>
   ]]>
  @@ -661,6 +658,544 @@
   			higher priority (otherwise they could not override the values of other
   			sources).
   		</p>
  +	</section>
  +	
  +	<section name="XML processing">
  +		<p>
  +			We have now loaded some configuration sources and accessed some of the
  +			properties. What else can we do? One additional feature provided by
  +			Configuration is its support for XML-like processing of <code>Configuration</code>
  +			objects that is implemented by the <code>ConfigurationXMLDocument</code>
  +			class. The XML format for data exchange has become very popular, so there
  +			are many use cases why you may want a XML-like view for your configuration.
  +			This section shows how to make use of these features.
  +		</p>
  +		<subsection name="Basic XML access">
  +			<p>
  +				When it comes to XML processing of configuration sources the
  +				<code>ConfigurationXMLDocument</code> class is usually involved.
  +				When an instance of this class is created a <code>Configuration</code>
  +				object is passed to the constructor. All operations executed on the
  +				instance are then related to this configuration or a subset of it.
  +			</p>
  +			<p>
  +				The most fundamental operation for treating a configuration source
  +				as a XML document is to request a <code>ConfigurationXMLReader</code>
  +				object from the <code>ConfigurationXMLDocument</code> instance.
  +				The object returned by this method implements the
  +				<code>org.xml.sax.XMLReader</code> interface and thus is a SAX 2
  +				conform XML parser. This parser can then be passed to components
  +				that can deal with SAX events. The following snippet shows how such
  +				a SAX parser can be obtained:
  +			</p>
  +			<source>
  +<![CDATA[
  +ConfigurationXMLDocument configDoc = new ConfigurationXMLDocument(config);
  +XMLReader reader = configDoc.createXMLReader();
  +
  +// or:
  +// XMLReader reader = configDoc.createXMLReader("tables");
  +// Now do something with the reader
  +...
  +]]>
  +			</source>
  +			<p>
  +				As this example shows it is either possible to obtain a reader object
  +				for the whole configuration or for a subset of it. The obtained object
  +				can now be used where ever a SAX parser is supported. There is
  +				only one thing to mention: The <code>XMLReader</code> returned
  +				by <code>createXMLReader()</code> has been initialized with the
  +				configuration source (or a subset of it) stored in the
  +				<code>ConfigurationXMLDocument</code> instance. If now one of the
  +				<code>parse()</code> methods is called, the passed in argument,
  +				which usually specifies the input source to parse, is ignored. This
  +				information is unnecessary because all parsing is always done on
  +				the associated <code>Configuration</code> object. Later in this section
  +				this fact should become clearer.
  +			</p>
  +		</subsection>
  +		<subsection name="Working with documents">
  +			<p>
  +				SAX may be the most efficient, but it is surely not the most convenient
  +				way of XML processing. If a document with a complex structure is to
  +				be navigated, a DOM based approach is usually more suitable.
  +			</p>
  +			<p>
  +				<code>ConfigurationXMLDocument</code> provides a couple of
  +				overloaded <code>getDocument()</code> methods that return a dom4j
  +				<code>Document</code> object. The arguments that can be passed to
  +				these methods allow to select a subset of the configuration source and
  +				to specify the name of the resulting document's root element. The following
  +				code fragment provides an example:
  +			</p>
  +			<source>
  +<![CDATA[
  +ConfigurationXMLDocument configDoc = new ConfigurationXMLDocument(config);
  +Document doc = configDoc.getDocument("tables", "database");
  +...
  +]]>
  +			</source>
  +			<p>
  +				The <code>Document</code> returned here contains all data below the
  +				<em>tables</em> section, i.e. it will have the root element <em>database</em>
  +				with three <em>table</em> elements as children. In addition to the
  +				<code>getDocument()</code> methods there is also a set of
  +				<code>getW3cDocument()</code> methods. These methods act in an
  +				analogous way, but return a <code>org.w3c.dom.Document</code> object
  +				rather than a dom4j document.
  +			</p>
  +			<p>
  +				Once a DOM document has been obtained the whole world of DOM processing
  +				is open. Especially dom4j allows for powerful and convenient manipulations,
  +				e.g. the document could be transformed using a stylesheet or written to
  +				a file. If a configuration source or parts of it should simply be saved as
  +				a XML document, there is an even easier way: the <code>write()</code>
  +				methods of <code>ConfigurationXMLDocument</code>. Let's assume our
  +				example application wants to send its database table definitions to an
  +				external tool, maybe to initialize the database schema. The following code
  +				shows how an XML file with this information could be written:
  +			</p>
  +			<source>
  +<![CDATA[
  +ConfigurationXMLDocument configDoc = new ConfigurationXMLDocument(config);
  +Writer out = null;
  +try
  +{
  +    out = new BufferedWriter(new FileWriter("tabledef.xml"));
  +    configDoc.write(out, "tables", "database");
  +}
  +finally
  +{
  +    out.close();
  +}
  +]]>
  +			</source>
  +		</subsection>
  +		<subsection name="Calling Digester">
  +			<p>
  +				<em>Commons Digester</em> is another Apache Jakarta project that
  +				implements a powerful engine for processing XML documents. In this section
  +				we will make use of Digester to transform the table definitions into a
  +				corresponding object model. For this tutorial the interesting part is
  +				the stuff related to the communication with Digester; more information
  +				about Digester and its features can be found at the homepage of the
  +				Digester project.
  +			</p>
  +			<p>
  +				We start with with the definition of a set of beans that will later store
  +				information about the database tables and their fields. To keep this
  +				example short these are very simple classes with hardly more than
  +				a couple of getter and setter methods. As you can see there are
  +				corresponding properties for all elements that can appear in the
  +				table configuration.
  +			</p>
  +			<source>
  +<![CDATA[
  +public class TestConfigurationXMLDocument
  +{
  +    /** Stores the tables.*/
  +    private ArrayList tables;
  +    
  +    /**
  +     * Adds a new table object. Called by Digester.
  +     * @param table the new table
  +     */
  +    public void addTable(Table table)
  +    {
  +        tables.add(table);
  +    }
  +
  +    /**
  +     * A simple bean class for storing information about a table field.
  +     * Used for the Digester test.
  +     */
  +    public static class TableField
  +    {
  +        private String name;
  +        private String type;
  +        
  +        public String getName()
  +        {
  +            return name;
  +        }
  +
  +        public String getType()
  +        {
  +            return type;
  +        }
  +
  +        public void setName(String string)
  +        {
  +            name = string;
  +        }
  +
  +        public void setType(String string)
  +        {
  +            type = string;
  +        }
  +    }
  +    
  +    /**
  +     * A simple bean class for storing information about a database table.
  +     * Used for the Digester test.
  +     */
  +    public static class Table
  +    {
  +        /** Stores the list of fields.*/
  +        private ArrayList fields;
  +        
  +        /** Stores the table name.*/
  +        private String name;
  +        
  +        /** Stores the table type.*/
  +        private String tableType;
  +        
  +        public Table()
  +        {
  +            fields = new ArrayList();
  +        }
  +        
  +        public String getName()
  +        {
  +            return name;
  +        }
  +
  +        public String getTableType()
  +        {
  +            return tableType;
  +        }
  +
  +        public void setName(String string)
  +        {
  +            name = string;
  +        }
  +
  +        public void setTableType(String string)
  +        {
  +            tableType = string;
  +        }
  +        
  +        /**
  +         * Adds a field.
  +         * @param field the new field
  +         */
  +        public void addField(TableField field)
  +        {
  +            fields.add(field);
  +        }
  +        
  +        /**
  +         * Returns the field with the given index.
  +         * @param idx the index
  +         * @return the field with this index
  +         */
  +        public TableField getField(int idx)
  +        {
  +            return (TableField) fields.get(idx);
  +        }
  +        
  +        /**
  +         * Returns the number of fields.
  +         * @return the number of fields
  +         */
  +        public int size()
  +        {
  +            return fields.size();
  +        }
  +    }
  +}
  +]]>
  +			</source>
  +			<p>
  +				While <code>TableField</code> is almost trivial, <code>Table</code>
  +				provides the ability of storing a number of field objects in an internal
  +				collection. The test class also defines a collection that will later
  +				store the <code>Table</code> objects created from the configuration.
  +			</p>
  +			<p>
  +				To pass the table configuration to Digester we have to
  +				<ol>
  +					<li>
  +						Ask our <code>ConfigurationXMLDocument</code> instance for
  +						a <code>XMLReader</code> for the <em>tables</em> section.
  +					</li>
  +					<li>
  +						Construct a new Digester object and pass this XML reader to
  +						the constructor.
  +					</li>
  +					<li>
  +						Initialize the Digester instance with the necessary processing rules
  +						to create a corresponding object hierarchy for the table definitions.
  +					</li>
  +					<li>
  +						Call the Digester's <code>parse()</code> method to initiate
  +						processing.
  +					</li>
  +				</ol>
  +				The following listing shows the implementation of these steps:
  +			</p>
  +			<source>
  +<![CDATA[
  +    public void testCallDigesterComplex() throws Exception
  +    {
  +        Digester digester = 
  +            new Digester(configDoc.createXMLReader("tables"));
  +        digester.addObjectCreate("config/table", Table.class);
  +        digester.addSetProperties("config/table");
  +        digester.addCallMethod("config/table/name", "setName", 0);
  +        digester.addObjectCreate("config/table/fields/field", TableField.class);
  +        digester.addCallMethod("config/table/fields/field/name",
  +            "setName", 0);
  +        digester.addCallMethod("config/table/fields/field/type",
  +            "setType", 0);
  +        digester.addSetNext("config/table/fields/field",
  +            "addField", TableField.class.getName());
  +        digester.addSetNext("config/table", "addTable", Table.class.getName());
  +        
  +        digester.push(this);
  +        digester.parse("config");
  +    }
  +]]>
  +			</source>
  +			<p>
  +				At the beginning of this listing <code>createXMLReader()</code>
  +				is called on the <code>ConfigurationXMLDocument</code> instance
  +				to obtain a SAX parser for the configuration subset with the table
  +				definitions. When the Digester object is created this parser is passed
  +				to its constructor.
  +			</p>
  +			<p>
  +				The major part of the fragment deals with setting up the necessary
  +				Digester rules. Details for that can be found in the Digester documentation.
  +				In short we define rules to create <code>Table</code> and
  +				<code>TableField</code> objects when the corresponding XML elements
  +				are detected. The newly created objects are initialized with the
  +				properties defined in the XML code. Then it is ensured that a new
  +				<code>TableField</code> object is added to a <code>Table</code>
  +				and that for each new <code>Table</code> object the
  +				<code>addTable()</code> method of the test class is invoked.
  +				This all conforms to the default usage pattern of Digester, only two
  +				points should be noticed:
  +			</p>
  +			<p>
  +				<ul>
  +					<li>
  +						The match strings for all Digester rules have the prefix
  +						<em>config/table</em>. The reason for this is that the XML document
  +						that is generated by the SAX parser has a corresponding structure.
  +						Remember when we called <code>createXMLReader()</code>, we
  +						specified the string <em>tables</em> as argument. This means that
  +						the resulting document will have all the content that can be found
  +						in the configuration under the key <em>tables</em>, which happens
  +						to be three <em>table</em> elements with their corresponding
  +						children. The root element of this document is named <em>config</em>.
  +						This is the default name of the root element if no other name is
  +						specified. If we had called <code>createXMLReader("tables", "tabledef")</code>,
  +						the root element would have been named <em>tabledef</em> and
  +						we would have to use match strings of the form <em>tabledef/table</em>.
  +					</li>
  +					<li>
  +						The call of the Digester's <code>parse()</code> method is a little
  +						strange because we only pass in the string <em>config</em> as
  +						argument. Fact is that if a <code>XMLReader</code> obtained
  +						from <code>ConfigurationXMLDocument</code> is involved, the
  +						parameter of the <code>parse()</code> method is completely
  +						ignored. The reader always refers to the configuration source it
  +						was constructed for. So we could have used an arbitrary string.
  +					</li>
  +				</ul>
  +			</p>
  +			<p>
  +				After the <code>parse()</code> method returns the <code>tables</code>
  +				collection of the test class contains three <code>Table</code> objects
  +				with all information specified in the configuration.
  +			</p>
  +		</subsection>
  +		<subsection name="Calling Digester for creating simple objects">
  +			<p>
  +				If an application's configuration defines complex objects that should
  +				be instantiated using Digester it will usually be necessary to provide
  +				specific Digester rules as shown in the last section. But another
  +				use case is to create an object whose class name is defined in the
  +				configuration and to perform some simple initialization on it.
  +			</p>
  +			<p>
  +				Imagine the example database application wants to open a connection to
  +				a database. Because it is a very sophisticated application it supports
  +				lots of different databases. To achieve this there is an abstract
  +				<code>ConnectionInfo</code> class that provides typical connect
  +				properties (such as user name, password and the name of the database)
  +				and an abstract <code>connect()</code> method, which establishes
  +				the connection to the database:
  +			</p>
  +			<source>
  +<![CDATA[
  +    public abstract class ConnectionInfo
  +    {
  +        private String dsn;
  +        private String user;
  +        private String passwd;
  +        
  +        public String getDsn()
  +        {
  +            return dsn;
  +        }
  +
  +        public String getPasswd()
  +        {
  +            return passwd;
  +        }
  +
  +        public String getUser()
  +        {
  +            return user;
  +        }
  +
  +        public void setDsn(String string)
  +        {
  +            dsn = string;
  +        }
  +
  +        public void setPasswd(String string)
  +        {
  +            passwd = string;
  +        }
  +
  +        public void setUser(String string)
  +        {
  +            user = string;
  +        }
  +        
  +        /**
  +         * Creates a connection to a database.
  +         * Must be defined in sub classes.
  +         * @return the created connection
  +         */
  +        public abstract Connection createConnection() throws SQLException;
  +    }
  +]]>
  +			</source>
  +			<p>
  +				There are now some sub classes of this class, one for each supported
  +				database with a proper implementation of <code>createConnection()</code>.
  +				When the example application is run it does not know from start,
  +				which database it has to access. Instead it determines the database
  +				driver class from a configuration property, creates an instance of it,
  +				and uses it to obtain a connection. For this use case the
  +				<code>callDigester()</code> method of <code>ConfigurationXMLDocument</code>
  +				was designed.
  +			</p>
  +			<p>
  +				To demonstrate this feature we start with an additional configuration source
  +				that defines the connection of the database to be used. We call it
  +				<code>connection.xml</code>.
  +			</p>
  +			<source>
  +<![CDATA[
  +<?xml version="1.0" encoding="UTF-8"?>
  +<configuration>
  +	<connection>
  +		<class name="myapp.ConnectionInfoOracle">
  +			<property name="dsn" value="MyData"/>
  +			<property name="user" value="scott"/>
  +			<property name="passwd" value="tiger"/>
  +		</class>
  +	</connection>
  +</configuration>
  +]]>
  +			</source>
  +			<p>
  +				This configuration file shows the typical structure of XML code
  +				that defines an object to be created using Digester: There must be
  +				a <code>class</code> element with a <code>name</code> attribute
  +				defining the full qualified name of the class to be instantiated. In the
  +				body of the <code>class</code> element there can be an arbitrary
  +				number of <code>property</code> elements. Each element defines
  +				the name and value of a property to be set, so Digester will call a
  +				corresponding setter method on the bean just created. Here we set
  +				properties with the names <em>dsn, user</em> and <em>passwd</em>.
  +				As you can see, our <code>ConnectionInfo</code> base class has
  +				getter and setter methods for exactly these properties. We have now to
  +				include this additional configuration file in our configuration definition file:
  +			</p>
  +  			<source>
  +<![CDATA[
  +<?xml version="1.0" encoding="ISO-8859-1" ?>
  +<!-- Configuration definition file that demonstrates the
  +     override and additional sections -->
  +
  +<configuration>
  +  <override>
  +    <properties fileName="usergui.properties"/>
  +    <dom4j fileName="gui.xml"/>
  +  </override>
  +  
  +  <additional>
  +    <hierarchicalDom4j fileName="tables.xml"/>
  +    <hierarchicalDom4j fileName="tasktables.xml" at="tables"/>
  +    <hierarchicalDom4j fileName="connection.xml"/>
  +  </additional>
  +</configuration>
  +]]>
  +			</source>
  +			<p>
  +				After that it is now quite easy to obtain an object with information
  +				about a database connection. The following code fragment shows
  +				how a connection can be opened (it assumes that the <code>Configuration</code>
  +				object obtained from the configuration factory is stored in a variable named
  +				<code>config</code>).
  +			</p>
  +  			<source>
  +<![CDATA[
  +ConfigurationXMLDocument configDoc = new ConfigurationXMLDocument(config);
  +ConnectionInfo connInfo = (ConnectionInfo) configDoc.callDigester("connection");
  +Connection conn = connInfo.createConnection();
  +]]>
  +			</source>
  +		</subsection>
  +		<subsection name="Some caveats">
  +			<p>
  +				At the end of this section about <code>ConfigurationXMLDocument</code>
  +				some notes about points a developer should be aware of are provided:
  +			</p>
  +			<p>
  +				<ul>
  +					<li>
  +						The class should not be used on the <code>Configuration</code>
  +						object obtained from <code>ConfigurationFactory</code> as a
  +						whole. Because this configuration can contain multiple configuration
  +						sources including those that override other properties the results
  +						are probably not what you expect. You can of course pass such a
  +						composite configuration to the constructor of
  +						<code>ConfigurationXMLDocument</code>, but you should then,
  +						when you call methods on this instance, always provide a
  +						configuration key that selects certain parts of the configuration.
  +					</li>
  +					<li>
  +						The XML processing abilities of the class naturally work best
  +						when a <code>HierarchicalConfiguration</code> object is
  +						involved. There is also support for other types of configuration
  +						sources, but this will work well only for very simple properties.
  +						The problem here is the loss of information concerning the
  +						structure of the properties (as was explained in an earlier secion). 
  +						So if you read a configuration source
  +						using the <code>dom4j</code> element rather than the
  +						<code>hierarchicalDom4j</code> element, XML documents
  +						generated by <code>ConfigurationXMLDocument</code> may
  +						look wired; the same is true for <code>properties</code>
  +						elements.
  +					</li>
  +					<li>
  +						If you read a XML configuration file and then save it again
  +						using <code>ConfigurationXMLDocument.write()</code> the
  +						result is not guaranteed to be identic to the original file.
  +						While the document structure is kept (i.e. the relation between
  +						elements and their children), there may be differences in the
  +						order in which elements are written.
  +					</li>
  +				</ul>
  +			</p>
  +		</subsection>
   	</section>
   </body>
   
  
  
  
  1.6       +6 -6      jakarta-commons-sandbox/configuration/xdocs/overview.xml
  
  Index: overview.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-commons-sandbox/configuration/xdocs/overview.xml,v
  retrieving revision 1.5
  retrieving revision 1.6
  diff -u -r1.5 -r1.6
  --- overview.xml	31 Oct 2003 17:10:03 -0000	1.5
  +++ overview.xml	26 Nov 2003 11:03:14 -0000	1.6
  @@ -67,9 +67,9 @@
   <?xml version="1.0" encoding="ISO-8859-1" ?>
   
   <configuration>
  -  <jndi className="org.apache.commons.configuration.JNDIConfiguration" prefix="java:comp/env"/>
  -  <properties className="org.apache.commons.configuration.PropertiesConfiguration" fileName="conf/test.properties"/>
  -  <dom4j className="org.apache.commons.configuration.DOM4JConfiguration" fileName="conf/test.xml"/>
  +  <jndi prefix="java:comp/env"/>
  +  <properties fileName="conf/test.properties"/>
  +  <dom4j fileName="conf/test.xml"/>
   </configuration>
   ]]>   
           </source>
  @@ -96,7 +96,7 @@
       	<subsection name="Classic Properties File">
           <source>
   <![CDATA[       
  -  <properties className="org.apache.commons.configuration.PropertiesConfiguration" fileName="conf/test.properties"/>
  +  <properties fileName="conf/test.properties"/>
   ]]>   
           </source>
           <p>
  @@ -106,7 +106,7 @@
       	<subsection name="XML Properties File">
           <source>
   <![CDATA[       
  -  <dom4j className="org.apache.commons.configuration.DOM4JConfiguration" fileName="conf/test.xml"/>
  +  <dom4j fileName="conf/test.xml"/>
   ]]>   
           </source>    	
           <p>
  @@ -136,7 +136,7 @@
       	<subsection name="JNDI Properties File">
           <source>
   <![CDATA[       
  -  <jndi className="org.apache.commons.configuration.JNDIConfiguration" prefix="java:comp/env"/>
  +  <jndi prefix="java:comp/env"/>
   ]]>   
           </source>    	
           <p>
  
  
  
  1.2       +2 -2      jakarta-commons-sandbox/configuration/conf/testDigesterConfiguration2.xml
  
  Index: testDigesterConfiguration2.xml
  ===================================================================
  RCS file: /home/cvs/jakarta-commons-sandbox/configuration/conf/testDigesterConfiguration2.xml,v
  retrieving revision 1.1
  retrieving revision 1.2
  diff -u -r1.1 -r1.2
  --- testDigesterConfiguration2.xml	11 Nov 2003 15:02:08 -0000	1.1
  +++ testDigesterConfiguration2.xml	26 Nov 2003 11:03:15 -0000	1.2
  @@ -4,8 +4,8 @@
   
   <configuration>
     <additional>
  -    <dom4j className="org.apache.commons.configuration.HierarchicalDOM4JConfiguration" fileName="testHierarchicalDOM4JConfiguration.xml"/>
  -    <dom4j className="org.apache.commons.configuration.HierarchicalDOM4JConfiguration" fileName="testDigesterConfigurationInclude1.xml" at="tables"/>
  +    <hierarchicalDom4j fileName="testHierarchicalDOM4JConfiguration.xml"/>
  +    <hierarchicalDom4j fileName="testDigesterConfigurationInclude1.xml" at="tables"/>
       <properties fileName="testDigesterConfigurationInclude2.properties" at="mail"/>
     </additional>
   
  
  
  
  1.1                  jakarta-commons-sandbox/configuration/conf/testConfigurationXMLDocument.xml
  
  Index: testConfigurationXMLDocument.xml
  ===================================================================
  <?xml version="1.0" encoding="ISO-8859-1" ?>
  <!-- Configuration test file that demonstrates usage of ConfigurationXMLDocument -->
  
  <configuration>
    <additional>
      <hierarchicalDom4j fileName="testHierarchicalDOM4JConfiguration.xml" at="database"/>
      <hierarchicalDom4j fileName="testDigesterConfigurationInclude1.xml" at="database.tables"/>
      <hierarchicalDom4j fileName="testDigesterCreateObject.xml" at="database"/>
    </additional>
  </configuration>
  
    
    
  
  
  
  
  
  
  1.1                  jakarta-commons-sandbox/configuration/conf/testDigesterCreateObject.xml
  
  Index: testDigesterCreateObject.xml
  ===================================================================
  <?xml version="1.0" encoding="UTF-8"?>
  <configuration>
  	<connection>
  		<class name="org.apache.commons.configuration.TestConfigurationXMLDocument$ConnectionData">
  			<property name="dsn" value="MyData"/>
  			<property name="user" value="scott"/>
  			<property name="passwd" value="tiger"/>
  		</class>
  	</connection>
  </configuration
  
  
  1.1                  jakarta-commons-sandbox/configuration/src/test/org/apache/commons/configuration/TestConfigurationXMLDocument.java
  
  Index: TestConfigurationXMLDocument.java
  ===================================================================
  package org.apache.commons.configuration;
  
  /* ====================================================================
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2002-2003 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Commons", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  import java.io.File;
  import java.io.StringReader;
  import java.io.StringWriter;
  import java.util.ArrayList;
  import java.util.List;
  import java.util.NoSuchElementException;
  
  import org.apache.commons.digester.Digester;
  import org.dom4j.Document;
  import org.dom4j.Element;
  import org.dom4j.io.DOMReader;
  import org.xml.sax.XMLReader;
  
  import junit.framework.TestCase;
  
  /**
   * Test class for ConfigurationXMLDocument.
   *
   * @author <a href="mailto:oliver.heger@t-online.de">Oliver Heger</a>
   * @version $Id: TestConfigurationXMLDocument.java,v 1.1 2003/11/26 11:03:15 epugh Exp $
   */
  public class TestConfigurationXMLDocument extends TestCase
  {
      private static final String DEF_FILE = "conf/testConfigurationXMLDocument.xml";
      
      private Configuration config;
      private ConfigurationFactory configFactory;
      private ConfigurationXMLDocument configDoc;
      
      private ArrayList tables;
      private int counter;
      
      public TestConfigurationXMLDocument(String arg0)
      {
          super(arg0);
      }
  
      protected void setUp() throws Exception
      {
          configFactory = new ConfigurationFactory();
          configFactory.setConfigurationURL(new File(DEF_FILE).toURL());
          configDoc = new ConfigurationXMLDocument(configFactory.getConfiguration());
          config = configDoc.getConfiguration();
          
          tables = new ArrayList();
      }
  
      public void testCreateXMLReader()
      {
          BaseConfiguration bc = new BaseConfiguration();
          bc.addProperty("test", "value");
          XMLReader reader = ConfigurationXMLDocument.createXMLReader(bc);
          assertTrue(reader instanceof BaseConfigurationXMLReader);
          
          HierarchicalConfiguration hc = new HierarchicalConfiguration();
          hc.addProperty("test", "value");
          reader = ConfigurationXMLDocument.createXMLReader(hc);
          assertTrue(reader instanceof HierarchicalConfigurationXMLReader);
          
          reader = configDoc.createXMLReader();
          assertTrue(reader instanceof BaseConfigurationXMLReader);
          ConfigurationXMLDocument doc = 
          new ConfigurationXMLDocument(config.subset("database"));
          reader = doc.createXMLReader();
          assertTrue(reader instanceof HierarchicalConfigurationXMLReader);
          
          try
          {
              configDoc.createXMLReader("unexisting.property");
              fail();
          }  /* try */
          catch(NoSuchElementException ex)
          {
              // fine
          }  /* catch */
      }
      
      public void testGetDocument() throws Exception
      {
          Document doc;
          ConfigurationXMLDocument xmlDoc = 
          new ConfigurationXMLDocument(config.subset("database"));
          
          doc = configDoc.getDocument("database");
          checkDocument(doc, "config");
          
          doc = xmlDoc.getDocument();
          checkDocument(doc, "config");
          
          doc = xmlDoc.getDocument(null, "database");
          checkDocument(doc, "database");
          
          doc = configDoc.getDocument("database", "db-config");
          checkDocument(doc, "db-config");
      }
      
      public void testGetW3CDocument() throws Exception
      {
          org.w3c.dom.Document doc;
          ConfigurationXMLDocument xmlDoc = 
          new ConfigurationXMLDocument(config.subset("database"));
          
          doc = configDoc.getW3cDocument("database");
          checkDocument(doc, "config");
          
          doc = xmlDoc.getW3cDocument();
          checkDocument(doc, "config");
          
          doc = xmlDoc.getW3cDocument(null, "database");
          checkDocument(doc, "database");
          
          doc = configDoc.getW3cDocument("database", "db-config");
          checkDocument(doc, "db-config");
      }
      
      public void testCallDigester() throws Exception
      {
          Object obj = configDoc.callDigester("database.connection");
          assertNotNull(obj);
          assertTrue(obj instanceof ConnectionData);
          
          ConnectionData cd = (ConnectionData) obj;
          assertEquals("MyData", cd.getDsn());
          assertEquals("scott", cd.getUser());
          assertEquals("tiger", cd.getPasswd()); 
      }
      
      private void checkDocument(Document doc, String root)
      throws Exception
      {
          Element rt = doc.getRootElement();
          assertEquals(root, rt.getName());
          
          check(rt, "tables.table(0).name", "users");
          check(rt, "tables.table(1).name", "documents");
          check(rt, "tables.table(2).name", "tasks");
          check(rt, "tables.table(0).fields.field(0).type", "long");
          check(rt, "tables.table(2).fields.field(6).name", "endDate");
          
          checkAttribute(rt, "connection.class.property(0)", "name", "dsn");
          checkAttribute(rt, "connection.class.property(0)", "value", "MyData");
          checkAttribute(rt, "connection.class.property(1)", "name", "user");
          checkAttribute(rt, "connection.class.property(1)", "value", "scott");
      }
      
      private void checkDocument(org.w3c.dom.Document doc, String root)
      throws Exception
      {
          DOMReader reader = new DOMReader();
          checkDocument(reader.read(doc), root);
      }
      
      /**
       * Helper method for checking values in the DOM4J document.
       * @param root the root element
       * @param path the path to be checked
       * @param value the expected element value
       */
      private void check(Element root, String path, String value)
      {
          assertEquals(value, findElement(root, path).getTextTrim());        
      }
      
      /**
       * Helper method for checking the value of an attribute in the DOM4J doc. 
       * @param root the root element
       * @param path the path to the element
       * @param attr the name of the attribute
       * @param value the expected value
       */
      private void checkAttribute(Element root, String path, String attr, String value)
      {
          Element elem = findElement(root, path);
          assertEquals(value, elem.attributeValue(attr));
      }
      
      /**
       * Helper method for searching an element in an document.
       * @param root the root element
       * @param path the path for the element
       * @return the found element
       */
      private Element findElement(Element root, String path)
      {
          ConfigurationKey.KeyIterator keyIt =
          new ConfigurationKey(path).iterator();
          Element e = root;
          
          while(keyIt.hasNext())
          {
              List elems = e.elements(keyIt.nextKey());
              assertNotNull(elems);
              int idx = (keyIt.hasIndex()) ? keyIt.getIndex() : 0;
              assertTrue(idx >= 0 && idx < elems.size());
              e = (Element) elems.get(idx);    
          }  /* while */
          
          return e;
      }
      
      /**
       * Adds a new table object. Called by Digester.
       * @param table the new table
       */
      public void addTable(Table table)
      {
          tables.add(table);
      }
      
      public void testCallDigesterComplex() throws Exception
      {
          Digester digester = 
              new Digester(configDoc.createXMLReader("database.tables"));
          digester.addObjectCreate("config/table", Table.class);
          digester.addSetProperties("config/table");
          digester.addCallMethod("config/table/name", "setName", 0);
          digester.addObjectCreate("config/table/fields/field", TableField.class);
          digester.addCallMethod("config/table/fields/field/name",
              "setName", 0);
          digester.addCallMethod("config/table/fields/field/type",
              "setType", 0);
          digester.addSetNext("config/table/fields/field",
              "addField", TableField.class.getName());
          digester.addSetNext("config/table", "addTable", Table.class.getName());
          
          digester.push(this);
          digester.parse("config");
          
          assertEquals(3, tables.size());
          Table table = (Table) tables.get(0);
          assertEquals("system", table.getTableType());
          assertEquals("users", table.getName());
          assertEquals(5, table.size());
          TableField field = table.getField(1);
          assertEquals("uname", field.getName());
          assertEquals(String.class.getName(), field.getType());
          
          table = (Table) tables.get(2);
          assertEquals("application", table.getTableType());
          assertEquals("tasks", table.getName());
          assertEquals(7, table.size());
          field = table.getField(4);
          assertEquals("creatorID", field.getName());
          assertEquals("long", field.getType());
      }
      
      public void testWrite() throws Exception
      {
          StringWriter out;
          String doc;
          
          out = new StringWriter();
          configDoc.write(out, "database.tables");
          doc = out.toString();
          assertEquals(3, countElements("config/table", doc));
          assertEquals(17, countElements("config/table/fields/field", doc));
          out = new StringWriter();
          configDoc.write(out, "database.tables", false);
          assertTrue(out.toString().length() <= doc.length());
          
          out = new StringWriter();
          configDoc.write(out, "database.tables.table(2)", "table");
          doc = out.toString();
          assertEquals(1, countElements("table", doc));
          assertEquals(7, countElements("table/fields/field", doc));
          out = new StringWriter();
          configDoc.write(out, "database.tables.table(2)", "table", false);
          assertTrue(out.toString().length() <= doc.length());
      }
      
      /**
       * Helper method for counting the number of occurrences of a certain
       * element constellation in a XML string.
       * @param match the match string for the element constellation
       * @param doc the XML as string
       * @return the number of occurrences
       * @throws Exception if an error occurs
       */
      private int countElements(String match, String doc) throws Exception
      {
          Digester digester = new Digester();
          digester.addCallMethod(match, "incrementCounter");
          digester.push(this);
          counter = 0;
          digester.parse(new StringReader(doc));
          return counter;
      }
      
      /**
       * Increments the internal counter. Called by Digester during testing.
       */
      public void incrementCounter()
      {
          counter++;
      }
      
      /**
       * An inner class for connection information. An instance will be
       * created by Digester.
       */
      public static class ConnectionData
      {
          private String dsn;
          private String user;
          private String passwd;
          
          public String getDsn()
          {
              return dsn;
          }
  
          public String getPasswd()
          {
              return passwd;
          }
  
          public String getUser()
          {
              return user;
          }
  
          public void setDsn(String string)
          {
              dsn = string;
          }
  
          public void setPasswd(String string)
          {
              passwd = string;
          }
  
          public void setUser(String string)
          {
              user = string;
          }
      }
      
      /**
       * A simple bean class for storing information about a table field.
       * Used for the Digester test.
       */
      public static class TableField
      {
          private String name;
          private String type;
          
          public String getName()
          {
              return name;
          }
  
          public String getType()
          {
              return type;
          }
  
          public void setName(String string)
          {
              name = string;
          }
  
          public void setType(String string)
          {
              type = string;
          }
      }
      
      /**
       * A simple bean class for storing information about a database table.
       * Used for the Digester test.
       */
      public static class Table
      {
          /** Stores the list of fields.*/
          private ArrayList fields;
          
          /** Stores the table name.*/
          private String name;
          
          /** Stores the table type.*/
          private String tableType;
          
          public Table()
          {
              fields = new ArrayList();
          }
          
          public String getName()
          {
              return name;
          }
  
          public String getTableType()
          {
              return tableType;
          }
  
          public void setName(String string)
          {
              name = string;
          }
  
          public void setTableType(String string)
          {
              tableType = string;
          }
          
          /**
           * Adds a field.
           * @param field the new field
           */
          public void addField(TableField field)
          {
              fields.add(field);
          }
          
          /**
           * Returns the field with the given index.
           * @param idx the index
           * @return the field with this index
           */
          public TableField getField(int idx)
          {
              return (TableField) fields.get(idx);
          }
          
          /**
           * Returns the number of fields.
           * @return the number of fields
           */
          public int size()
          {
              return fields.size();
          }
      }
  }
  
  
  

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