Author: oheger Date: Mon Jan 15 13:23:58 2007 New Revision: 496498 URL: http://svn.apache.org/viewvc?view=rev&rev=496498 Log: CONFIGURATION-245: DatabaseConfiguration will now generate error events in case on an internal error.
Modified: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/DatabaseConfiguration.java jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestDatabaseConfiguration.java Modified: jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/DatabaseConfiguration.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/DatabaseConfiguration.java?view=diff&rev=496498&r1=496497&r2=496498 ============================================================================== --- jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/DatabaseConfiguration.java (original) +++ jakarta/commons/proper/configuration/trunk/src/java/org/apache/commons/configuration/DatabaseConfiguration.java Mon Jan 15 13:23:58 2007 @@ -79,6 +79,7 @@ this.valueColumn = valueColumn; this.name = name; setLogger(LogFactory.getLog(getClass())); + addErrorLogListener(); // log errors per default } /** @@ -95,7 +96,14 @@ } /** - * [EMAIL PROTECTED] + * 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 + * event's <code>propertyName</code> is set to the passed in property key, + * the <code>propertyValue</code> is undefined. + * + * @param key the key of the desired property + * @return the value of this property */ public Object getProperty(String key) { @@ -113,7 +121,7 @@ try { - conn = datasource.getConnection(); + conn = getConnection(); // bind the parameters pstmt = conn.prepareStatement(query.toString()); @@ -145,7 +153,7 @@ } catch (SQLException e) { - getLogger().error(e.getMessage(), e); + fireError(EVENT_READ_PROPERTY, key, null, e); } finally { @@ -156,7 +164,14 @@ } /** - * [EMAIL PROTECTED] + * Adds a property to this configuration. If this causes a database error, + * an error event will be generated of type <code>EVENT_ADD_PROPERTY</code> + * with the causing exception. The event's <code>propertyName</code> is + * set to the passed in property key, the <code>propertyValue</code> + * points to the passed in value. + * + * @param key the property key + * @param obj the value of the property to add */ protected void addPropertyDirect(String key, Object obj) { @@ -176,7 +191,7 @@ try { - conn = datasource.getConnection(); + conn = getConnection(); // bind the parameters pstmt = conn.prepareStatement(query.toString()); @@ -192,7 +207,7 @@ } catch (SQLException e) { - getLogger().error(e.getMessage(), e); + fireError(EVENT_ADD_PROPERTY, key, obj, e); } finally { @@ -202,7 +217,12 @@ } /** - * [EMAIL PROTECTED] + * Checks if this configuration is empty. If this causes a database error, + * an error event will be generated of type <code>EVENT_READ_PROPERTY</code> + * with the causing exception. Both the event's <code>propertyName</code> + * and <code>propertyValue</code> will be undefined. + * + * @return a flag whether this configuration is empty. */ public boolean isEmpty() { @@ -220,7 +240,7 @@ try { - conn = datasource.getConnection(); + conn = getConnection(); // bind the parameters pstmt = conn.prepareStatement(query.toString()); @@ -238,7 +258,7 @@ } catch (SQLException e) { - getLogger().error(e.getMessage(), e); + fireError(EVENT_READ_PROPERTY, null, null, e); } finally { @@ -250,7 +270,14 @@ } /** - * [EMAIL PROTECTED] + * Checks whether this configuration contains the specified key. If this + * causes a database error, an error event will be generated of type + * <code>EVENT_READ_PROPERTY</code> with the causing exception. The + * event's <code>propertyName</code> will be set to the passed in key, the + * <code>propertyValue</code> will be undefined. + * + * @param key the key to be checked + * @return a flag whether this key is defined */ public boolean containsKey(String key) { @@ -268,7 +295,7 @@ try { - conn = datasource.getConnection(); + conn = getConnection(); // bind the parameters pstmt = conn.prepareStatement(query.toString()); @@ -284,7 +311,7 @@ } catch (SQLException e) { - getLogger().error(e.getMessage(), e); + fireError(EVENT_READ_PROPERTY, key, null, e); } finally { @@ -296,7 +323,13 @@ } /** - * [EMAIL PROTECTED] + * Removes the specified value from this configuration. If this causes a + * database error, an error event will be generated of type + * <code>EVENT_CLEAR_PROPERTY</code> with the causing exception. The + * event's <code>propertyName</code> will be set to the passed in key, the + * <code>propertyValue</code> will be undefined. + * + * @param key the key of the property to be removed */ public void clearProperty(String key) { @@ -312,7 +345,7 @@ try { - conn = datasource.getConnection(); + conn = getConnection(); // bind the parameters pstmt = conn.prepareStatement(query.toString()); @@ -326,7 +359,7 @@ } catch (SQLException e) { - getLogger().error(e.getMessage(), e); + fireError(EVENT_CLEAR_PROPERTY, key, null, e); } finally { @@ -336,7 +369,11 @@ } /** - * [EMAIL PROTECTED] + * Removes all entries from this configuration. If this causes a database + * error, an error event will be generated of type + * <code>EVENT_CLEAR</code> with the causing exception. Both the + * event's <code>propertyName</code> and the <code>propertyValue</code> + * will be undefined. */ public void clear() { @@ -352,7 +389,7 @@ try { - conn = datasource.getConnection(); + conn = getConnection(); // bind the parameters pstmt = conn.prepareStatement(query.toString()); @@ -365,7 +402,7 @@ } catch (SQLException e) { - getLogger().error(e.getMessage(), e); + fireError(EVENT_CLEAR, null, null, e); } finally { @@ -375,7 +412,14 @@ } /** - * [EMAIL PROTECTED] + * Returns an iterator with the names of all properties contained in this + * configuration. If this causes a database + * error, an error event will be generated of type + * <code>EVENT_READ_PROPERTY</code> with the causing exception. Both the + * event's <code>propertyName</code> and the <code>propertyValue</code> + * will be undefined. + * @return an iterator with the contained keys (an empty iterator in case + * of an error) */ public Iterator getKeys() { @@ -393,7 +437,7 @@ try { - conn = datasource.getConnection(); + conn = getConnection(); // bind the parameters pstmt = conn.prepareStatement(query.toString()); @@ -411,7 +455,7 @@ } catch (SQLException e) { - getLogger().error(e.getMessage(), e); + fireError(EVENT_READ_PROPERTY, null, null, e); } finally { @@ -423,6 +467,31 @@ } /** + * Returns the used <code>DataSource</code> object. + * + * @return the data source + * @since 1.4 + */ + public DataSource getDatasource() + { + return datasource; + } + + /** + * Returns a <code>Connection</code> object. This method is called when + * ever the database is to be accessed. This implementation returns a + * connection from the current <code>DataSource</code>. + * + * @return the <code>Connection</code> object to be used + * @throws SQLException if an error occurs + * @since 1.4 + */ + protected Connection getConnection() throws SQLException + { + return getDatasource().getConnection(); + } + + /** * Close a <code>Connection</code> and, <code>Statement</code>. * Avoid closing if null and hide any SQLExceptions that occur. * @@ -447,4 +516,4 @@ getLogger().error(e.getMessage(), e); } } -} +} \ No newline at end of file Modified: jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestDatabaseConfiguration.java URL: http://svn.apache.org/viewvc/jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestDatabaseConfiguration.java?view=diff&rev=496498&r1=496497&r2=496498 ============================================================================== --- jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestDatabaseConfiguration.java (original) +++ jakarta/commons/proper/configuration/trunk/src/test/org/apache/commons/configuration/TestDatabaseConfiguration.java Mon Jan 15 13:23:58 2007 @@ -18,6 +18,7 @@ package org.apache.commons.configuration; import java.io.FileInputStream; +import java.sql.Connection; import java.sql.SQLException; import java.util.Iterator; import java.util.List; @@ -26,6 +27,8 @@ import junit.framework.TestCase; +import org.apache.commons.configuration.event.ConfigurationErrorEvent; +import org.apache.commons.configuration.event.ConfigurationErrorListener; import org.apache.commons.configuration.test.HsqlDB; import org.apache.commons.dbcp.BasicDataSource; import org.dbunit.database.DatabaseConnection; @@ -35,7 +38,7 @@ import org.dbunit.operation.DatabaseOperation; /** - * Test for database stored configurations. Note, when running this Unit + * Test for database stored configurations. Note, when running this Unit * Test in Eclipse it sometimes takes a couple tries. Otherwise you may get * database is already in use by another process errors. * @@ -48,10 +51,31 @@ public final String DATABASE_USERNAME = "sa"; public final String DATABASE_PASSWORD = ""; + /** Constant for the configuration table.*/ + private static final String TABLE = "configuration"; + + /** Constant for the multi configuration table.*/ + private static final String TABLE_MULTI = "configurations"; + + /** Constant for the column with the keys.*/ + private static final String COL_KEY = "key"; + + /** Constant for the column with the values.*/ + private static final String COL_VALUE = "value"; + + /** Constant for the column with the configuration name.*/ + private static final String COL_NAME = "name"; + + /** Constant for the name of the test configuration.*/ + private static final String CONFIG_NAME = "test"; + private static HsqlDB hsqlDB = null; private DataSource datasource; + /** An error listener for testing whether internal errors occurred.*/ + private TestErrorListener listener; + protected void setUp() throws Exception { /* @@ -59,9 +83,9 @@ * use exception. */ //Thread.sleep(1000); - + // set up the datasource - + if (hsqlDB == null) { hsqlDB = new HsqlDB(DATABASE_URL, DATABASE_DRIVER, "conf/testdb.script"); @@ -74,7 +98,7 @@ datasource.setPassword(DATABASE_PASSWORD); this.datasource = datasource; - + // prepare the database IDatabaseConnection connection = new DatabaseConnection(datasource.getConnection()); @@ -89,15 +113,98 @@ connection.close(); } } - - protected void tearDown() throws SQLException{ + + protected void tearDown() throws Exception{ datasource.getConnection().commit(); datasource.getConnection().close(); + + // if an error listener is defined, we check whether an error occurred + if(listener != null) + { + assertEquals("An internal error occurred", 0, listener.errorCount); + } + super.tearDown(); + } + + /** + * Creates a database configuration with default values. + * + * @return the configuration + */ + private PotentialErrorDatabaseConfiguration setUpConfig() + { + return new PotentialErrorDatabaseConfiguration(datasource, TABLE, + COL_KEY, COL_VALUE); + } + + /** + * Creates a database configuration that supports multiple configurations in + * a table with default values. + * + * @return the configuration + */ + private DatabaseConfiguration setUpMultiConfig() + { + return new DatabaseConfiguration(datasource, TABLE_MULTI, COL_NAME, + COL_KEY, COL_VALUE, CONFIG_NAME); + } + + /** + * Creates an error listener and adds it to the specified configuration. + * + * @param config the configuration + */ + private void setUpErrorListener(PotentialErrorDatabaseConfiguration config) + { + // remove log listener to avoid exception longs + config.removeErrorListener((ConfigurationErrorListener) config + .getErrorListeners().iterator().next()); + listener = new TestErrorListener(); + config.addErrorListener(listener); + config.failOnConnect = true; + } + + /** + * Prepares a test for a database error. Sets up a config and registers an + * error listener. + * + * @return the initialized configuration + */ + private PotentialErrorDatabaseConfiguration setUpErrorConfig() + { + PotentialErrorDatabaseConfiguration config = setUpConfig(); + setUpErrorListener(config); + return config; + } + + /** + * Checks the error listener for an expected error. The properties of the + * error event will be compared with the expected values. + * + * @param type the expected type of the error event + * @param key the expected property key + * @param value the expected property value + */ + private void checkErrorListener(int type, String key, Object value) + { + assertEquals("Wrong number of errors", 1, listener.errorCount); + assertEquals("Wrong event type", type, listener.event.getType()); + assertTrue("Wrong event source", + listener.event.getSource() instanceof DatabaseConfiguration); + assertTrue("Wrong exception", + listener.event.getCause() instanceof SQLException); + assertTrue("Wrong property key", (key == null) ? listener.event + .getPropertyName() == null : key.equals(listener.event + .getPropertyName())); + assertTrue("Wrong property value", (value == null) ? listener.event + .getPropertyValue() == null : value.equals(listener.event + .getPropertyValue())); + listener = null; // mark as checked } public void testAddPropertyDirectSingle() { - DatabaseConfiguration config = new DatabaseConfiguration(datasource, "configuration", "key", "value"); + DatabaseConfiguration config = setUpConfig(); config.addPropertyDirect("key", "value"); assertTrue("missing property", config.containsKey("key")); @@ -105,7 +212,7 @@ public void testAddPropertyDirectMultiple() { - DatabaseConfiguration config = new DatabaseConfiguration(datasource, "configurations", "name", "key", "value", "test"); + DatabaseConfiguration config = setUpMultiConfig(); config.addPropertyDirect("key", "value"); assertTrue("missing property", config.containsKey("key")); @@ -113,7 +220,7 @@ public void testAddNonStringProperty() { - DatabaseConfiguration config = new DatabaseConfiguration(datasource, "configuration", "key", "value"); + DatabaseConfiguration config = setUpConfig(); config.addPropertyDirect("boolean", Boolean.TRUE); assertTrue("missing property", config.containsKey("boolean")); @@ -121,7 +228,7 @@ public void testGetPropertyDirectSingle() { - Configuration config = new DatabaseConfiguration(datasource, "configuration", "key", "value"); + Configuration config = setUpConfig(); assertEquals("property1", "value1", config.getProperty("key1")); assertEquals("property2", "value2", config.getProperty("key2")); @@ -130,7 +237,7 @@ public void testGetPropertyDirectMultiple() { - Configuration config = new DatabaseConfiguration(datasource, "configurations", "name", "key", "value", "test"); + Configuration config = setUpMultiConfig(); assertEquals("property1", "value1", config.getProperty("key1")); assertEquals("property2", "value2", config.getProperty("key2")); @@ -139,7 +246,7 @@ public void testClearPropertySingle() { - Configuration config = new DatabaseConfiguration(datasource, "configuration", "key", "value"); + Configuration config = setUpConfig(); config.clearProperty("key"); assertFalse("property not cleared", config.containsKey("key")); @@ -147,7 +254,7 @@ public void testClearPropertyMultiple() { - Configuration config = new DatabaseConfiguration(datasource, "configurations", "name", "key", "value", "test"); + Configuration config = setUpMultiConfig(); config.clearProperty("key"); assertFalse("property not cleared", config.containsKey("key")); @@ -155,7 +262,7 @@ public void testClearSingle() { - Configuration config = new DatabaseConfiguration(datasource, "configuration", "key", "value"); + Configuration config = setUpConfig(); config.clear(); assertTrue("configuration is not cleared", config.isEmpty()); @@ -163,7 +270,7 @@ public void testClearMultiple() { - Configuration config = new DatabaseConfiguration(datasource, "configurations", "name", "key", "value", "test"); + Configuration config = setUpMultiConfig(); config.clear(); assertTrue("configuration is not cleared", config.isEmpty()); @@ -171,7 +278,7 @@ public void testGetKeysSingle() { - Configuration config = new DatabaseConfiguration(datasource, "configuration", "key", "value"); + Configuration config = setUpConfig(); Iterator it = config.getKeys(); assertEquals("1st key", "key1", it.next()); @@ -180,7 +287,7 @@ public void testGetKeysMultiple() { - Configuration config = new DatabaseConfiguration(datasource, "configurations", "name", "key", "value", "test"); + Configuration config = setUpMultiConfig(); Iterator it = config.getKeys(); assertEquals("1st key", "key1", it.next()); @@ -189,43 +296,43 @@ public void testContainsKeySingle() { - Configuration config = new DatabaseConfiguration(datasource, "configuration", "key", "value"); + Configuration config = setUpConfig(); assertTrue("missing key1", config.containsKey("key1")); assertTrue("missing key2", config.containsKey("key2")); } public void testContainsKeyMultiple() { - Configuration config = new DatabaseConfiguration(datasource, "configurations", "name", "key", "value", "test"); + Configuration config = setUpMultiConfig(); assertTrue("missing key1", config.containsKey("key1")); assertTrue("missing key2", config.containsKey("key2")); } public void testIsEmptySingle() { - Configuration config1 = new DatabaseConfiguration(datasource, "configuration", "key", "value"); + Configuration config1 = setUpConfig(); assertFalse("The configuration is empty", config1.isEmpty()); } public void testIsEmptyMultiple() { - Configuration config1 = new DatabaseConfiguration(datasource, "configurations", "name", "key", "value", "test"); + Configuration config1 = setUpMultiConfig(); assertFalse("The configuration named 'test' is empty", config1.isEmpty()); - Configuration config2 = new DatabaseConfiguration(datasource, "configurations", "name", "key", "value", "testIsEmpty"); + Configuration config2 = new DatabaseConfiguration(datasource, TABLE_MULTI, COL_NAME, COL_KEY, COL_VALUE, "testIsEmpty"); assertTrue("The configuration named 'testIsEmpty' is not empty", config2.isEmpty()); } - + public void testGetList() { - Configuration config1 = new DatabaseConfiguration(datasource, "configurationList", "key", "value"); + Configuration config1 = new DatabaseConfiguration(datasource, "configurationList", COL_KEY, COL_VALUE); List list = config1.getList("key3"); assertEquals(3,list.size()); - } - + } + public void testGetKeys() { - Configuration config1 = new DatabaseConfiguration(datasource, "configurationList", "key", "value"); + Configuration config1 = new DatabaseConfiguration(datasource, "configurationList", COL_KEY, COL_VALUE); Iterator i = config1.getKeys(); assertTrue(i.hasNext()); Object key = i.next(); @@ -235,7 +342,7 @@ public void testClearSubset() { - Configuration config = new DatabaseConfiguration(datasource, "configuration", "key", "value"); + Configuration config = setUpConfig(); Configuration subset = config.subset("key1"); subset.clear(); @@ -244,4 +351,133 @@ assertFalse("the parent configuration is empty", config.isEmpty()); } + /** + * Tests whether the configuration has already an error listener registered + * that is used for logging. + */ + public void testLogErrorListener() + { + DatabaseConfiguration config = new DatabaseConfiguration(datasource, + TABLE, COL_KEY, COL_VALUE); + assertEquals("No error listener registered", 1, config + .getErrorListeners().size()); + } + + /** + * Tests handling of errors in getProperty(). + */ + public void testGetPropertyError() + { + setUpErrorConfig().getProperty("key1"); + checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, "key1", + null); + } + + /** + * Tests handling of errors in addPropertyDirect(). + */ + public void testAddPropertyError() + { + setUpErrorConfig().addProperty("key1", "value"); + checkErrorListener(AbstractConfiguration.EVENT_ADD_PROPERTY, "key1", + "value"); + } + + /** + * Tests handling of errors in isEmpty(). + */ + public void testIsEmptyError() + { + assertTrue("Wrong return value for failure", setUpErrorConfig() + .isEmpty()); + checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, null, + null); + } + + /** + * Tests handling of errors in containsKey(). + */ + public void testContainsKeyError() + { + assertFalse("Wrong return value for failure", setUpErrorConfig() + .containsKey("key1")); + checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, "key1", + null); + } + + /** + * Tests handling of errors in clearProperty(). + */ + public void testClearPropertyError() + { + setUpErrorConfig().clearProperty("key1"); + checkErrorListener(AbstractConfiguration.EVENT_CLEAR_PROPERTY, "key1", + null); + } + + /** + * Tests handling of errors in clear(). + */ + public void testClearError() + { + setUpErrorConfig().clear(); + checkErrorListener(AbstractConfiguration.EVENT_CLEAR, null, null); + } + + /** + * Tests handling of errors in getKeys(). + */ + public void testGetKeysError() + { + Iterator it = setUpErrorConfig().getKeys(); + checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, null, + null); + assertFalse("Iteration is not empty", it.hasNext()); + } + + /** + * A specialized database configuration implementation that can be + * configured to throw an exception when obtaining a connection. This way + * database exceptions can be simulated. + */ + static class PotentialErrorDatabaseConfiguration extends + DatabaseConfiguration + { + /** A flag whether a getConnection() call should fail. */ + boolean failOnConnect; + + public PotentialErrorDatabaseConfiguration(DataSource datasource, + String table, String keyColumn, String valueColumn) + { + super(datasource, table, keyColumn, valueColumn); + } + + protected Connection getConnection() throws SQLException + { + if (failOnConnect) + { + throw new SQLException("Simulated DB error"); + } + return super.getConnection(); + } + } + + /** + * A test error listener implementation that is used for finding out whether + * error events are correctly triggered. + */ + static class TestErrorListener implements ConfigurationErrorListener + { + /** Stores the number of calls. */ + int errorCount; + + /** Stores the last error event. */ + ConfigurationErrorEvent event; + + public void configurationError(ConfigurationErrorEvent event) + { + errorCount++; + this.event = event; + } + } } --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]