001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017:
018: package org.apache.commons.configuration;
019:
020: import java.io.FileInputStream;
021: import java.sql.Connection;
022: import java.sql.SQLException;
023: import java.util.Iterator;
024: import java.util.List;
025:
026: import javax.sql.DataSource;
027:
028: import junit.framework.TestCase;
029:
030: import org.apache.commons.configuration.event.ConfigurationErrorEvent;
031: import org.apache.commons.configuration.event.ConfigurationErrorListener;
032: import org.apache.commons.configuration.test.HsqlDB;
033: import org.apache.commons.dbcp.BasicDataSource;
034: import org.dbunit.database.DatabaseConnection;
035: import org.dbunit.database.IDatabaseConnection;
036: import org.dbunit.dataset.IDataSet;
037: import org.dbunit.dataset.xml.XmlDataSet;
038: import org.dbunit.operation.DatabaseOperation;
039:
040: /**
041: * Test for database stored configurations. Note, when running this Unit
042: * Test in Eclipse it sometimes takes a couple tries. Otherwise you may get
043: * database is already in use by another process errors.
044: *
045: * @version $Revision: 514234 $, $Date: 2007-03-03 21:18:14 +0100 (Sa, 03 Mrz 2007) $
046: */
047: public class TestDatabaseConfiguration extends TestCase {
048: public final String DATABASE_DRIVER = "org.hsqldb.jdbcDriver";
049: public final String DATABASE_URL = "jdbc:hsqldb:target/test-classes/testdb";
050: public final String DATABASE_USERNAME = "sa";
051: public final String DATABASE_PASSWORD = "";
052:
053: /** Constant for the configuration table.*/
054: private static final String TABLE = "configuration";
055:
056: /** Constant for the multi configuration table.*/
057: private static final String TABLE_MULTI = "configurations";
058:
059: /** Constant for the column with the keys.*/
060: private static final String COL_KEY = "key";
061:
062: /** Constant for the column with the values.*/
063: private static final String COL_VALUE = "value";
064:
065: /** Constant for the column with the configuration name.*/
066: private static final String COL_NAME = "name";
067:
068: /** Constant for the name of the test configuration.*/
069: private static final String CONFIG_NAME = "test";
070:
071: private static HsqlDB hsqlDB = null;
072:
073: private DataSource datasource;
074:
075: /** An error listener for testing whether internal errors occurred.*/
076: private TestErrorListener listener;
077:
078: protected void setUp() throws Exception {
079: /*
080: * Thread.sleep may or may not help with the database is already in
081: * use exception.
082: */
083: //Thread.sleep(1000);
084: // set up the datasource
085: if (hsqlDB == null) {
086: hsqlDB = new HsqlDB(DATABASE_URL, DATABASE_DRIVER,
087: "conf/testdb.script");
088: }
089:
090: BasicDataSource datasource = new BasicDataSource();
091: datasource.setDriverClassName(DATABASE_DRIVER);
092: datasource.setUrl(DATABASE_URL);
093: datasource.setUsername(DATABASE_USERNAME);
094: datasource.setPassword(DATABASE_PASSWORD);
095:
096: this .datasource = datasource;
097:
098: // prepare the database
099: IDatabaseConnection connection = new DatabaseConnection(
100: datasource.getConnection());
101: IDataSet dataSet = new XmlDataSet(new FileInputStream(
102: "conf/dataset.xml"));
103:
104: try {
105: DatabaseOperation.CLEAN_INSERT.execute(connection, dataSet);
106: } finally {
107: connection.close();
108: }
109: }
110:
111: protected void tearDown() throws Exception {
112: datasource.getConnection().commit();
113: datasource.getConnection().close();
114:
115: // if an error listener is defined, we check whether an error occurred
116: if (listener != null) {
117: assertEquals("An internal error occurred", 0,
118: listener.errorCount);
119: }
120: super .tearDown();
121: }
122:
123: /**
124: * Creates a database configuration with default values.
125: *
126: * @return the configuration
127: */
128: private PotentialErrorDatabaseConfiguration setUpConfig() {
129: return new PotentialErrorDatabaseConfiguration(datasource,
130: TABLE, COL_KEY, COL_VALUE);
131: }
132:
133: /**
134: * Creates a database configuration that supports multiple configurations in
135: * a table with default values.
136: *
137: * @return the configuration
138: */
139: private DatabaseConfiguration setUpMultiConfig() {
140: return new DatabaseConfiguration(datasource, TABLE_MULTI,
141: COL_NAME, COL_KEY, COL_VALUE, CONFIG_NAME);
142: }
143:
144: /**
145: * Creates an error listener and adds it to the specified configuration.
146: *
147: * @param config the configuration
148: */
149: private void setUpErrorListener(
150: PotentialErrorDatabaseConfiguration config) {
151: // remove log listener to avoid exception longs
152: config.removeErrorListener((ConfigurationErrorListener) config
153: .getErrorListeners().iterator().next());
154: listener = new TestErrorListener();
155: config.addErrorListener(listener);
156: config.failOnConnect = true;
157: }
158:
159: /**
160: * Prepares a test for a database error. Sets up a config and registers an
161: * error listener.
162: *
163: * @return the initialized configuration
164: */
165: private PotentialErrorDatabaseConfiguration setUpErrorConfig() {
166: PotentialErrorDatabaseConfiguration config = setUpConfig();
167: setUpErrorListener(config);
168: return config;
169: }
170:
171: /**
172: * Checks the error listener for an expected error. The properties of the
173: * error event will be compared with the expected values.
174: *
175: * @param type the expected type of the error event
176: * @param key the expected property key
177: * @param value the expected property value
178: */
179: private void checkErrorListener(int type, String key, Object value) {
180: assertEquals("Wrong number of errors", 1, listener.errorCount);
181: assertEquals("Wrong event type", type, listener.event.getType());
182: assertTrue(
183: "Wrong event source",
184: listener.event.getSource() instanceof DatabaseConfiguration);
185: assertTrue("Wrong exception",
186: listener.event.getCause() instanceof SQLException);
187: assertTrue("Wrong property key", (key == null) ? listener.event
188: .getPropertyName() == null : key.equals(listener.event
189: .getPropertyName()));
190: assertTrue(
191: "Wrong property value",
192: (value == null) ? listener.event.getPropertyValue() == null
193: : value.equals(listener.event
194: .getPropertyValue()));
195: listener = null; // mark as checked
196: }
197:
198: public void testAddPropertyDirectSingle() {
199: DatabaseConfiguration config = setUpConfig();
200: config.addPropertyDirect("key", "value");
201:
202: assertTrue("missing property", config.containsKey("key"));
203: }
204:
205: public void testAddPropertyDirectMultiple() {
206: DatabaseConfiguration config = setUpMultiConfig();
207: config.addPropertyDirect("key", "value");
208:
209: assertTrue("missing property", config.containsKey("key"));
210: }
211:
212: public void testAddNonStringProperty() {
213: DatabaseConfiguration config = setUpConfig();
214: config.addPropertyDirect("boolean", Boolean.TRUE);
215:
216: assertTrue("missing property", config.containsKey("boolean"));
217: }
218:
219: public void testGetPropertyDirectSingle() {
220: Configuration config = setUpConfig();
221:
222: assertEquals("property1", "value1", config.getProperty("key1"));
223: assertEquals("property2", "value2", config.getProperty("key2"));
224: assertEquals("unknown property", null, config
225: .getProperty("key3"));
226: }
227:
228: public void testGetPropertyDirectMultiple() {
229: Configuration config = setUpMultiConfig();
230:
231: assertEquals("property1", "value1", config.getProperty("key1"));
232: assertEquals("property2", "value2", config.getProperty("key2"));
233: assertEquals("unknown property", null, config
234: .getProperty("key3"));
235: }
236:
237: public void testClearPropertySingle() {
238: Configuration config = setUpConfig();
239: config.clearProperty("key");
240:
241: assertFalse("property not cleared", config.containsKey("key"));
242: }
243:
244: public void testClearPropertyMultiple() {
245: Configuration config = setUpMultiConfig();
246: config.clearProperty("key");
247:
248: assertFalse("property not cleared", config.containsKey("key"));
249: }
250:
251: public void testClearSingle() {
252: Configuration config = setUpConfig();
253: config.clear();
254:
255: assertTrue("configuration is not cleared", config.isEmpty());
256: }
257:
258: public void testClearMultiple() {
259: Configuration config = setUpMultiConfig();
260: config.clear();
261:
262: assertTrue("configuration is not cleared", config.isEmpty());
263: }
264:
265: public void testGetKeysSingle() {
266: Configuration config = setUpConfig();
267: Iterator it = config.getKeys();
268:
269: assertEquals("1st key", "key1", it.next());
270: assertEquals("2nd key", "key2", it.next());
271: }
272:
273: public void testGetKeysMultiple() {
274: Configuration config = setUpMultiConfig();
275: Iterator it = config.getKeys();
276:
277: assertEquals("1st key", "key1", it.next());
278: assertEquals("2nd key", "key2", it.next());
279: }
280:
281: public void testContainsKeySingle() {
282: Configuration config = setUpConfig();
283: assertTrue("missing key1", config.containsKey("key1"));
284: assertTrue("missing key2", config.containsKey("key2"));
285: }
286:
287: public void testContainsKeyMultiple() {
288: Configuration config = setUpMultiConfig();
289: assertTrue("missing key1", config.containsKey("key1"));
290: assertTrue("missing key2", config.containsKey("key2"));
291: }
292:
293: public void testIsEmptySingle() {
294: Configuration config1 = setUpConfig();
295: assertFalse("The configuration is empty", config1.isEmpty());
296: }
297:
298: public void testIsEmptyMultiple() {
299: Configuration config1 = setUpMultiConfig();
300: assertFalse("The configuration named 'test' is empty", config1
301: .isEmpty());
302:
303: Configuration config2 = new DatabaseConfiguration(datasource,
304: TABLE_MULTI, COL_NAME, COL_KEY, COL_VALUE,
305: "testIsEmpty");
306: assertTrue(
307: "The configuration named 'testIsEmpty' is not empty",
308: config2.isEmpty());
309: }
310:
311: public void testGetList() {
312: Configuration config1 = new DatabaseConfiguration(datasource,
313: "configurationList", COL_KEY, COL_VALUE);
314: List list = config1.getList("key3");
315: assertEquals(3, list.size());
316: }
317:
318: public void testGetKeys() {
319: Configuration config1 = new DatabaseConfiguration(datasource,
320: "configurationList", COL_KEY, COL_VALUE);
321: Iterator i = config1.getKeys();
322: assertTrue(i.hasNext());
323: Object key = i.next();
324: assertEquals("key3", key.toString());
325: assertFalse(i.hasNext());
326: }
327:
328: public void testClearSubset() {
329: Configuration config = setUpConfig();
330:
331: Configuration subset = config.subset("key1");
332: subset.clear();
333:
334: assertTrue("the subset is not empty", subset.isEmpty());
335: assertFalse("the parent configuration is empty", config
336: .isEmpty());
337: }
338:
339: /**
340: * Tests whether the configuration has already an error listener registered
341: * that is used for logging.
342: */
343: public void testLogErrorListener() {
344: DatabaseConfiguration config = new DatabaseConfiguration(
345: datasource, TABLE, COL_KEY, COL_VALUE);
346: assertEquals("No error listener registered", 1, config
347: .getErrorListeners().size());
348: }
349:
350: /**
351: * Tests handling of errors in getProperty().
352: */
353: public void testGetPropertyError() {
354: setUpErrorConfig().getProperty("key1");
355: checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY,
356: "key1", null);
357: }
358:
359: /**
360: * Tests handling of errors in addPropertyDirect().
361: */
362: public void testAddPropertyError() {
363: setUpErrorConfig().addProperty("key1", "value");
364: checkErrorListener(AbstractConfiguration.EVENT_ADD_PROPERTY,
365: "key1", "value");
366: }
367:
368: /**
369: * Tests handling of errors in isEmpty().
370: */
371: public void testIsEmptyError() {
372: assertTrue("Wrong return value for failure", setUpErrorConfig()
373: .isEmpty());
374: checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY,
375: null, null);
376: }
377:
378: /**
379: * Tests handling of errors in containsKey().
380: */
381: public void testContainsKeyError() {
382: assertFalse("Wrong return value for failure",
383: setUpErrorConfig().containsKey("key1"));
384: checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY,
385: "key1", null);
386: }
387:
388: /**
389: * Tests handling of errors in clearProperty().
390: */
391: public void testClearPropertyError() {
392: setUpErrorConfig().clearProperty("key1");
393: checkErrorListener(AbstractConfiguration.EVENT_CLEAR_PROPERTY,
394: "key1", null);
395: }
396:
397: /**
398: * Tests handling of errors in clear().
399: */
400: public void testClearError() {
401: setUpErrorConfig().clear();
402: checkErrorListener(AbstractConfiguration.EVENT_CLEAR, null,
403: null);
404: }
405:
406: /**
407: * Tests handling of errors in getKeys().
408: */
409: public void testGetKeysError() {
410: Iterator it = setUpErrorConfig().getKeys();
411: checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY,
412: null, null);
413: assertFalse("Iteration is not empty", it.hasNext());
414: }
415:
416: /**
417: * Tests obtaining a property as list whose value contains the list
418: * delimiter. Multiple values should be returned.
419: */
420: public void testGetListWithDelimiter() {
421: DatabaseConfiguration config = setUpConfig();
422: config.setListDelimiter(';');
423: List values = config.getList("keyMulti");
424: assertEquals("Wrong number of list elements", 3, values.size());
425: assertEquals("Wrong list element 0", "a", values.get(0));
426: assertEquals("Wrong list element 2", "c", values.get(2));
427: }
428:
429: /**
430: * Tests obtaining a property whose value contains the list delimiter when
431: * delimiter parsing is disabled.
432: */
433: public void testGetListWithDelimiterParsingDisabled() {
434: DatabaseConfiguration config = setUpConfig();
435: config.setListDelimiter(';');
436: config.setDelimiterParsingDisabled(true);
437: assertEquals("Wrong value of property", "a;b;c", config
438: .getString("keyMulti"));
439: }
440:
441: /**
442: * Tests adding a property containing the list delimiter. When this property
443: * is queried multiple values should be returned.
444: */
445: public void testAddWithDelimiter() {
446: DatabaseConfiguration config = setUpConfig();
447: config.setListDelimiter(';');
448: config.addProperty("keyList", "1;2;3");
449: String[] values = config.getStringArray("keyList");
450: assertEquals("Wrong number of property values", 3,
451: values.length);
452: assertEquals("Wrong value at index 1", "2", values[1]);
453: }
454:
455: /**
456: * A specialized database configuration implementation that can be
457: * configured to throw an exception when obtaining a connection. This way
458: * database exceptions can be simulated.
459: */
460: static class PotentialErrorDatabaseConfiguration extends
461: DatabaseConfiguration {
462: /** A flag whether a getConnection() call should fail. */
463: boolean failOnConnect;
464:
465: public PotentialErrorDatabaseConfiguration(
466: DataSource datasource, String table, String keyColumn,
467: String valueColumn) {
468: super (datasource, table, keyColumn, valueColumn);
469: }
470:
471: protected Connection getConnection() throws SQLException {
472: if (failOnConnect) {
473: throw new SQLException("Simulated DB error");
474: }
475: return super .getConnection();
476: }
477: }
478:
479: /**
480: * A test error listener implementation that is used for finding out whether
481: * error events are correctly triggered.
482: */
483: static class TestErrorListener implements
484: ConfigurationErrorListener {
485: /** Stores the number of calls. */
486: int errorCount;
487:
488: /** Stores the last error event. */
489: ConfigurationErrorEvent event;
490:
491: public void configurationError(ConfigurationErrorEvent event) {
492: errorCount++;
493: this.event = event;
494: }
495: }
496: }
|