Using Hierarchical ConfigurationsThis section explains how to use hierarchical and structured XML datasets. Hierarchical properties
Many sources of configuration data have a hierarchical or tree-like
nature. They can represent data that is structured in many ways.
Such configuration sources are represented by classes derived from
Prominent examples of hierarchical configuration sources are XML
documents. They can be read and written using the
Accessing properties in hierarchical configurations
We will start with a simple XML document to show some basics
about accessing properties. The following file named
<?xml version="1.0" encoding="ISO-8859-1" ?> <gui-definition> <colors> <background>#808080</background> <text>#000000</text> <header>#008000</header> <link normal="#000080" visited="#800080"/> <default>${colors.header}</default> </colors> <rowsPerPage>15</rowsPerPage> <buttons> <name>OK,Cancel,Help</name> </buttons> <numberFormat pattern="###\,###.##"/> </gui-definition>
(As becomes obvious, this tutorial does not bother with good
design of XML documents, the example file should rather
demonstrate the different ways of accessing properties.)
To access the data stored in this document it must be loaded
by try { XMLConfiguration config = new XMLConfiguration("tables.xml"); // do something with config } catch(ConfigurationException cex) { // something went wrong, e.g. the file was not found } If no exception was thrown, the properties defined in the XML document are now available in the configuration object. Other hierarchical configuration classes that operate on files have corresponding constructors and methods for loading their data. The following fragment shows how the properties can be accessed: String backColor = config.getString("colors.background"); String textColor = config.getString("colors.text"); String linkNormal = config.getString("colors.link[@normal]"); String defColor = config.getString("colors.default"); int rowsPerPage = config.getInt("rowsPerPage"); List<Object> buttons = config.getList("buttons.name");
This listing demonstrates some important points about constructing
keys for accessing properties in hierarchical configuration sources and about
features of
In the next section will show how data in a more complex XML document can be processed. Complex hierarchical structuresConsider 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: <?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>
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. This
XML document (let's call it When we now want to access 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.
Because the document contains a list of tables some properties
are defined more than once. E.g. the configuration key
Multiple definitions of a property do not cause problems and are
supported by all classes of Configuration. If such a property
is queried using Object prop = config.getProperty("tables.table.name"); if(prop instanceof Collection) { System.out.println("Number of tables: " + ((Collection<?>) prop).size()); }
An alternative to this code would be the Accessing structured properties
Okay, we can obtain a list with the names of all defined
tables. In the same way we can retrieve a list with the names
of all table fields: just pass the key
When working with such hierarchical structures the configuration keys
used to query properties can have 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. So if we have two
We will now provide some configuration keys and show the results
of a
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 hierarchical configurations; each property can be uniquely identified.
Sometimes dealing with long property keys may become inconvenient,
especially if always the same properties are accessed. For this
case HierarchicalConfiguration sub = config.configurationAt("tables.table(0)"); String tableName = sub.getString("name"); // only need to provide relative path List<Object> fieldNames = sub.getList("fields.field.name");
For dealing with complex list-like structures there is another
short cut. Often it will be necessary to iterate over all items
in the list and access their (sub) properties. A good example are
the fields of the tables in our demo configuration. When you want
to process all fields of a table (e.g. for constructing a
List<Object> fieldNames = config.getList("tables.table(0).fields.field.name"); List<Object> fieldTypes = config.getList("tables.table(0).fields.field.type"); List<Object> ... // further calls for other data that might be stored in the config
But this is not very readable and will fail if not all field
elements contain the same set of data (for instance the
List<HierarchicalConfiguration> fields = config.configurationsAt("tables.table(0).fields.field"); for(HierarchicalConfiguration sub : fields) { // sub contains all data about a single field String fieldName = sub.getString("name"); String fieldType = sub.getString("type"); ...
The configurations returned by the Adding new properties
So far we have learned how to use indices to avoid ambiguities when
querying properties. The same problem occurs when adding new
properties to a structured configuration. As an example let's
assume we want to add a new field to the second table. New properties
can be added to a configuration using the // Warning: This might cause trouble! config.addProperty("tables.table.fields.field.name", "size");
would not be sufficient because it does not contain all needed
information. How is such a statement processed by the
The following parts of the key are processed in exactly the same
manner. Under the selected ... <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> <name>size</name> <== Newly added property <type>int</type> </field> </fields> </table> </tables> </database>
This result is obviously not what was desired, but it demonstrates
how
If we want a different behavior, we must explicitly tell
config.addProperty("tables.table(1).fields.field(-1).name", "size"); config.addProperty("tables.table(1).fields.field.type", "int");
The first line in this fragment specifies that a new branch is
to be created for the // Add a new table element and define the name config.addProperty("tables.table(-1).name", "versions"); // Add a new field to the new table // (an index for the table is not necessary because the latest is used) config.addProperty("tables.table.fields.field(-1).name", "id"); config.addProperty("tables.table.fields.field.type", "int"); // Add another field to the new table config.addProperty("tables.table.fields.field(-1).name", "date"); config.addProperty("tables.table.fields.field.type", "java.sql.Date"); ...
For more information about adding properties to a hierarchical
configuration also have a look at the javadocs for
Escaping special charactersSome characters in property keys or values require a special treatment. Per default the dot character is used as delimiter by most configuration classes (we will learn how to change this for hierarchical configurations in a later section). In some configuration formats however, dots can be contained in the names of properties. For instance, in XML the dot is a legal character that can occur in any tag. The same is true for the names of properties in windows ini files. So the following XML document is completely valid: <?xml version="1.0" encoding="ISO-8859-1" ?> <configuration> <test.value>42</test.value> <test.complex> <test.sub.element>many dots</test.sub.element> </test.complex> </configuration>
This XML document can be loaded by
Of course, it is the dot character contained in the property
names, which causes this problem. A dot is always interpreted
as a delimiter between elements. So given the property key
int testVal = config.getInt("test..value"); String complex = config.getString("test..complex.test..sub..element"); Note the duplicated dots wherever the dot does not act as delimiter. This way it is possible to access properties containing dots in arbitrary combination. However, as you can see, the escaping can be confusing sometimes. So if you have a choice, you should avoid dots in the tag names of your XML configuration files or other configuration sources.
Another source of problems is related to list delimiter characters
in the values of properties. Like other configuration classes
<?xml version="1.0" encoding="ISO-8859-1" ?> <configuration> <pi>3,1415</pi> </configuration> Here we use the comma as delimiter for fraction digits (as is standard for some languages). However, the configuration will interpret the comma as list delimiter character and assign the property pi the two values 3 and 1415. This was not desired. XML has a natural way of defining list properties by simply repeating elements. So defining multiple values of a property in a single element or attribute is a rather untypical use case. Unfortunately, early versions of Commons Configuration had list delimiter splitting enabled per default. Later it became obvious that this feature can cause serious problems related to the interpretation of property values and the escaping of delimiter characters. For reasons of backwards compatibility we have to stick to this approach in the 1.x series though.
In the next major release the handling of lists will probably be
reworked. Therefore it is recommended not to use this feature.
You are save if you disable it immediately after the creation of
an XMLConfiguration config = new XMLConfiguration(); config.setDelimiterParsingDisabled(true); config.setAttributeSplittingDisabled(true); config.load("config.xml"); Expression engines
In the previous chapters we saw many examples about how properties
in a
The separation of the task of interpreting property keys into a
helper object is a typical application of the Strategy
design pattern. In this case it also has the advantage that it
becomes possible to plug in different expression engines into a
Before we discuss the available expression engines that ship
with Commons Configuration, it should be explained how an
expression engine can be associated with a configuration object.
In addition to instance specific expression engines that change the
behavior of single configuration objects it is also possible to set
a global expression engine. This engine is shared between all
hierarchical configuration objects, for which no specific expression
engine was set. The global expression engine can be set using the
static The default expression engine
The syntax described so far for property keys of hierarchical
configurations is implemented by a specific implementation of the
After reading the examples of property keys provided so far in
this document you should have a sound understanding regarding
the features and the syntax supported by the
DefaultExpressionEngine engine = new DefaultExpressionEngine(); // Use a slash as property delimiter engine.setPropertyDelimiter("/"); // Indices should be provided in curly brackets engine.setIndexStart("{"); engine.setIndexEnd("}"); // For attributes use simply a @ engine.setAttributeStart("@"); engine.setAttributeEnd(null); // A Backslash is used for escaping property delimiters engine.setEscapedDelimiter("\\/"); // Now install this engine as the global engine HierarchicalConfiguration.setDefaultExpressionEngine(engine); // Access properties using the new syntax HierarchicalConfiguration config = ... String tableName = config.getString("tables/table{0}/name"); String tableType = config.getString("tables/table{0}@type");
Tip: Sometimes when processing an XML document you
don't want to distinguish between attributes and "normal"
child nodes. You can achieve this by setting the
DefaultExpressionEngine engine = new DefaultExpressionEngine(); engine.setAttributeEnd(null); engine.setAttributeStart(engine.getPropertyDelimiter()); ... Object value = config.getProperty("tables.table(0).name"); // name can either be a child node of table or an attribute The XPATH expression engine
The expression language provided by the
Think about our example configuration that stores information about
database tables. A use case could be to load all fields that belong
to the "users" table. If you knew the index of this
table, you could simply build a property key like
Life would be much easier if an expression language could be used,
which would directly support queries of such kind. In the XML
world, the XPATH syntax has grown popular as a powerful means
of querying structured data. In XPATH a query that selects all
field names of the "users" table would look something
like Given the power of XPATH it is no wonder that we got many user requests to add XPATH support to Commons Configuration. Well, here is it!
For enabling XPATH syntax for property keys you need the
HierarchicalConfiguration config = ... config.setExpressionEngine(new XPathExpressionEngine()); // Now we can use XPATH queries: List<Object> fields = config.getList("tables/table[1]/fields/name");
XPATH expressions are not only used for selecting properties
(i.e. for the several getter methods), but also for adding new
properties. For this purpose the keys passed into the
Okay, let's make an example. Say, we want to add a config.addProperty("tables/table[1] type", "system");
(Note that indices in XPATH are 1-based, while in the default
expression language they are 0-based.) In this example the part
config.addProperty("tables/table[1] @type", "system"); It is possible to add complete paths at once. Then the single elements in the new path are separated by "/" characters. The following example shows how data about a new table can be added to the configuration. Here we use full paths: // Add new table "tasks" with name element and type attribute config.addProperty("tables table/name", "tasks"); // last() selects the last element of this name, // which is the newest table element config.addProperty("tables/table[last()] @type", "system"); // Now add fields config.addProperty("tables/table[last()] fields/field/name", "taskid"); config.addProperty("tables/table[last()]/fields/field[last()] type", "int"); config.addProperty("tables/table[last()]/fields field/name", "name"); config.addProperty("tables/table[last()]/fields field/name", "startDate"); ...
The first line of this example adds the path
There is one gotcha with these keys described so far: they do
not work with the
To make this clearer let's go back to the example with the
tables. Consider that there is a configuration which already
contains information about some database tables. In order to add
a new table element in the configuration
config.addProperty("tables/table/name", "documents");
In the configuration a config.addProperty("tables table/name", "documents");
Now it is clear that new nodes should be added as children of
the Note: XPATH support is implemented through Commons JXPath. So when making use of this feature, be sure you include the commons-jxpath jar in your classpath. In this tutorial we don't want to describe XPATH syntax and expressions in detail. Please refer to corresponding documentation. It is important to mention that by embedding Commons JXPath the full extent of the XPATH 1.0 standard can be used for constructing property keys. Validation of XML configuration files
XML parsers provide support for validation of XML documents to ensure that they
conform to a certain DTD or XML Schema. This feature can be useful for
configuration files, too. Validation using a DTD
The easiest way to turn on validation is to simply set the
XMLConfiguration config = new XMLConfiguration(); config.setFileName("myconfig.xml"); config.setValidating(true); // This will throw a ConfigurationException if the XML document does not // conform to its DTD. config.load();
Setting the Validation using a Schema
XML Parsers also provide support for validating XML documents using an
XML Schema. XMLConfiguration provides a simple mechanism for enabling
this by setting the
XMLConfiguration config = new XMLConfiguration(); config.setFileName("myconfig.xml"); config.setSchemaValidation(true); // This will throw a ConfigurationException if the XML document does not // conform to its Schema. config.load(); Default Entity Resolution
There is also some support for dealing with DTD files. Often the
DTD of an XML document is stored locally so that it can be quickly
accessed. However the <?xml version="1.0" encoding="ISO-8859-1"?> <!DOCTYPE web-app PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN" "http://java.sun.com/j2ee/dtds/web-app_2.2.dtd">
When working with XML documents directly you would use an
XMLConfiguration config = new XMLConfiguration(); // load the URL to the DTD file from class path URL dtdURL = getClass().getResource("web-app_2.2.dtd"); // register it at the configuration config.registerEntityId("-//Sun Microsystems, Inc.//DTD Web Application 2.2//EN", dtdURL); config.setValidating(true); // enable validation config.setFileName("web.xml"); config.load();
This basically tells the XML configuration to use the specified
URL when it encounters the given public ID. Note that the call to
Enhanced Entity ResolutionWhile the default entity resolver can be used under certain circumstances, it does not work well when using the DefaultConfigurationBuilder. Furthermore, in many circumstances the programmatic nature of registering entities will tie the application tightly to the XML content. In addition, because it only works with the public id it cannot support XML documents using an XML Schema. XML Entity and URI Resolvers describes using a set of catalog files to resolve entities. Commons Configuration provides support for this Catalog Resolver through its own CatalogResolver class. <?xml version="1.0" encoding="ISO-8859-1"?> <Employees xmlns="https://commons.apache.org/employee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="https://commons.apache.org/employee https://commons.apache.org/sample.xsd"> <Employee> <SSN>555121211</SSN> <Name>John Doe</Name> <DateOfBirth>1975-05-15</DateOfBirth> <EmployeeType>Exempt</EmployeeType> <Salary>100000</Salary> </Employee> </Employees> The XML sample above is an XML document using a default namespace of https://commons.apache.org/employee. The schemaLocation allows a set of namespaces and hints to the location of their corresponding schemas. When processing the document the parser will pass the hint, in this case https://commons.apache.org/sample.xsd, to the entity resolver as the system id. More information on using schema locations can be found at schemaLocation. The example that follows shows how to use the CatalogResolver when processing an XMLConfiguration. It should be noted that by using the setEntityResolver method any EntityResolver may be used, not just those provided by Commons Configuration. CatalogResolver resolver = new CatalogResolver(); resolver.setCatalogFiles("local/catalog.xml","http://test.org/catalogs/catalog1.xml"); XMLConfiguration config = new XMLConfiguration(); config.setEntityResolver(resolver); config.setSchemaValidation(true); // enable schema validation config.setFileName("config.xml"); config.load(); Extending Validation and Entity Resolution
The mechanisms provided with Commons Configuration will hopefully be
sufficient in most cases, however there will certainly be circumstances
where they are not. XMLConfiguration provides two extension mechanisms
that should provide applications with all the flexibility they may
need. The first, registering a custom Entity Resolver has already been
discussed in the preceding section. The second is that XMLConfiguration
provides a generic way of setting up the XML parser to use: A preconfigured
So an application can create a |