    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  package org.apache.commons.configuration;
    18
    19  import java.io.File;
    20  import java.io.FileWriter;
    21  import java.io.IOException;
    22  import java.io.PrintWriter;
    23  import java.util.Collection;
    24  import java.util.Set;
    25
    26  import org.apache.commons.configuration.event.ConfigurationEvent;
    27  import org.apache.commons.configuration.event.ConfigurationListener;
    28  import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
    29  import org.apache.commons.configuration.tree.NodeCombiner;
    30  import org.apache.commons.configuration.tree.UnionCombiner;
    31
    32  import junit.framework.Assert;
    33  import junit.framework.TestCase;
    34
    35  /***
    36   * Test class for CombinedConfiguration.
    37   *
    38   * @version $Id: TestCombinedConfiguration.java 555737 2007-07-12 19:59:25Z oheger $
    39   */
    40  public class TestCombinedConfiguration extends TestCase
    41  {
    42      /*** Constant for the name of a sub configuration. */
    43      static final String TEST_NAME = "SUBCONFIG";
    44
    45      /*** Constant for a test key. */
    46      static final String TEST_KEY = "test.value";
    47
    48      /*** Constant for the name of the first child configuration.*/
    49      static final String CHILD1 = TEST_NAME + "1";
    50
    51      /*** Constant for the name of the second child configuration.*/
    52      static final String CHILD2 = TEST_NAME + "2";
    53
    54      /*** The configuration to be tested. */
    55      CombinedConfiguration config;
    56
    57      /*** The test event listener. */
    58      CombinedListener listener;
    59
    60      protected void setUp() throws Exception
    61      {
    62          super.setUp();
    63          config = new CombinedConfiguration();
    64          listener = new CombinedListener();
    65          config.addConfigurationListener(listener);
    66      }
    67
    68      /***
    69       * Tests accessing a newly created combined configuration.
    70       */
    71      public void testInit()
    72      {
    73          assertEquals("Already configurations contained", 0, config
    74                  .getNumberOfConfigurations());
    75          assertTrue("Set of names is not empty", config.getConfigurationNames()
    76                  .isEmpty());
    77          assertTrue("Wrong node combiner",
    78                  config.getNodeCombiner() instanceof UnionCombiner);
    79          assertNull("Test config was found", config.getConfiguration(TEST_NAME));
    80          assertFalse("Force reload check flag is set", config.isForceReloadCheck());
    81      }
    82
    83      /***
    84       * Tests adding a configuration (without further information).
    85       */
    86      public void testAddConfiguration()
    87      {
    88          AbstractConfiguration c = setUpTestConfiguration();
    89          config.addConfiguration(c);
    90          checkAddConfig(c);
    91          assertEquals("Wrong number of configs", 1, config
    92                  .getNumberOfConfigurations());
    93          assertTrue("Name list is not empty", config.getConfigurationNames()
    94                  .isEmpty());
    95          assertSame("Added config not found", c, config.getConfiguration(0));
    96          assertTrue("Wrong property value", config.getBoolean(TEST_KEY));
    97          listener.checkEvent(1, 0);
    98      }
    99
    100     /***
    101      * Tests adding a configuration with a name.
    102      */
    103     public void testAddConfigurationWithName()
    104     {
    105         AbstractConfiguration c = setUpTestConfiguration();
    106         config.addConfiguration(c, TEST_NAME);
    107         checkAddConfig(c);
    108         assertEquals("Wrong number of configs", 1, config
    109                 .getNumberOfConfigurations());
    110         assertSame("Added config not found", c, config.getConfiguration(0));
    111         assertSame("Added config not found by name", c, config
    112                 .getConfiguration(TEST_NAME));
    113         Set names = config.getConfigurationNames();
    114         assertEquals("Wrong number of config names", 1, names.size());
    115         assertTrue("Name not found", names.contains(TEST_NAME));
    116         assertTrue("Wrong property value", config.getBoolean(TEST_KEY));
    117         listener.checkEvent(1, 0);
    118     }
    119
    120     /***
    121      * Tests adding a configuration with a name when this name already exists.
    122      * This should cause an exception.
    123      */
    124     public void testAddConfigurationWithNameTwice()
    125     {
    126         config.addConfiguration(setUpTestConfiguration(), TEST_NAME);
    127         try
    128         {
    129             config.addConfiguration(setUpTestConfiguration(), TEST_NAME,
    130                     "prefix");
    131             fail("Could add config with same name!");
    132         }
    133         catch (ConfigurationRuntimeException cex)
    134         {
    135             // ok
    136         }
    137     }
    138
    139     /***
    140      * Tests adding a configuration and specifying an at position.
    141      */
    142     public void testAddConfigurationAt()
    143     {
    144         AbstractConfiguration c = setUpTestConfiguration();
    145         config.addConfiguration(c, null, "my");
    146         checkAddConfig(c);
    147         assertTrue("Wrong property value", config.getBoolean("my." + TEST_KEY));
    148     }
    149
    150     /***
    151      * Tests adding a configuration with a complex at position. Here the at path
    152      * contains a dot, which must be escaped.
    153      */
    154     public void testAddConfigurationComplexAt()
    155     {
    156         AbstractConfiguration c = setUpTestConfiguration();
    157         config.addConfiguration(c, null, "This..is.a.complex");
    158         checkAddConfig(c);
    159         assertTrue("Wrong property value", config
    160                 .getBoolean("This..is.a.complex." + TEST_KEY));
    161     }
    162
    163     /***
    164      * Checks if a configuration was correctly added to the combined config.
    165      *
    166      * @param c the config to check
    167      */
    168     private void checkAddConfig(AbstractConfiguration c)
    169     {
    170         Collection listeners = c.getConfigurationListeners();
    171         assertEquals("Wrong number of configuration listeners", 1, listeners
    172                 .size());
    173         assertTrue("Combined config is no listener", listeners.contains(config));
    174     }
    175
    176     /***
    177      * Tests adding a null configuration. This should cause an exception to be
    178      * thrown.
    179      */
    180     public void testAddNullConfiguration()
    181     {
    182         try
    183         {
    184             config.addConfiguration(null);
    185             fail("Could add null configuration!");
    186         }
    187         catch (IllegalArgumentException iex)
    188         {
    189             // ok
    190         }
    191     }
    192
    193     /***
    194      * Tests accessing properties if no configurations have been added.
    195      */
    196     public void testAccessPropertyEmpty()
    197     {
    198         assertFalse("Found a key", config.containsKey(TEST_KEY));
    199         assertNull("Key has a value", config.getString("test.comment"));
    200         assertTrue("Config is not empty", config.isEmpty());
    201     }
    202
    203     /***
    204      * Tests accessing properties if multiple configurations have been added.
    205      */
    206     public void testAccessPropertyMulti()
    207     {
    208         config.addConfiguration(setUpTestConfiguration());
    209         config.addConfiguration(setUpTestConfiguration(), null, "prefix1");
    210         config.addConfiguration(setUpTestConfiguration(), null, "prefix2");
    211         assertTrue("Prop1 not found", config.getBoolean(TEST_KEY));
    212         assertTrue("Prop 2 not found", config.getBoolean("prefix1." + TEST_KEY));
    213         assertTrue("Prop 3 not found", config.getBoolean("prefix2." + TEST_KEY));
    214         assertFalse("Configuration is empty", config.isEmpty());
    215         listener.checkEvent(3, 0);
    216     }
    217
    218     /***
    219      * Tests removing a configuration.
    220      */
    221     public void testRemoveConfiguration()
    222     {
    223         AbstractConfiguration c = setUpTestConfiguration();
    224         config.addConfiguration(c);
    225         checkAddConfig(c);
    226         assertTrue("Config could not be removed", config.removeConfiguration(c));
    227         checkRemoveConfig(c);
    228     }
    229
    230     /***
    231      * Tests removing a configuration by index.
    232      */
    233     public void testRemoveConfigurationAt()
    234     {
    235         AbstractConfiguration c = setUpTestConfiguration();
    236         config.addConfiguration(c);
    237         assertSame("Wrong config removed", c, config.removeConfigurationAt(0));
    238         checkRemoveConfig(c);
    239     }
    240
    241     /***
    242      * Tests removing a configuration by name.
    243      */
    244     public void testRemoveConfigurationByName()
    245     {
    246         AbstractConfiguration c = setUpTestConfiguration();
    247         config.addConfiguration(c, TEST_NAME);
    248         assertSame("Wrong config removed", c, config
    249                 .removeConfiguration(TEST_NAME));
    250         checkRemoveConfig(c);
    251     }
    252
    253     /***
    254      * Tests removing a configuration with a name.
    255      */
    256     public void testRemoveNamedConfiguration()
    257     {
    258         AbstractConfiguration c = setUpTestConfiguration();
    259         config.addConfiguration(c, TEST_NAME);
    260         config.removeConfiguration(c);
    261         checkRemoveConfig(c);
    262     }
    263
    264     /***
    265      * Tests removing a named configuration by index.
    266      */
    267     public void testRemoveNamedConfigurationAt()
    268     {
    269         AbstractConfiguration c = setUpTestConfiguration();
    270         config.addConfiguration(c, TEST_NAME);
    271         assertSame("Wrong config removed", c, config.removeConfigurationAt(0));
    272         checkRemoveConfig(c);
    273     }
    274
    275     /***
    276      * Tests removing a configuration that was not added prior.
    277      */
    278     public void testRemoveNonContainedConfiguration()
    279     {
    280         assertFalse("Could remove non contained config", config
    281                 .removeConfiguration(setUpTestConfiguration()));
    282         listener.checkEvent(0, 0);
    283     }
    284
    285     /***
    286      * Tests removing a configuration by name, which is not contained.
    287      */
    288     public void testRemoveConfigurationByUnknownName()
    289     {
    290         assertNull("Could remove configuration by unknown name", config
    291                 .removeConfiguration("unknownName"));
    292         listener.checkEvent(0, 0);
    293     }
    294
    295     /***
    296      * Tests whether a configuration was completely removed.
    297      *
    298      * @param c the removed configuration
    299      */
    300     private void checkRemoveConfig(AbstractConfiguration c)
    301     {
    302         assertTrue("Listener was not removed", c.getConfigurationListeners()
    303                 .isEmpty());
    304         assertEquals("Wrong number of contained configs", 0, config
    305                 .getNumberOfConfigurations());
    306         assertTrue("Name was not removed", config.getConfigurationNames()
    307                 .isEmpty());
    308         listener.checkEvent(2, 0);
    309     }
    310
    311     /***
    312      * Tests if an update of a contained configuration leeds to an invalidation
    313      * of the combined configuration.
    314      */
    315     public void testUpdateContainedConfiguration()
    316     {
    317         AbstractConfiguration c = setUpTestConfiguration();
    318         config.addConfiguration(c);
    319         c.addProperty("test.otherTest", "yes");
    320         assertEquals("New property not found", "yes", config
    321                 .getString("test.otherTest"));
    322         listener.checkEvent(3, 0);
    323     }
    324
    325     /***
    326      * Tests if setting a node combiner causes an invalidation.
    327      */
    328     public void testSetNodeCombiner()
    329     {
    330         NodeCombiner combiner = new UnionCombiner();
    331         config.setNodeCombiner(combiner);
    332         assertSame("Node combiner was not set", combiner, config
    333                 .getNodeCombiner());
    334         listener.checkEvent(1, 0);
    335     }
    336
    337     /***
    338      * Tests setting a null node combiner. This should cause an exception.
    339      */
    340     public void testSetNullNodeCombiner()
    341     {
    342         try
    343         {
    344             config.setNodeCombiner(null);
    345             fail("Could set null node combiner!");
    346         }
    347         catch (IllegalArgumentException iex)
    348         {
    349             // ok
    350         }
    351     }
    352
    353     /***
    354      * Tests cloning a combined configuration.
    355      */
    356     public void testClone()
    357     {
    358         config.addConfiguration(setUpTestConfiguration());
    359         config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2");
    360         config.addConfiguration(new PropertiesConfiguration(), "props");
    361
    362         CombinedConfiguration cc2 = (CombinedConfiguration) config.clone();
    363         assertEquals("Wrong number of contained configurations", config
    364                 .getNumberOfConfigurations(), cc2.getNumberOfConfigurations());
    365         assertSame("Wrong node combiner", config.getNodeCombiner(), cc2
    366                 .getNodeCombiner());
    367         assertEquals("Wrong number of names", config.getConfigurationNames()
    368                 .size(), cc2.getConfigurationNames().size());
    369         assertTrue("Event listeners were cloned", cc2
    370                 .getConfigurationListeners().isEmpty());
    371
    372         StrictConfigurationComparator comp = new StrictConfigurationComparator();
    373         for (int i = 0; i < config.getNumberOfConfigurations(); i++)
    374         {
    375             assertNotSame("Configuration at " + i + " was not cloned", config
    376                     .getConfiguration(i), cc2.getConfiguration(i));
    377             assertEquals("Wrong config class at " + i, config.getConfiguration(
    378                     i).getClass(), cc2.getConfiguration(i).getClass());
    379             assertTrue("Configs not equal at " + i, comp.compare(config
    380                     .getConfiguration(i), cc2.getConfiguration(i)));
    381         }
    382
    383         assertTrue("Combined configs not equal", comp.compare(config, cc2));
    384     }
    385
    386     /***
    387      * Tests if the cloned configuration is decoupled from the original.
    388      */
    389     public void testCloneModify()
    390     {
    391         config.addConfiguration(setUpTestConfiguration(), TEST_NAME);
    392         CombinedConfiguration cc2 = (CombinedConfiguration) config.clone();
    393         assertTrue("Name is missing", cc2.getConfigurationNames().contains(
    394                 TEST_NAME));
    395         cc2.removeConfiguration(TEST_NAME);
    396         assertFalse("Names in original changed", config.getConfigurationNames()
    397                 .isEmpty());
    398     }
    399
    400     /***
    401      * Tests clearing a combined configuration. This should remove all contained
    402      * configurations.
    403      */
    404     public void testClear()
    405     {
    406         config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "test");
    407         config.addConfiguration(setUpTestConfiguration());
    408
    409         config.clear();
    410         assertEquals("Still configs contained", 0, config
    411                 .getNumberOfConfigurations());
    412         assertTrue("Still names contained", config.getConfigurationNames()
    413                 .isEmpty());
    414         assertTrue("Config is not empty", config.isEmpty());
    415
    416         listener.checkEvent(3, 2);
    417     }
    418
    419     /***
    420      * Tests if file-based configurations can be reloaded.
    421      */
    422     public void testReloading() throws Exception
    423     {
    424         config.setForceReloadCheck(true);
    425         File testDir = new File("target");
    426         File testXmlFile = new File(testDir, "reload.xml");
    427         File testPropsFile = new File(testDir, "reload.properties");
    428         writeFile(testXmlFile, "<xml><xmlReload>0</xmlReload></xml>");
    429         writeFile(testPropsFile, "propsReload = 0");
    430         XMLConfiguration c1 = new XMLConfiguration(testXmlFile);
    431         c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
    432         PropertiesConfiguration c2 = new PropertiesConfiguration(testPropsFile);
    433         c2.setThrowExceptionOnMissing(true);
    434         c2.setReloadingStrategy(new FileAlwaysReloadingStrategy());
    435         config.addConfiguration(c1);
    436         config.addConfiguration(c2);
    437         assertEquals("Wrong xml reload value", 0, config.getInt("xmlReload"));
    438         assertEquals("Wrong props reload value", 0, config
    439                 .getInt("propsReload"));
    440
    441         writeFile(testXmlFile, "<xml><xmlReload>1</xmlReload></xml>");
    442         assertEquals("XML reload not detected", 1, config.getInt("xmlReload"));
    443         config.setForceReloadCheck(false);
    444         writeFile(testPropsFile, "propsReload = 1");
    445         assertEquals("Props reload detected though check flag is false", 0, config
    446                 .getInt("propsReload"));
    447
    448         assertTrue("XML file cannot be removed", testXmlFile.delete());
    449         assertTrue("Props file cannot be removed", testPropsFile.delete());
    450     }
    451
    452     /***
    453      * Prepares a test of the getSource() method.
    454      */
    455     private void setUpSourceTest()
    456     {
    457         HierarchicalConfiguration c1 = new HierarchicalConfiguration();
    458         PropertiesConfiguration c2 = new PropertiesConfiguration();
    459         c1.addProperty(TEST_KEY, TEST_NAME);
    460         c2.addProperty("another.key", "test");
    461         config.addConfiguration(c1, CHILD1);
    462         config.addConfiguration(c2, CHILD2);
    463     }
    464
    465     /***
    466      * Tests the gestSource() method when the source property is defined in a
    467      * hierarchical configuration.
    468      */
    469     public void testGetSourceHierarchical()
    470     {
    471         setUpSourceTest();
    472         assertEquals("Wrong source configuration", config
    473                 .getConfiguration(CHILD1), config.getSource(TEST_KEY));
    474     }
    475
    476     /***
    477      * Tests whether the source configuration can be detected for non
    478      * hierarchical configurations.
    479      */
    480     public void testGetSourceNonHierarchical()
    481     {
    482         setUpSourceTest();
    483         assertEquals("Wrong source configuration", config
    484                 .getConfiguration(CHILD2), config.getSource("another.key"));
    485     }
    486
    487     /***
    488      * Tests the getSource() method when the passed in key is not contained.
    489      * Result should be null in this case.
    490      */
    491     public void testGetSourceUnknown()
    492     {
    493         setUpSourceTest();
    494         assertNull("Wrong result for unknown key", config
    495                 .getSource("an.unknown.key"));
    496     }
    497
    498     /***
    499      * Tests the getSource() method when a null key is passed in. This should
    500      * cause an exception.
    501      */
    502     public void testGetSourceNull()
    503     {
    504         try
    505         {
    506             config.getSource(null);
    507             fail("Could resolve source for null key!");
    508         }
    509         catch (IllegalArgumentException iex)
    510         {
    511             // ok
    512         }
    513     }
    514
    515     /***
    516      * Tests the getSource() method when the passed in key belongs to the
    517      * combined configuration itself.
    518      */
    519     public void testGetSourceCombined()
    520     {
    521         setUpSourceTest();
    522         final String key = "yet.another.key";
    523         config.addProperty(key, Boolean.TRUE);
    524         assertEquals("Wrong source for key", config, config.getSource(key));
    525     }
    526
    527     /***
    528      * Tests the getSource() method when the passed in key refers to multiple
    529      * values, which are all defined in the same source configuration.
    530      */
    531     public void testGetSourceMulti()
    532     {
    533         setUpSourceTest();
    534         final String key = "list.key";
    535         config.getConfiguration(CHILD1).addProperty(key, "1,2,3");
    536         assertEquals("Wrong source for multi-value property", config
    537                 .getConfiguration(CHILD1), config.getSource(key));
    538     }
    539
    540     /***
    541      * Tests the getSource() method when the passed in key refers to multiple
    542      * values defined by different sources. This should cause an exception.
    543      */
    544     public void testGetSourceMultiSources()
    545     {
    546         setUpSourceTest();
    547         final String key = "list.key";
    548         config.getConfiguration(CHILD1).addProperty(key, "1,2,3");
    549         config.getConfiguration(CHILD2).addProperty(key, "a,b,c");
    550         try
    551         {
    552             config.getSource(key);
    553             fail("Multiple sources not detected!");
    554         }
    555         catch (IllegalArgumentException iex)
    556         {
    557             //ok
    558         }
    559     }
    560
    561     /***
    562      * Tests whether escaped list delimiters are treated correctly.
    563      */
    564     public void testEscapeListDelimiters()
    565     {
    566         PropertiesConfiguration sub = new PropertiesConfiguration();
    567         sub.addProperty("test.pi", "3//,1415");
    568         config.addConfiguration(sub);
    569         assertEquals("Wrong value", "3,1415", config.getString("test.pi"));
    570     }
    571
    572     /***
    573      * Helper method for writing a file.
    574      *
    575      * @param file the file to be written
    576      * @param content the file's content
    577      * @throws IOException if an error occurs
    578      */
    579     private static void writeFile(File file, String content) throws IOException
    580     {
    581         PrintWriter out = null;
    582         try
    583         {
    584             out = new PrintWriter(new FileWriter(file));
    585             out.print(content);
    586         }
    587         finally
    588         {
    589             if (out != null)
    590             {
    591                 out.close();
    592             }
    593         }
    594     }
    595
    596     /***
    597      * Helper method for creating a test configuration to be added to the
    598      * combined configuration.
    599      *
    600      * @return the test configuration
    601      */
    602     private AbstractConfiguration setUpTestConfiguration()
    603     {
    604         HierarchicalConfiguration config = new HierarchicalConfiguration();
    605         config.addProperty(TEST_KEY, Boolean.TRUE);
    606         config.addProperty("test.comment", "This is a test");
    607         return config;
    608     }
    609
    610     /***
    611      * Test event listener class for checking if the expected invalidate events
    612      * are fired.
    613      */
    614     static class CombinedListener implements ConfigurationListener
    615     {
    616         int invalidateEvents;
    617
    618         int otherEvents;
    619
    620         public void configurationChanged(ConfigurationEvent event)
    621         {
    622             if (event.getType() == CombinedConfiguration.EVENT_COMBINED_INVALIDATE)
    623             {
    624                 invalidateEvents++;
    625             }
    626             else
    627             {
    628                 otherEvents++;
    629             }
    630         }
    631
    632         /***
    633          * Checks if the expected number of events was fired.
    634          *
    635          * @param expectedInvalidate the expected number of invalidate events
    636          * @param expectedOthers the expected number of other events
    637          */
    638         public void checkEvent(int expectedInvalidate, int expectedOthers)
    639         {
    640             Assert.assertEquals("Wrong number of invalidate events",
    641                     expectedInvalidate, invalidateEvents);
    642             Assert.assertEquals("Wrong number of other events", expectedOthers,
    643                     otherEvents);
    644         }
    645     }
    646 }