    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 }