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>
* ...
* <class name="mypackage.MyClass"/>
* <args>
* <property name="myFirstProperty" value="myFirstValue"/>
* <property name="MySecondProperty" value="mySecondValue"/>
* ...
* </args>
* ...
* </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