Author: oheger
Date: Wed Apr 7 20:22:21 2010
New Revision: 931667
URL: http://svn.apache.org/viewvc?rev=931667&view=rev
Log:
CONFIGURATION-412: DatabaseConfiguration can now be instructed to perform
commits after database updates. Ported fix to configuration2 branch.
Modified:
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/DatabaseConfiguration.java
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/DatabaseConfigurationTestHelper.java
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDatabaseConfiguration.java
commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml
Modified:
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/DatabaseConfiguration.java
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/DatabaseConfiguration.java?rev=931667&r1=931666&r2=931667&view=diff
==============================================================================
---
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/DatabaseConfiguration.java
(original)
+++
commons/proper/configuration/branches/configuration2_experimental/src/main/java/org/apache/commons/configuration2/DatabaseConfiguration.java
Wed Apr 7 20:22:21 2010
@@ -26,11 +26,11 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
-import org.apache.commons.logging.LogFactory;
import javax.sql.DataSource;
import org.apache.commons.configuration2.flat.AbstractFlatConfiguration;
+import org.apache.commons.logging.LogFactory;
/**
* Configuration stored in a database. The properties are retrieved from a
@@ -99,22 +99,25 @@ public class DatabaseConfiguration exten
private static final String SQL_GET_KEYS = "SELECT DISTINCT %s FROM %s
WHERE 1 = 1";
/** The datasource to connect to the database. */
- private DataSource datasource;
+ private final DataSource datasource;
/** The name of the table containing the configurations. */
- private String table;
+ private final String table;
/** The column containing the name of the configuration. */
- private String nameColumn;
+ private final String nameColumn;
/** The column containing the keys. */
- private String keyColumn;
+ private final String keyColumn;
/** The column containing the values. */
- private String valueColumn;
+ private final String valueColumn;
/** The name of the configuration. */
- private String name;
+ private final String name;
+
+ /** A flag whether commits should be performed by this configuration. */
+ private final boolean doCommits;
/**
* Build a configuration from a table containing multiple configurations.
@@ -129,18 +132,39 @@ public class DatabaseConfiguration exten
public DatabaseConfiguration(DataSource datasource, String table, String
nameColumn,
String keyColumn, String valueColumn, String name)
{
+ this(datasource, table, nameColumn, keyColumn, valueColumn, name,
false);
+ }
+
+ /**
+ * Creates a new instance of {...@code DatabaseConfiguration} that
operates on
+ * a database table containing multiple configurations.
+ *
+ * @param datasource the <code>DataSource</code> to connect to the database
+ * @param table the name of the table containing the configurations
+ * @param nameColumn the column containing the name of the configuration
+ * @param keyColumn the column containing the keys of the configuration
+ * @param valueColumn the column containing the values of the configuration
+ * @param name the name of the configuration
+ * @param commits a flag whether the configuration should perform a commit
+ * after a database update
+ */
+ public DatabaseConfiguration(DataSource datasource, String table,
+ String nameColumn, String keyColumn, String valueColumn,
+ String name, boolean commits)
+ {
this.datasource = datasource;
this.table = table;
this.nameColumn = nameColumn;
this.keyColumn = keyColumn;
this.valueColumn = valueColumn;
this.name = name;
- setLogger(LogFactory.getLog(getClass().getName()));
+ doCommits = commits;
+ setLogger(LogFactory.getLog(getClass()));
addErrorLogListener(); // log errors per default
}
/**
- * Build a configuration from a table.-
+ * Build a configuration from a table.
*
* @param datasource the datasource to connect to the database
* @param table the name of the table containing the configurations
@@ -153,6 +177,35 @@ public class DatabaseConfiguration exten
}
/**
+ * Creates a new instance of {...@code DatabaseConfiguration} that
+ * operates on a database table containing a single configuration only.
+ *
+ * @param datasource the <code>DataSource</code> to connect to the database
+ * @param table the name of the table containing the configurations
+ * @param keyColumn the column containing the keys of the configuration
+ * @param valueColumn the column containing the values of the configuration
+ * @param name the name of the configuration
+ * @param commits a flag whether the configuration should perform a commit
+ * after a database update
+ */
+ public DatabaseConfiguration(DataSource datasource, String table,
+ String keyColumn, String valueColumn, boolean commits)
+ {
+ this(datasource, table, null, keyColumn, valueColumn, null, commits);
+ }
+
+ /**
+ * Returns a flag whether this configuration performs commits after
database
+ * updates.
+ *
+ * @return a flag whether commits are performed
+ */
+ public boolean isDoCommits()
+ {
+ return doCommits;
+ }
+
+ /**
* Returns the value of the specified property. If this causes a database
* error, an error event will be generated of type
* <code>EVENT_READ_PROPERTY</code> with the causing exception. The
@@ -162,6 +215,7 @@ public class DatabaseConfiguration exten
* @param key the key of the desired property
* @return the value of this property
*/
+ @Override
public Object getProperty(final String key)
{
JdbcOperation op = new JdbcOperation(EVENT_READ_PROPERTY, key, null)
@@ -288,6 +342,7 @@ public class DatabaseConfiguration exten
*
* @return a flag whether this configuration is empty.
*/
+ @Override
public boolean isEmpty()
{
JdbcOperation op = new JdbcOperation(EVENT_READ_PROPERTY, null, null)
@@ -317,6 +372,7 @@ public class DatabaseConfiguration exten
* @param key the key to be checked
* @return a flag whether this key is defined
*/
+ @Override
public boolean containsKey(final String key)
{
JdbcOperation op = new JdbcOperation(EVENT_READ_PROPERTY, key, null)
@@ -393,6 +449,7 @@ public class DatabaseConfiguration exten
* @return an iterator with the contained keys (an empty iterator in case
of
* an error)
*/
+ @Override
public Iterator<String> getKeys()
{
final Collection<String> keys = new ArrayList<String>();
@@ -518,6 +575,11 @@ public class DatabaseConfiguration exten
{
conn = getDatasource().getConnection();
result = performOperation();
+
+ if (isDoCommits())
+ {
+ conn.commit();
+ }
}
catch (SQLException e)
{
Modified:
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/DatabaseConfigurationTestHelper.java
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/DatabaseConfigurationTestHelper.java?rev=931667&r1=931666&r2=931667&view=diff
==============================================================================
---
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/DatabaseConfigurationTestHelper.java
(original)
+++
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/DatabaseConfigurationTestHelper.java
Wed Apr 7 20:22:21 2010
@@ -70,12 +70,40 @@ public class DatabaseConfigurationTestHe
public static final String CONFIG_NAME = "test";
/** Stores the in-process database. */
- private static HsqlDB hsqlDB = null;
+ private HsqlDB hsqlDB;
/** The data source. */
private PotentialErrorDataSource datasource;
/**
+ * The auto-commit mode for the connections created by the managed data
+ * source.
+ */
+ private boolean autoCommit = true;
+
+ /**
+ * Returns the auto-commit mode of the connections created by the managed
+ * data source.
+ *
+ * @return the auto-commit mode
+ */
+ public boolean isAutoCommit()
+ {
+ return autoCommit;
+ }
+
+ /**
+ * Sets the auto-commit mode of the connections created by the managed data
+ * source.
+ *
+ * @param autoCommit the auto-commit mode
+ */
+ public void setAutoCommit(boolean autoCommit)
+ {
+ this.autoCommit = autoCommit;
+ }
+
+ /**
* Initializes this helper object. This method can be called from a
* <code>setUp()</code> method of a unit test class. It creates the
database
* instance if necessary and populates it with test data.
@@ -84,35 +112,9 @@ public class DatabaseConfigurationTestHe
*/
public void setUp() throws Exception
{
- if (hsqlDB == null)
- {
- File script = ConfigurationAssert.getTestFile("testdb.script");
- hsqlDB = new HsqlDB(DATABASE_URL, DATABASE_DRIVER, script
- .getAbsolutePath());
- }
-
- PotentialErrorDataSource datasource = new PotentialErrorDataSource();
- datasource.setDriverClassName(DATABASE_DRIVER);
- datasource.setUrl(DATABASE_URL);
- datasource.setUsername(DATABASE_USERNAME);
- datasource.setPassword(DATABASE_PASSWORD);
-
- this.datasource = datasource;
-
- // prepare the database
- IDatabaseConnection connection = new DatabaseConnection(datasource
- .getConnection());
- IDataSet dataSet = new XmlDataSet(new FileInputStream(
- ConfigurationAssert.getTestFile("dataset.xml")));
-
- try
- {
- DatabaseOperation.CLEAN_INSERT.execute(connection, dataSet);
- }
- finally
- {
- connection.close();
- }
+ File script = ConfigurationAssert.getTestFile("testdb.script");
+ hsqlDB = new HsqlDB(DATABASE_URL, DATABASE_DRIVER, script
+ .getAbsolutePath());
}
/**
@@ -123,8 +125,8 @@ public class DatabaseConfigurationTestHe
*/
public void tearDown() throws Exception
{
- datasource.getConnection().commit();
- datasource.getConnection().close();
+ datasource.close();
+ hsqlDB.close();
}
/**
@@ -134,7 +136,8 @@ public class DatabaseConfigurationTestHe
*/
public DatabaseConfiguration setUpConfig()
{
- return new DatabaseConfiguration(datasource, TABLE, COL_KEY,
COL_VALUE);
+ return new DatabaseConfiguration(getDatasource(), TABLE, COL_KEY,
+ COL_VALUE, !isAutoCommit());
}
/**
@@ -145,21 +148,84 @@ public class DatabaseConfigurationTestHe
*/
public DatabaseConfiguration setUpMultiConfig()
{
- return new DatabaseConfiguration(datasource, TABLE_MULTI, COL_NAME,
- COL_KEY, COL_VALUE, CONFIG_NAME);
+ return setUpMultiConfig(CONFIG_NAME);
}
/**
- * Returns the <code>DataSource</code> managed by this class.
+ * Creates a database configuration that supports multiple configurations
in
+ * a table and sets the specified configuration name.
+ *
+ * @param configName the name of the configuration
+ * @return the configuration
+ */
+ public DatabaseConfiguration setUpMultiConfig(String configName)
+ {
+ return new DatabaseConfiguration(getDatasource(), TABLE_MULTI,
+ COL_NAME, COL_KEY, COL_VALUE, configName, !isAutoCommit());
+ }
+
+ /**
+ * Returns the <code>DataSource</code> managed by this class. The data
+ * source is created on first access.
*
* @return the <code>DataSource</code>
*/
public PotentialErrorDataSource getDatasource()
{
+ if (datasource == null)
+ {
+ try
+ {
+ datasource = setUpDataSource();
+ }
+ catch (Exception ex)
+ {
+ throw new ConfigurationRuntimeException(
+ "Could not create data source", ex);
+ }
+ }
return datasource;
}
/**
+ * Creates the internal data source. This method also initializes the
+ * database.
+ *
+ * @return the data source
+ * @throws Exception if an error occurs
+ */
+ private PotentialErrorDataSource setUpDataSource() throws Exception
+ {
+ PotentialErrorDataSource ds = new PotentialErrorDataSource();
+ ds.setDriverClassName(DATABASE_DRIVER);
+ ds.setUrl(DATABASE_URL);
+ ds.setUsername(DATABASE_USERNAME);
+ ds.setPassword(DATABASE_PASSWORD);
+ ds.setDefaultAutoCommit(isAutoCommit());
+
+ // prepare the database
+ Connection conn = ds.getConnection();
+ IDatabaseConnection connection = new DatabaseConnection(conn);
+ IDataSet dataSet = new XmlDataSet(new FileInputStream(
+ ConfigurationAssert.getTestFile("dataset.xml")));
+
+ try
+ {
+ DatabaseOperation.CLEAN_INSERT.execute(connection, dataSet);
+ }
+ finally
+ {
+ if (!isAutoCommit())
+ {
+ conn.commit();
+ }
+ connection.close();
+ }
+
+ return ds;
+ }
+
+ /**
* A specialized DataSource implementation that can be configured to throw
* an exception when obtaining a connection. This way database exceptions
* can be simulated.
Modified:
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDatabaseConfiguration.java
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDatabaseConfiguration.java?rev=931667&r1=931666&r2=931667&view=diff
==============================================================================
---
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDatabaseConfiguration.java
(original)
+++
commons/proper/configuration/branches/configuration2_experimental/src/test/java/org/apache/commons/configuration2/TestDatabaseConfiguration.java
Wed Apr 7 20:22:21 2010
@@ -44,6 +44,9 @@ import org.codehaus.spice.jndikit.memory
*/
public class TestDatabaseConfiguration extends TestCase
{
+ /** Constant for another configuration name. */
+ private static final String CONFIG_NAME2 = "anotherTestConfig";
+
/** An error listener for testing whether internal errors occurred.*/
private ConfigurationErrorListenerImpl listener;
@@ -76,27 +79,6 @@ public class TestDatabaseConfiguration e
}
/**
- * Creates a database configuration with default values.
- *
- * @return the configuration
- */
- private DatabaseConfiguration setUpConfig()
- {
- return helper.setUpConfig();
- }
-
- /**
- * Creates a database configuration that supports multiple configurations
in
- * a table with default values.
- *
- * @return the configuration
- */
- private DatabaseConfiguration setUpMultiConfig()
- {
- return helper.setUpMultiConfig();
- }
-
- /**
* Creates an error listener and adds it to the specified configuration.
*
* @param config the configuration
@@ -118,7 +100,7 @@ public class TestDatabaseConfiguration e
*/
private DatabaseConfiguration setUpErrorConfig()
{
- DatabaseConfiguration config = setUpConfig();
+ DatabaseConfiguration config = helper.setUpConfig();
setUpErrorListener(config);
return config;
}
@@ -139,17 +121,55 @@ public class TestDatabaseConfiguration e
listener = null; // mark as checked
}
+ /**
+ * Tests the default value of the doCommits property.
+ */
+ public void testDoCommitsDefault()
+ {
+ DatabaseConfiguration config = new DatabaseConfiguration(helper
+ .getDatasource(), DatabaseConfigurationTestHelper.TABLE,
+ DatabaseConfigurationTestHelper.COL_KEY,
+ DatabaseConfigurationTestHelper.COL_VALUE);
+ assertFalse("Wrong commits flag", config.isDoCommits());
+ }
+
+ /**
+ * Tests the default value of the doCommits property for multiple
+ * configurations in a table.
+ */
+ public void testDoCommitsDefaultMulti()
+ {
+ DatabaseConfiguration config = new DatabaseConfiguration(helper
+ .getDatasource(), DatabaseConfigurationTestHelper.TABLE,
+ DatabaseConfigurationTestHelper.COL_NAME,
+ DatabaseConfigurationTestHelper.COL_KEY,
+ DatabaseConfigurationTestHelper.COL_VALUE,
+ DatabaseConfigurationTestHelper.CONFIG_NAME);
+ assertFalse("Wrong commits flag", config.isDoCommits());
+ }
+
public void testAddPropertyDirectSingle()
{
- DatabaseConfiguration config = setUpConfig();
+ DatabaseConfiguration config = helper.setUpConfig();
config.addPropertyDirect("key", "value");
assertTrue("missing property", config.containsKey("key"));
}
+ /**
+ * Tests whether a commit is performed after a property was added.
+ */
+ public void testAddPropertyDirectCommit()
+ {
+ helper.setAutoCommit(false);
+ DatabaseConfiguration config = helper.setUpConfig();
+ config.addPropertyDirect("key", "value");
+ assertTrue("missing property", config.containsKey("key"));
+ }
+
public void testAddPropertyDirectMultiple()
{
- DatabaseConfiguration config = setUpMultiConfig();
+ DatabaseConfiguration config = helper.setUpMultiConfig();
config.addPropertyDirect("key", "value");
assertTrue("missing property", config.containsKey("key"));
@@ -157,7 +177,7 @@ public class TestDatabaseConfiguration e
public void testAddNonStringProperty()
{
- DatabaseConfiguration config = setUpConfig();
+ DatabaseConfiguration config = helper.setUpConfig();
config.addPropertyDirect("boolean", Boolean.TRUE);
assertTrue("missing property", config.containsKey("boolean"));
@@ -165,7 +185,7 @@ public class TestDatabaseConfiguration e
public void testGetPropertyDirectSingle()
{
- Configuration config = setUpConfig();
+ Configuration config = helper.setUpConfig();
assertEquals("property1", "value1", config.getProperty("key1"));
assertEquals("property2", "value2", config.getProperty("key2"));
@@ -174,7 +194,7 @@ public class TestDatabaseConfiguration e
public void testGetPropertyDirectMultiple()
{
- Configuration config = setUpMultiConfig();
+ Configuration config = helper.setUpMultiConfig();
assertEquals("property1", "value1", config.getProperty("key1"));
assertEquals("property2", "value2", config.getProperty("key2"));
@@ -183,23 +203,49 @@ public class TestDatabaseConfiguration e
public void testClearPropertySingle()
{
- Configuration config = setUpConfig();
- config.clearProperty("key");
+ Configuration config = helper.setUpConfig();
+ config.clearProperty("key1");
- assertFalse("property not cleared", config.containsKey("key"));
+ assertFalse("property not cleared", config.containsKey("key1"));
}
public void testClearPropertyMultiple()
{
- Configuration config = setUpMultiConfig();
- config.clearProperty("key");
+ Configuration config = helper.setUpMultiConfig();
+ config.clearProperty("key1");
- assertFalse("property not cleared", config.containsKey("key"));
+ assertFalse("property not cleared", config.containsKey("key1"));
+ }
+
+ /**
+ * Tests that another configuration is not affected when clearing
+ * properties.
+ */
+ public void testClearPropertyMultipleOtherConfig()
+ {
+ DatabaseConfiguration config = helper.setUpMultiConfig();
+ DatabaseConfiguration config2 = helper.setUpMultiConfig(CONFIG_NAME2);
+ config2.addProperty("key1", "some test");
+ config.clearProperty("key1");
+ assertFalse("property not cleared", config.containsKey("key1"));
+ assertTrue("Property cleared in other config", config2
+ .containsKey("key1"));
+ }
+
+ /**
+ * Tests whether a commit is performed after a property was cleared.
+ */
+ public void testClearPropertyCommit()
+ {
+ helper.setAutoCommit(false);
+ Configuration config = helper.setUpConfig();
+ config.clearProperty("key1");
+ assertFalse("property not cleared", config.containsKey("key1"));
}
public void testClearSingle()
{
- Configuration config = setUpConfig();
+ Configuration config = helper.setUpConfig();
config.clear();
assertTrue("configuration is not cleared", config.isEmpty());
@@ -207,15 +253,26 @@ public class TestDatabaseConfiguration e
public void testClearMultiple()
{
- Configuration config = setUpMultiConfig();
+ Configuration config = helper.setUpMultiConfig();
config.clear();
assertTrue("configuration is not cleared", config.isEmpty());
}
+ /**
+ * Tests whether a commit is performed after a clear operation.
+ */
+ public void testClearCommit()
+ {
+ helper.setAutoCommit(false);
+ Configuration config = helper.setUpConfig();
+ config.clear();
+ assertTrue("configuration is not cleared", config.isEmpty());
+ }
+
public void testGetKeysSingle()
{
- Configuration config = setUpConfig();
+ Configuration config = helper.setUpConfig();
Iterator<?> it = config.getKeys();
assertEquals("1st key", "key1", it.next());
@@ -224,7 +281,7 @@ public class TestDatabaseConfiguration e
public void testGetKeysMultiple()
{
- Configuration config = setUpMultiConfig();
+ Configuration config = helper.setUpMultiConfig();
Iterator<?> it = config.getKeys();
assertEquals("1st key", "key1", it.next());
@@ -233,27 +290,27 @@ public class TestDatabaseConfiguration e
public void testContainsKeySingle()
{
- Configuration config = setUpConfig();
+ Configuration config = helper.setUpConfig();
assertTrue("missing key1", config.containsKey("key1"));
assertTrue("missing key2", config.containsKey("key2"));
}
public void testContainsKeyMultiple()
{
- Configuration config = setUpMultiConfig();
+ Configuration config = helper.setUpMultiConfig();
assertTrue("missing key1", config.containsKey("key1"));
assertTrue("missing key2", config.containsKey("key2"));
}
public void testIsEmptySingle()
{
- Configuration config1 = setUpConfig();
+ Configuration config1 = helper.setUpConfig();
assertFalse("The configuration is empty", config1.isEmpty());
}
public void testIsEmptyMultiple()
{
- Configuration config1 = setUpMultiConfig();
+ Configuration config1 = helper.setUpMultiConfig();
assertFalse("The configuration named 'test' is empty", config1
.isEmpty());
@@ -291,7 +348,7 @@ public class TestDatabaseConfiguration e
public void testClearSubset()
{
- Configuration config = setUpConfig();
+ Configuration config = helper.setUpConfig();
Configuration subset = config.subset("key1");
subset.clear();
@@ -384,7 +441,7 @@ public class TestDatabaseConfiguration e
*/
public void testGetListWithDelimiter()
{
- DatabaseConfiguration config = setUpConfig();
+ DatabaseConfiguration config = helper.setUpConfig();
config.setListDelimiter(';');
List<?> values = config.getList("keyMulti");
assertEquals("Wrong number of list elements", 3, values.size());
@@ -398,7 +455,7 @@ public class TestDatabaseConfiguration e
*/
public void testGetListWithDelimiterParsingDisabled()
{
- DatabaseConfiguration config = setUpConfig();
+ DatabaseConfiguration config = helper.setUpConfig();
config.setListDelimiter(';');
config.setDelimiterParsingDisabled(true);
assertEquals("Wrong value of property", "a;b;c",
config.getString("keyMulti"));
@@ -410,7 +467,7 @@ public class TestDatabaseConfiguration e
*/
public void testAddWithDelimiter()
{
- DatabaseConfiguration config = setUpConfig();
+ DatabaseConfiguration config = helper.setUpConfig();
config.setListDelimiter(';');
config.addProperty("keyList", "1;2;3");
String[] values = config.getStringArray("keyList");
@@ -423,7 +480,7 @@ public class TestDatabaseConfiguration e
*/
public void testSetPropertyWithDelimiter()
{
- DatabaseConfiguration config = setUpMultiConfig();
+ DatabaseConfiguration config = helper.setUpMultiConfig();
config.setListDelimiter(';');
config.setProperty("keyList", "1;2;3");
String[] values = config.getStringArray("keyList");
Modified:
commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml
URL:
http://svn.apache.org/viewvc/commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml?rev=931667&r1=931666&r2=931667&view=diff
==============================================================================
---
commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml
(original)
+++
commons/proper/configuration/branches/configuration2_experimental/xdocs/changes.xml
Wed Apr 7 20:22:21 2010
@@ -82,6 +82,11 @@
<action dev="oheger" type="fix" issue="CONFIGURATION-413"
due-to="Alexander Prishchepov">
SubsetConfiguration now produces correct events.
</action>
+ <action dev="oheger" type="add" issue="CONFIGURATION-412">
+ DatabaseConfiguration can now be instructed to perform a commit after
an
+ update of the managed database table. This makes it usable in
+ environments where the connections do not use auto-commit mode.
+ </action>
<action dev="oheger" type="fix" issue="CONFIGURATION-409">
HierarchicalINIConfiguration now correctly saves sections whose name
contains delimiter characters.