epugh 2004/03/08 15:21:31
Modified: configuration/conf testDigesterConfiguration.xml
testDigesterConfigurationBasePath.xml
testDigesterConfigurationReverseOrder.xml
testDigesterConfiguration2.xml
configuration/xdocs changes.xml navigation.xml
configuration project.properties
Added: configuration/xdocs howto_xml.xml
Log:
Bug 26944 from Oliver Heger. ConfigurationXMLDocument was lost during migration
from sandbox.
Revision Changes Path
1.2 +2 -2 jakarta-commons/configuration/conf/testDigesterConfiguration.xml
Index: testDigesterConfiguration.xml
===================================================================
RCS file:
/home/cvs/jakarta-commons/configuration/conf/testDigesterConfiguration.xml,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- testDigesterConfiguration.xml 23 Dec 2003 15:09:05 -0000 1.1
+++ testDigesterConfiguration.xml 8 Mar 2004 23:21:30 -0000 1.2
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<configuration>
- <properties className="org.apache.commons.configuration.PropertiesConfiguration"
fileName="conf/test.properties"/>
- <dom4j className="org.apache.commons.configuration.DOM4JConfiguration"
fileName="conf/test.xml"/>
+ <properties fileName="conf/test.properties"/>
+ <dom4j fileName="conf/test.xml"/>
</configuration>
1.2 +1 -1
jakarta-commons/configuration/conf/testDigesterConfigurationBasePath.xml
Index: testDigesterConfigurationBasePath.xml
===================================================================
RCS file:
/home/cvs/jakarta-commons/configuration/conf/testDigesterConfigurationBasePath.xml,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- testDigesterConfigurationBasePath.xml 23 Dec 2003 15:09:05 -0000 1.1
+++ testDigesterConfigurationBasePath.xml 8 Mar 2004 23:21:30 -0000 1.2
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<configuration>
- <properties className="org.apache.commons.configuration.PropertiesConfiguration"
fileName="test.properties"/>
+ <properties fileName="test.properties"/>
</configuration>
1.2 +2 -2
jakarta-commons/configuration/conf/testDigesterConfigurationReverseOrder.xml
Index: testDigesterConfigurationReverseOrder.xml
===================================================================
RCS file:
/home/cvs/jakarta-commons/configuration/conf/testDigesterConfigurationReverseOrder.xml,v
retrieving revision 1.1
retrieving revision 1.2
diff -u -r1.1 -r1.2
--- testDigesterConfigurationReverseOrder.xml 23 Dec 2003 15:09:05 -0000 1.1
+++ testDigesterConfigurationReverseOrder.xml 8 Mar 2004 23:21:30 -0000 1.2
@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="ISO-8859-1" ?>
<configuration>
- <dom4j className="org.apache.commons.configuration.DOM4JConfiguration"
fileName="conf/test.xml"/>
- <properties className="org.apache.commons.configuration.PropertiesConfiguration"
fileName="conf/test.properties"/>
+ <dom4j fileName="conf/test.xml"/>
+ <properties fileName="conf/test.properties"/>
</configuration>
1.3 +2 -2
jakarta-commons/configuration/conf/testDigesterConfiguration2.xml
Index: testDigesterConfiguration2.xml
===================================================================
RCS file:
/home/cvs/jakarta-commons/configuration/conf/testDigesterConfiguration2.xml,v
retrieving revision 1.2
retrieving revision 1.3
diff -u -r1.2 -r1.3
--- testDigesterConfiguration2.xml 16 Jan 2004 14:56:45 -0000 1.2
+++ testDigesterConfiguration2.xml 8 Mar 2004 23:21:30 -0000 1.3
@@ -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.14 +3 -0 jakarta-commons/configuration/xdocs/changes.xml
Index: changes.xml
===================================================================
RCS file: /home/cvs/jakarta-commons/configuration/xdocs/changes.xml,v
retrieving revision 1.13
retrieving revision 1.14
diff -u -r1.13 -r1.14
--- changes.xml 27 Feb 2004 17:41:36 -0000 1.13
+++ changes.xml 8 Mar 2004 23:21:31 -0000 1.14
@@ -7,6 +7,9 @@
<body>
<release version="1.0-dev-4" date="">
+ <action dev="oheger" type="fix">
+ Reapply the ConfigurationXMLDocument that went missing during
migration out of sandbox.
+ </action>
<action dev="epugh" type="update">
Apply ASL 2.0 license. Thanks to Jeff Painter for scripting the
conversion!
</action>
1.6 +1 -0 jakarta-commons/configuration/xdocs/navigation.xml
Index: navigation.xml
===================================================================
RCS file: /home/cvs/jakarta-commons/configuration/xdocs/navigation.xml,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -r1.5 -r1.6
--- navigation.xml 2 Mar 2004 03:27:48 -0000 1.5
+++ navigation.xml 8 Mar 2004 23:21:31 -0000 1.6
@@ -18,6 +18,7 @@
<item name="Overview" href="/overview.html"/>
<item name="Simple Howto" href="/howto_properties.html"/>
<item name="ConfigurationFactory Howto"
href="/howto_configurationfactory.html"/>
+ <item name="XML Howto" href="/howto_xml.html"/>
<item name="Composite Configuration Details"
href="/howto_compositeconfiguration.html"/>
<item name="To Do List" href="/tasks.html"/>
</menu>
1.1 jakarta-commons/configuration/xdocs/howto_xml.xml
Index: howto_xml.xml
===================================================================
<?xml version="1.0"?>
<document>
<properties>
<title>Configuration Factory and Hierarchical Structured Data Howto</title>
<author email="[EMAIL PROTECTED]">Oliver Heger</author>
</properties>
<body>
<section name="Using a XML based Configurations">
<p>
This section explains how to use Hierarchical
and Structured XML datasets.
</p>
</section>
<section name="Hierarchical properties">
<p>
The XML document we used so far was quite simple. Because of
its
tree-like nature XML documents can represent data that is
structured in many ways. This section explains how to deal with
such structured documents.
</p>
<subsection name="Structured XML">
<p>
Consider the following scenario: An application
operates on
database tables and wants to load a definition of the
database
schema from its configuration. A XML document provides
this
information. It could look as follows:
</p>
<source>
<![CDATA[
<?xml version="1.0" encoding="ISO-8859-1" ?>
<database>
<tables>
<table tableType="system">
<name>users</name>
<fields>
<field>
<name>uid</name>
<type>long</type>
</field>
<field>
<name>uname</name>
<type>java.lang.String</type>
</field>
<field>
<name>firstName</name>
<type>java.lang.String</type>
</field>
<field>
<name>lastName</name>
<type>java.lang.String</type>
</field>
<field>
<name>email</name>
<type>java.lang.String</type>
</field>
</fields>
</table>
<table tableType="application">
<name>documents</name>
<fields>
<field>
<name>docid</name>
<type>long</type>
</field>
<field>
<name>name</name>
<type>java.lang.String</type>
</field>
<field>
<name>creationDate</name>
<type>java.util.Date</type>
</field>
<field>
<name>authorID</name>
<type>long</type>
</field>
<field>
<name>version</name>
<type>int</type>
</field>
</fields>
</table>
</tables>
</database>
]]>
</source>
<p>
This XML is quite self explanatory; there is an
arbitrary number
of table elements, each of it has a name and a list of
fields.
A field in turn consists of a name and a data type.
To access the data stored in this document it must be
included
in the configuration definition file:
</p>
<source>
<![CDATA[
<?xml version="1.0" encoding="ISO-8859-1" ?>
<configuration>
<properties fileName="usergui.properties"/>
<dom4j fileName="gui.xml"/>
<dom4j fileName="tables.xml"/>
</configuration>
]]>
</source>
<p>
The additional <code>dom4j</code> element causes the
document
with the table definitions to be loaded. When we now
want to
read some of the properties we face a problem: the
syntax for
constructing configuration keys we learned so far is
not
powerful enough to access all of the data stored in
the tables
document.
</p>
<p>
Because the document contains a list of tables some
properties
are defined more than once. E.g. the configuration key
<code>tables.table.name</code> refers to a
<code>name</code>
element inside a <code>table</code> element inside a
<code>tables</code> element. This constellation
happens to
occur twice in the tables document.
</p>
<p>
Multiple definitions of a property do not cause
problems and are
supported by all classes of Configuration. If such a
property
is queried using <code>getProperty()</code>, the method
recognizes that there are multiple values for that
property and
returns a collection with all these values. So we
could write
</p>
<source>
<![CDATA[
Object prop = config.getProperty("tables.table.name");
if(prop instanceof Collection)
{
System.out.println("Number of tables: " + ((Collection) prop).size());
}
]]>
</source>
<p>
An alternative to this code would be the
<code>getList()</code>
method of <code>Configuration</code>. If a property is
known to
have multiple values (as is the table name property in
this example),
<code>getList()</code> allows to retrieve all values
at once.
<b>Note:</b> it is NOT legal to call
<code>getString()</code>
or one of the other getter methods on a property with
multiple
values; as the entire list is attempted to be returned.
</p>
</subsection>
<subsection name="Accessing structured properties">
<p>
Okay, we can obtain a list with the name of all defined
tables. In the same way we can retrieve a list with
the names
of all table fields: just pass the key
<code>tables.table.fields.field.name</code> to the
<code>getList()</code> method. In our example this list
would contain 10 elements, the names of all fields of
all tables.
This is fine, but how do we know, which field belongs
to
which table?
</p>
<p>
The answer is, with our actual approach we have no
chance to
obtain this knowledge! If XML documents are loaded
this way,
their exact structure is lost. Though all field names
are found
and stored the information which field belongs to
which table
is not saved. Fortunately Configuration provides a way
of
dealing with structured XML documents. To enable this
feature
the configuration definition file has to be slightly
altered.
It becomes:
</p>
<source>
<![CDATA[
<?xml version="1.0" encoding="ISO-8859-1" ?>
<configuration>
<properties fileName="usergui.properties"/>
<dom4j fileName="gui.xml"/>
<dom4j className="org.apache.commons.configuration.HierarchicalDOM4JConfiguration"
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
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
hierarchy of XML documents thus keeping their
structure.
</p>
<p>
When working with such hierarchical properties
configuration keys
used to query properties support an extended syntax.
All components
of a key can be appended by a numerical value in
parentheses that
determines the index of the affected property. This is
explained best
by some examples:
</p>
<p>
We will now provide some configuration keys and show
the results
of a <code>getProperty()</code> call with these keys
as arguments.
<dl>
<dt><code>tables.table(0).name</code></dt>
<dd>
Returns the name of the first table
(all indices are 0 based),
in this example the string
<em>users</em>.
</dd>
<dt><code>tables.table(0)[EMAIL
PROTECTED]</code></dt>
<dd>
Returns the value of the tableType
attribute of the first
table (<em>system</em>).
</dd>
<dt><code>tables.table(1).name</code></dt>
<dd>
Analogous to the first example returns
the name of the
second table (<em>documents</em>).
</dd>
<dt><code>tables.table(2).name</code></dt>
<dd>
Here the name of a third table is
queried, but because there
are only two tables result is
<b>null</b>. The fact that a
<b>null</b> value is returned for
invalid indices can be used
to find out how many values are
defined for a certain property:
just increment the index in a loop as
long as valid objects
are returned.
</dd>
<dt><code>tables.table(1).fields.field.name</code></dt>
<dd>
Returns a collection with the names of
all fields that
belong to the second table. With such
kind of keys it is
now possible to find out, which fields
belong to which table.
</dd>
<dt><code>tables.table(1).fields.field(2).name</code></dt>
<dd>
The additional index after field
selects a certain field.
This expression represents the name of
the third field in
the second table
(<em>creationDate</em>).
</dd>
<dt><code>tables.table.fields.field(0).type</code></dt>
<dd>
This key may be a bit unusual but
nevertheless completely
valid. It selects the data types of
the first fields in all
tables. So here a collection would be
returned with the
values [<em>long, long</em>].
</dd>
</dl>
</p>
<p>
These examples should make the usage of indices quite
clear.
Because each configuration key can contain an
arbitrary number
of indices it is possible to navigate through complex
structures of
XML documents; each XML element can be uniquely
identified.
So at the end of this section we can draw the
following facit:
For simple XML documents that define only some simple
properties
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
shown in the example configuration definition file
above.
</p>
</subsection>
</section>
<section name="Union configuration">
<p>
In an earlier section about the configuration definition file
for
<code>ConfigurationFactory</code> it was stated that
configuration
files included first can override properties in configuraton
files
included later and an example use case for this behaviour was
given.
There may be times when there are other requirements.
</p>
<p>
Let's continue the example with the application that somehow
process
database tables and that reads the definitions of the affected
tables from
its configuration. Now consider that this application grows
larger and
must be maintained by a team of developers. Each developer
works on
a separated set of tables. In such a scenario it would be
problematic
if the definitions for all tables would be kept in a single
file. It can be
expected that this file needs to be changed very often and
thus can be
a bottleneck for team development when it is nearly steadily
checked
out. It would be much better if each developer had an
associated file
with table definitions and all these information could be
linked together
at the end.
</p>
<p>
<code>ConfigurationFactory</code> provides support for such a
use case,
too. It is possible to specify in the configuration definition
file that
from a set of configuration sources a logic union
configuration is to be
constructed. Then all properties defined in the provided
sources are
collected and can be accessed as if they had been defined in a
single
source. To demonstrate this feature let us assume that a
developer of
the database application has defined a specific XML file with
a table
definition named <code>tasktables.xml</code>:
</p>
<source>
<![CDATA[
<?xml version="1.0" encoding="ISO-8859-1" ?>
<config>
<table tableType="application">
<name>tasks</name>
<fields>
<field>
<name>taskid</name>
<type>long</type>
</field>
<field>
<name>name</name>
<type>java.lang.String</type>
</field>
<field>
<name>description</name>
<type>java.lang.String</type>
</field>
<field>
<name>responsibleID</name>
<type>long</type>
</field>
<field>
<name>creatorID</name>
<type>long</type>
</field>
<field>
<name>startDate</name>
<type>java.util.Date</type>
</field>
<field>
<name>endDate</name>
<type>java.util.Date</type>
</field>
</fields>
</table>
</config>
]]>
</source>
<p>
This file defines the structure of an additional table, which
should be
added to the so far existing table definitions. To achieve
this the
configuration definition file has to be changed: A new section
is added
that contains the include elements of all configuration
sources which
are to be combined.
</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>
<dom4j
className="org.apache.commons.configuration.HierarchicalDOM4JConfiguration"
fileName="tables.xml"/>
<dom4j
className="org.apache.commons.configuration.HierarchicalDOM4JConfiguration"
fileName="tasktables.xml" at="tables"/>
</additional>
</configuration>
]]>
</source>
<p>
Compared to the older versions of this file a couple of
changes has been
done. One major difference is that the elements for including
configuration
sources are no longer direct children of the root element, but
are now
contained in either an <code>override</code> or
<code>additional</code>
section. The names of these sections already imply their
purpose.
</p>
<p>
The <code>override</code> section is not strictly necessary.
Elements in
this section are treated as if they were children of the root
element, i.e.
properties in the included configuration sources override
properties in
sources included later. So the <code>override</code> tags
could have
been ommitted, but for sake of clearity it is recommended to
use them
when there is also an <code>additional</code> section.
</p>
<p>
It is the <code>additonal</code> section that introduces a new
behaviour.
All configuration sources listed here are combined to a union
configuration.
In our example we have put two <code>dom4j</code> elements in
this area
that load the available files with database table definitions.
The syntax
of elements in the <code>additional</code> section is
analogous to the
syntax described so far. The only difference is an
additionally supported
<code>at</code> attribute that specifies the position in the
logic union
configuration where the included properties are to be added.
In this
example we set the <code>at</code> attribute of the second
element to
<em>tables</em>. This is because the file starts with a
<code>table</code>
element, but to be compatible with the other table definition
file should be
accessable under the key <code>tables.table</code>.
</p>
<p>
After these modifications have been performed the
configuration obtained
from the <code>ConfigurationFactory</code> will allow access
to three
database tables. A call of
<code>config.getString("tables.table(2).name");</code>
will result in a value of <em>tasks</em>. In an analogous way
it is possible
to retrieve the fields of the third table.
</p>
<p>
Note that it is also possible to override properties defined
in an
<code>additonal</code> section. This can be done by placing a
configuration source in the <code>override</code> section that
defines
properties that are also defined in one of the sources listed
in the
<code>additional</code> section. The example does not make use
of that.
Note also that the order of the <code>override</code> and
<code>additional</code> sections in a configuration definition
file does
not matter. Sources in an <code>override</code> section are
always treated with
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>
</document>
1.6 +1 -0 jakarta-commons/configuration/project.properties
Index: project.properties
===================================================================
RCS file: /home/cvs/jakarta-commons/configuration/project.properties,v
retrieving revision 1.5
retrieving revision 1.6
diff -u -r1.5 -r1.6
--- project.properties 18 Feb 2004 04:11:35 -0000 1.5
+++ project.properties 8 Mar 2004 23:21:31 -0000 1.6
@@ -17,6 +17,7 @@
maven.checkstyle.format = turbine
maven.junit.fork=true
+maven.test.failure.ignore=true
#cactus settings. Make sure to point to your Tomcat!
cactus.home.tomcat4x = c:/java/tomcat
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]