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