1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.configuration;
19  
20  import java.io.FileInputStream;
21  import java.sql.Connection;
22  import java.sql.SQLException;
23  import java.util.Iterator;
24  import java.util.List;
25  
26  import javax.sql.DataSource;
27  
28  import junit.framework.TestCase;
29  
30  import org.apache.commons.configuration.event.ConfigurationErrorEvent;
31  import org.apache.commons.configuration.event.ConfigurationErrorListener;
32  import org.apache.commons.configuration.test.HsqlDB;
33  import org.apache.commons.dbcp.BasicDataSource;
34  import org.dbunit.database.DatabaseConnection;
35  import org.dbunit.database.IDatabaseConnection;
36  import org.dbunit.dataset.IDataSet;
37  import org.dbunit.dataset.xml.XmlDataSet;
38  import org.dbunit.operation.DatabaseOperation;
39  
40  /***
41   * Test for database stored configurations.  Note, when running this Unit
42   * Test in Eclipse it sometimes takes a couple tries. Otherwise you may get
43   * database is already in use by another process errors.
44   *
45   * @version $Revision: 514234 $, $Date: 2007-03-03 21:18:14 +0100 (Sa, 03 Mrz 2007) $
46   */
47  public class TestDatabaseConfiguration extends TestCase
48  {
49      public final String DATABASE_DRIVER = "org.hsqldb.jdbcDriver";
50      public final String DATABASE_URL = "jdbc:hsqldb:target/test-classes/testdb";
51      public final String DATABASE_USERNAME = "sa";
52      public final String DATABASE_PASSWORD = "";
53  
54      /*** Constant for the configuration table.*/
55      private static final String TABLE = "configuration";
56  
57      /*** Constant for the multi configuration table.*/
58      private static final String TABLE_MULTI = "configurations";
59  
60      /*** Constant for the column with the keys.*/
61      private static final String COL_KEY = "key";
62  
63      /*** Constant for the column with the values.*/
64      private static final String COL_VALUE = "value";
65  
66      /*** Constant for the column with the configuration name.*/
67      private static final String COL_NAME = "name";
68  
69      /*** Constant for the name of the test configuration.*/
70      private static final String CONFIG_NAME = "test";
71  
72      private static HsqlDB hsqlDB = null;
73  
74      private DataSource datasource;
75  
76      /*** An error listener for testing whether internal errors occurred.*/
77      private TestErrorListener listener;
78  
79      protected void setUp() throws Exception
80      {
81          /*
82           * Thread.sleep may or may not help with the database is already in
83           * use exception.
84           */
85          //Thread.sleep(1000);
86  
87          // set up the datasource
88  
89          if (hsqlDB == null)
90          {
91              hsqlDB = new HsqlDB(DATABASE_URL, DATABASE_DRIVER, "conf/testdb.script");
92          }
93  
94          BasicDataSource datasource = new BasicDataSource();
95          datasource.setDriverClassName(DATABASE_DRIVER);
96          datasource.setUrl(DATABASE_URL);
97          datasource.setUsername(DATABASE_USERNAME);
98          datasource.setPassword(DATABASE_PASSWORD);
99  
100         this.datasource = datasource;
101 
102 
103         // prepare the database
104         IDatabaseConnection connection = new DatabaseConnection(datasource.getConnection());
105         IDataSet dataSet = new XmlDataSet(new FileInputStream("conf/dataset.xml"));
106 
107         try
108         {
109             DatabaseOperation.CLEAN_INSERT.execute(connection, dataSet);
110         }
111         finally
112         {
113             connection.close();
114         }
115     }
116 
117     protected void tearDown() throws Exception{
118         datasource.getConnection().commit();
119         datasource.getConnection().close();
120 
121         // if an error listener is defined, we check whether an error occurred
122         if(listener != null)
123         {
124             assertEquals("An internal error occurred", 0, listener.errorCount);
125         }
126         super.tearDown();
127     }
128 
129     /***
130      * Creates a database configuration with default values.
131      *
132      * @return the configuration
133      */
134     private PotentialErrorDatabaseConfiguration setUpConfig()
135     {
136         return new PotentialErrorDatabaseConfiguration(datasource, TABLE,
137                 COL_KEY, COL_VALUE);
138     }
139 
140     /***
141      * Creates a database configuration that supports multiple configurations in
142      * a table with default values.
143      *
144      * @return the configuration
145      */
146     private DatabaseConfiguration setUpMultiConfig()
147     {
148         return new DatabaseConfiguration(datasource, TABLE_MULTI, COL_NAME,
149                 COL_KEY, COL_VALUE, CONFIG_NAME);
150     }
151 
152     /***
153      * Creates an error listener and adds it to the specified configuration.
154      *
155      * @param config the configuration
156      */
157     private void setUpErrorListener(PotentialErrorDatabaseConfiguration config)
158     {
159         // remove log listener to avoid exception longs
160         config.removeErrorListener((ConfigurationErrorListener) config
161                 .getErrorListeners().iterator().next());
162         listener = new TestErrorListener();
163         config.addErrorListener(listener);
164         config.failOnConnect = true;
165     }
166 
167     /***
168      * Prepares a test for a database error. Sets up a config and registers an
169      * error listener.
170      *
171      * @return the initialized configuration
172      */
173     private PotentialErrorDatabaseConfiguration setUpErrorConfig()
174     {
175         PotentialErrorDatabaseConfiguration config = setUpConfig();
176         setUpErrorListener(config);
177         return config;
178     }
179 
180     /***
181      * Checks the error listener for an expected error. The properties of the
182      * error event will be compared with the expected values.
183      *
184      * @param type the expected type of the error event
185      * @param key the expected property key
186      * @param value the expected property value
187      */
188     private void checkErrorListener(int type, String key, Object value)
189     {
190         assertEquals("Wrong number of errors", 1, listener.errorCount);
191         assertEquals("Wrong event type", type, listener.event.getType());
192         assertTrue("Wrong event source",
193                 listener.event.getSource() instanceof DatabaseConfiguration);
194         assertTrue("Wrong exception",
195                 listener.event.getCause() instanceof SQLException);
196         assertTrue("Wrong property key", (key == null) ? listener.event
197                 .getPropertyName() == null : key.equals(listener.event
198                 .getPropertyName()));
199         assertTrue("Wrong property value", (value == null) ? listener.event
200                 .getPropertyValue() == null : value.equals(listener.event
201                 .getPropertyValue()));
202         listener = null; // mark as checked
203     }
204 
205     public void testAddPropertyDirectSingle()
206     {
207         DatabaseConfiguration config = setUpConfig();
208         config.addPropertyDirect("key", "value");
209 
210         assertTrue("missing property", config.containsKey("key"));
211     }
212 
213     public void testAddPropertyDirectMultiple()
214     {
215         DatabaseConfiguration config = setUpMultiConfig();
216         config.addPropertyDirect("key", "value");
217 
218         assertTrue("missing property", config.containsKey("key"));
219     }
220 
221     public void testAddNonStringProperty()
222     {
223         DatabaseConfiguration config = setUpConfig();
224         config.addPropertyDirect("boolean", Boolean.TRUE);
225 
226         assertTrue("missing property", config.containsKey("boolean"));
227     }
228 
229     public void testGetPropertyDirectSingle()
230     {
231         Configuration config = setUpConfig();
232 
233         assertEquals("property1", "value1", config.getProperty("key1"));
234         assertEquals("property2", "value2", config.getProperty("key2"));
235         assertEquals("unknown property", null, config.getProperty("key3"));
236     }
237 
238     public void testGetPropertyDirectMultiple()
239     {
240         Configuration config = setUpMultiConfig();
241 
242         assertEquals("property1", "value1", config.getProperty("key1"));
243         assertEquals("property2", "value2", config.getProperty("key2"));
244         assertEquals("unknown property", null, config.getProperty("key3"));
245     }
246 
247     public void testClearPropertySingle()
248     {
249         Configuration config = setUpConfig();
250         config.clearProperty("key");
251 
252         assertFalse("property not cleared", config.containsKey("key"));
253     }
254 
255     public void testClearPropertyMultiple()
256     {
257         Configuration config = setUpMultiConfig();
258         config.clearProperty("key");
259 
260         assertFalse("property not cleared", config.containsKey("key"));
261     }
262 
263     public void testClearSingle()
264     {
265         Configuration config = setUpConfig();
266         config.clear();
267 
268         assertTrue("configuration is not cleared", config.isEmpty());
269     }
270 
271     public void testClearMultiple()
272     {
273         Configuration config = setUpMultiConfig();
274         config.clear();
275 
276         assertTrue("configuration is not cleared", config.isEmpty());
277     }
278 
279     public void testGetKeysSingle()
280     {
281         Configuration config = setUpConfig();
282         Iterator it = config.getKeys();
283 
284         assertEquals("1st key", "key1", it.next());
285         assertEquals("2nd key", "key2", it.next());
286     }
287 
288     public void testGetKeysMultiple()
289     {
290         Configuration config = setUpMultiConfig();
291         Iterator it = config.getKeys();
292 
293         assertEquals("1st key", "key1", it.next());
294         assertEquals("2nd key", "key2", it.next());
295     }
296 
297     public void testContainsKeySingle()
298     {
299         Configuration config = setUpConfig();
300         assertTrue("missing key1", config.containsKey("key1"));
301         assertTrue("missing key2", config.containsKey("key2"));
302     }
303 
304     public void testContainsKeyMultiple()
305     {
306         Configuration config = setUpMultiConfig();
307         assertTrue("missing key1", config.containsKey("key1"));
308         assertTrue("missing key2", config.containsKey("key2"));
309     }
310 
311     public void testIsEmptySingle()
312     {
313         Configuration config1 = setUpConfig();
314         assertFalse("The configuration is empty", config1.isEmpty());
315     }
316 
317     public void testIsEmptyMultiple()
318     {
319         Configuration config1 = setUpMultiConfig();
320         assertFalse("The configuration named 'test' is empty", config1.isEmpty());
321 
322         Configuration config2 = new DatabaseConfiguration(datasource, TABLE_MULTI, COL_NAME, COL_KEY, COL_VALUE, "testIsEmpty");
323         assertTrue("The configuration named 'testIsEmpty' is not empty", config2.isEmpty());
324     }
325 
326     public void testGetList()
327     {
328         Configuration config1 = new DatabaseConfiguration(datasource, "configurationList", COL_KEY, COL_VALUE);
329         List list = config1.getList("key3");
330         assertEquals(3,list.size());
331     }
332 
333     public void testGetKeys()
334     {
335         Configuration config1 = new DatabaseConfiguration(datasource, "configurationList", COL_KEY, COL_VALUE);
336         Iterator i = config1.getKeys();
337         assertTrue(i.hasNext());
338         Object key = i.next();
339         assertEquals("key3",key.toString());
340         assertFalse(i.hasNext());
341     }
342 
343     public void testClearSubset()
344     {
345         Configuration config = setUpConfig();
346 
347         Configuration subset = config.subset("key1");
348         subset.clear();
349 
350         assertTrue("the subset is not empty", subset.isEmpty());
351         assertFalse("the parent configuration is empty", config.isEmpty());
352     }
353 
354     /***
355      * Tests whether the configuration has already an error listener registered
356      * that is used for logging.
357      */
358     public void testLogErrorListener()
359     {
360         DatabaseConfiguration config = new DatabaseConfiguration(datasource,
361                 TABLE, COL_KEY, COL_VALUE);
362         assertEquals("No error listener registered", 1, config
363                 .getErrorListeners().size());
364     }
365 
366     /***
367      * Tests handling of errors in getProperty().
368      */
369     public void testGetPropertyError()
370     {
371         setUpErrorConfig().getProperty("key1");
372         checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, "key1",
373                 null);
374     }
375 
376     /***
377      * Tests handling of errors in addPropertyDirect().
378      */
379     public void testAddPropertyError()
380     {
381         setUpErrorConfig().addProperty("key1", "value");
382         checkErrorListener(AbstractConfiguration.EVENT_ADD_PROPERTY, "key1",
383                 "value");
384     }
385 
386     /***
387      * Tests handling of errors in isEmpty().
388      */
389     public void testIsEmptyError()
390     {
391         assertTrue("Wrong return value for failure", setUpErrorConfig()
392                 .isEmpty());
393         checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, null,
394                 null);
395     }
396 
397     /***
398      * Tests handling of errors in containsKey().
399      */
400     public void testContainsKeyError()
401     {
402         assertFalse("Wrong return value for failure", setUpErrorConfig()
403                 .containsKey("key1"));
404         checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, "key1",
405                 null);
406     }
407 
408     /***
409      * Tests handling of errors in clearProperty().
410      */
411     public void testClearPropertyError()
412     {
413         setUpErrorConfig().clearProperty("key1");
414         checkErrorListener(AbstractConfiguration.EVENT_CLEAR_PROPERTY, "key1",
415                 null);
416     }
417 
418     /***
419      * Tests handling of errors in clear().
420      */
421     public void testClearError()
422     {
423         setUpErrorConfig().clear();
424         checkErrorListener(AbstractConfiguration.EVENT_CLEAR, null, null);
425     }
426 
427     /***
428      * Tests handling of errors in getKeys().
429      */
430     public void testGetKeysError()
431     {
432         Iterator it = setUpErrorConfig().getKeys();
433         checkErrorListener(AbstractConfiguration.EVENT_READ_PROPERTY, null,
434                 null);
435         assertFalse("Iteration is not empty", it.hasNext());
436     }
437 
438     /***
439      * Tests obtaining a property as list whose value contains the list
440      * delimiter. Multiple values should be returned.
441      */
442     public void testGetListWithDelimiter()
443     {
444         DatabaseConfiguration config = setUpConfig();
445         config.setListDelimiter(';');
446         List values = config.getList("keyMulti");
447         assertEquals("Wrong number of list elements", 3, values.size());
448         assertEquals("Wrong list element 0", "a", values.get(0));
449         assertEquals("Wrong list element 2", "c", values.get(2));
450     }
451 
452     /***
453      * Tests obtaining a property whose value contains the list delimiter when
454      * delimiter parsing is disabled.
455      */
456     public void testGetListWithDelimiterParsingDisabled()
457     {
458         DatabaseConfiguration config = setUpConfig();
459         config.setListDelimiter(';');
460         config.setDelimiterParsingDisabled(true);
461         assertEquals("Wrong value of property", "a;b;c", config
462                 .getString("keyMulti"));
463     }
464 
465     /***
466      * Tests adding a property containing the list delimiter. When this property
467      * is queried multiple values should be returned.
468      */
469     public void testAddWithDelimiter()
470     {
471         DatabaseConfiguration config = setUpConfig();
472         config.setListDelimiter(';');
473         config.addProperty("keyList", "1;2;3");
474         String[] values = config.getStringArray("keyList");
475         assertEquals("Wrong number of property values", 3, values.length);
476         assertEquals("Wrong value at index 1", "2", values[1]);
477     }
478 
479     /***
480      * A specialized database configuration implementation that can be
481      * configured to throw an exception when obtaining a connection. This way
482      * database exceptions can be simulated.
483      */
484     static class PotentialErrorDatabaseConfiguration extends
485             DatabaseConfiguration
486     {
487         /*** A flag whether a getConnection() call should fail. */
488         boolean failOnConnect;
489 
490         public PotentialErrorDatabaseConfiguration(DataSource datasource,
491                 String table, String keyColumn, String valueColumn)
492         {
493             super(datasource, table, keyColumn, valueColumn);
494         }
495 
496         protected Connection getConnection() throws SQLException
497         {
498             if (failOnConnect)
499             {
500                 throw new SQLException("Simulated DB error");
501             }
502             return super.getConnection();
503         }
504     }
505 
506     /***
507      * A test error listener implementation that is used for finding out whether
508      * error events are correctly triggered.
509      */
510     static class TestErrorListener implements ConfigurationErrorListener
511     {
512         /*** Stores the number of calls. */
513         int errorCount;
514 
515         /*** Stores the last error event. */
516         ConfigurationErrorEvent event;
517 
518         public void configurationError(ConfigurationErrorEvent event)
519         {
520             errorCount++;
521             this.event = event;
522         }
523     }
524 }