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 484692 2006-12-08 18:30:15Z 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      /*** The configuration to be tested. */
49      CombinedConfiguration config;
50  
51      /*** The test event listener. */
52      CombinedListener listener;
53  
54      protected void setUp() throws Exception
55      {
56          super.setUp();
57          config = new CombinedConfiguration();
58          listener = new CombinedListener();
59          config.addConfigurationListener(listener);
60      }
61  
62      /***
63       * Tests accessing a newly created combined configuration.
64       */
65      public void testInit()
66      {
67          assertEquals("Already configurations contained", 0, config
68                  .getNumberOfConfigurations());
69          assertTrue("Set of names is not empty", config.getConfigurationNames()
70                  .isEmpty());
71          assertTrue("Wrong node combiner",
72                  config.getNodeCombiner() instanceof UnionCombiner);
73          assertNull("Test config was found", config.getConfiguration(TEST_NAME));
74          assertFalse("Force reload check flag is set", config.isForceReloadCheck());
75      }
76  
77      /***
78       * Tests adding a configuration (without further information).
79       */
80      public void testAddConfiguration()
81      {
82          AbstractConfiguration c = setUpTestConfiguration();
83          config.addConfiguration(c);
84          checkAddConfig(c);
85          assertEquals("Wrong number of configs", 1, config
86                  .getNumberOfConfigurations());
87          assertTrue("Name list is not empty", config.getConfigurationNames()
88                  .isEmpty());
89          assertSame("Added config not found", c, config.getConfiguration(0));
90          assertTrue("Wrong property value", config.getBoolean(TEST_KEY));
91          listener.checkEvent(1, 0);
92      }
93  
94      /***
95       * Tests adding a configuration with a name.
96       */
97      public void testAddConfigurationWithName()
98      {
99          AbstractConfiguration c = setUpTestConfiguration();
100         config.addConfiguration(c, TEST_NAME);
101         checkAddConfig(c);
102         assertEquals("Wrong number of configs", 1, config
103                 .getNumberOfConfigurations());
104         assertSame("Added config not found", c, config.getConfiguration(0));
105         assertSame("Added config not found by name", c, config
106                 .getConfiguration(TEST_NAME));
107         Set names = config.getConfigurationNames();
108         assertEquals("Wrong number of config names", 1, names.size());
109         assertTrue("Name not found", names.contains(TEST_NAME));
110         assertTrue("Wrong property value", config.getBoolean(TEST_KEY));
111         listener.checkEvent(1, 0);
112     }
113 
114     /***
115      * Tests adding a configuration with a name when this name already exists.
116      * This should cause an exception.
117      */
118     public void testAddConfigurationWithNameTwice()
119     {
120         config.addConfiguration(setUpTestConfiguration(), TEST_NAME);
121         try
122         {
123             config.addConfiguration(setUpTestConfiguration(), TEST_NAME,
124                     "prefix");
125             fail("Could add config with same name!");
126         }
127         catch (ConfigurationRuntimeException cex)
128         {
129             // ok
130         }
131     }
132 
133     /***
134      * Tests adding a configuration and specifying an at position.
135      */
136     public void testAddConfigurationAt()
137     {
138         AbstractConfiguration c = setUpTestConfiguration();
139         config.addConfiguration(c, null, "my");
140         checkAddConfig(c);
141         assertTrue("Wrong property value", config.getBoolean("my." + TEST_KEY));
142     }
143 
144     /***
145      * Tests adding a configuration with a complex at position. Here the at path
146      * contains a dot, which must be escaped.
147      */
148     public void testAddConfigurationComplexAt()
149     {
150         AbstractConfiguration c = setUpTestConfiguration();
151         config.addConfiguration(c, null, "This..is.a.complex");
152         checkAddConfig(c);
153         assertTrue("Wrong property value", config
154                 .getBoolean("This..is.a.complex." + TEST_KEY));
155     }
156 
157     /***
158      * Checks if a configuration was correctly added to the combined config.
159      *
160      * @param c the config to check
161      */
162     private void checkAddConfig(AbstractConfiguration c)
163     {
164         Collection listeners = c.getConfigurationListeners();
165         assertEquals("Wrong number of configuration listeners", 1, listeners
166                 .size());
167         assertTrue("Combined config is no listener", listeners.contains(config));
168     }
169 
170     /***
171      * Tests adding a null configuration. This should cause an exception to be
172      * thrown.
173      */
174     public void testAddNullConfiguration()
175     {
176         try
177         {
178             config.addConfiguration(null);
179             fail("Could add null configuration!");
180         }
181         catch (IllegalArgumentException iex)
182         {
183             // ok
184         }
185     }
186 
187     /***
188      * Tests accessing properties if no configurations have been added.
189      */
190     public void testAccessPropertyEmpty()
191     {
192         assertFalse("Found a key", config.containsKey(TEST_KEY));
193         assertNull("Key has a value", config.getString("test.comment"));
194         assertTrue("Config is not empty", config.isEmpty());
195     }
196 
197     /***
198      * Tests accessing properties if multiple configurations have been added.
199      */
200     public void testAccessPropertyMulti()
201     {
202         config.addConfiguration(setUpTestConfiguration());
203         config.addConfiguration(setUpTestConfiguration(), null, "prefix1");
204         config.addConfiguration(setUpTestConfiguration(), null, "prefix2");
205         assertTrue("Prop1 not found", config.getBoolean(TEST_KEY));
206         assertTrue("Prop 2 not found", config.getBoolean("prefix1." + TEST_KEY));
207         assertTrue("Prop 3 not found", config.getBoolean("prefix2." + TEST_KEY));
208         assertFalse("Configuration is empty", config.isEmpty());
209         listener.checkEvent(3, 0);
210     }
211 
212     /***
213      * Tests removing a configuration.
214      */
215     public void testRemoveConfiguration()
216     {
217         AbstractConfiguration c = setUpTestConfiguration();
218         config.addConfiguration(c);
219         checkAddConfig(c);
220         assertTrue("Config could not be removed", config.removeConfiguration(c));
221         checkRemoveConfig(c);
222     }
223 
224     /***
225      * Tests removing a configuration by index.
226      */
227     public void testRemoveConfigurationAt()
228     {
229         AbstractConfiguration c = setUpTestConfiguration();
230         config.addConfiguration(c);
231         assertSame("Wrong config removed", c, config.removeConfigurationAt(0));
232         checkRemoveConfig(c);
233     }
234 
235     /***
236      * Tests removing a configuration by name.
237      */
238     public void testRemoveConfigurationByName()
239     {
240         AbstractConfiguration c = setUpTestConfiguration();
241         config.addConfiguration(c, TEST_NAME);
242         assertSame("Wrong config removed", c, config
243                 .removeConfiguration(TEST_NAME));
244         checkRemoveConfig(c);
245     }
246 
247     /***
248      * Tests removing a configuration with a name.
249      */
250     public void testRemoveNamedConfiguration()
251     {
252         AbstractConfiguration c = setUpTestConfiguration();
253         config.addConfiguration(c, TEST_NAME);
254         config.removeConfiguration(c);
255         checkRemoveConfig(c);
256     }
257 
258     /***
259      * Tests removing a named configuration by index.
260      */
261     public void testRemoveNamedConfigurationAt()
262     {
263         AbstractConfiguration c = setUpTestConfiguration();
264         config.addConfiguration(c, TEST_NAME);
265         assertSame("Wrong config removed", c, config.removeConfigurationAt(0));
266         checkRemoveConfig(c);
267     }
268 
269     /***
270      * Tests removing a configuration that was not added prior.
271      */
272     public void testRemoveNonContainedConfiguration()
273     {
274         assertFalse("Could remove non contained config", config
275                 .removeConfiguration(setUpTestConfiguration()));
276         listener.checkEvent(0, 0);
277     }
278 
279     /***
280      * Tests removing a configuration by name, which is not contained.
281      */
282     public void testRemoveConfigurationByUnknownName()
283     {
284         assertNull("Could remove configuration by unknown name", config
285                 .removeConfiguration("unknownName"));
286         listener.checkEvent(0, 0);
287     }
288 
289     /***
290      * Tests whether a configuration was completely removed.
291      *
292      * @param c the removed configuration
293      */
294     private void checkRemoveConfig(AbstractConfiguration c)
295     {
296         assertTrue("Listener was not removed", c.getConfigurationListeners()
297                 .isEmpty());
298         assertEquals("Wrong number of contained configs", 0, config
299                 .getNumberOfConfigurations());
300         assertTrue("Name was not removed", config.getConfigurationNames()
301                 .isEmpty());
302         listener.checkEvent(2, 0);
303     }
304 
305     /***
306      * Tests if an update of a contained configuration leeds to an invalidation
307      * of the combined configuration.
308      */
309     public void testUpdateContainedConfiguration()
310     {
311         AbstractConfiguration c = setUpTestConfiguration();
312         config.addConfiguration(c);
313         c.addProperty("test.otherTest", "yes");
314         assertEquals("New property not found", "yes", config
315                 .getString("test.otherTest"));
316         listener.checkEvent(3, 0);
317     }
318 
319     /***
320      * Tests if setting a node combiner causes an invalidation.
321      */
322     public void testSetNodeCombiner()
323     {
324         NodeCombiner combiner = new UnionCombiner();
325         config.setNodeCombiner(combiner);
326         assertSame("Node combiner was not set", combiner, config
327                 .getNodeCombiner());
328         listener.checkEvent(1, 0);
329     }
330 
331     /***
332      * Tests setting a null node combiner. This should cause an exception.
333      */
334     public void testSetNullNodeCombiner()
335     {
336         try
337         {
338             config.setNodeCombiner(null);
339             fail("Could set null node combiner!");
340         }
341         catch (IllegalArgumentException iex)
342         {
343             // ok
344         }
345     }
346 
347     /***
348      * Tests cloning a combined configuration.
349      */
350     public void testClone()
351     {
352         config.addConfiguration(setUpTestConfiguration());
353         config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2");
354         config.addConfiguration(new PropertiesConfiguration(), "props");
355 
356         CombinedConfiguration cc2 = (CombinedConfiguration) config.clone();
357         assertEquals("Wrong number of contained configurations", config
358                 .getNumberOfConfigurations(), cc2.getNumberOfConfigurations());
359         assertSame("Wrong node combiner", config.getNodeCombiner(), cc2
360                 .getNodeCombiner());
361         assertEquals("Wrong number of names", config.getConfigurationNames()
362                 .size(), cc2.getConfigurationNames().size());
363         assertTrue("Event listeners were cloned", cc2
364                 .getConfigurationListeners().isEmpty());
365 
366         StrictConfigurationComparator comp = new StrictConfigurationComparator();
367         for (int i = 0; i < config.getNumberOfConfigurations(); i++)
368         {
369             assertNotSame("Configuration at " + i + " was not cloned", config
370                     .getConfiguration(i), cc2.getConfiguration(i));
371             assertEquals("Wrong config class at " + i, config.getConfiguration(
372                     i).getClass(), cc2.getConfiguration(i).getClass());
373             assertTrue("Configs not equal at " + i, comp.compare(config
374                     .getConfiguration(i), cc2.getConfiguration(i)));
375         }
376 
377         assertTrue("Combined configs not equal", comp.compare(config, cc2));
378     }
379 
380     /***
381      * Tests if the cloned configuration is decoupled from the original.
382      */
383     public void testCloneModify()
384     {
385         config.addConfiguration(setUpTestConfiguration(), TEST_NAME);
386         CombinedConfiguration cc2 = (CombinedConfiguration) config.clone();
387         assertTrue("Name is missing", cc2.getConfigurationNames().contains(
388                 TEST_NAME));
389         cc2.removeConfiguration(TEST_NAME);
390         assertFalse("Names in original changed", config.getConfigurationNames()
391                 .isEmpty());
392     }
393 
394     /***
395      * Tests clearing a combined configuration. This should remove all contained
396      * configurations.
397      */
398     public void testClear()
399     {
400         config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "test");
401         config.addConfiguration(setUpTestConfiguration());
402 
403         config.clear();
404         assertEquals("Still configs contained", 0, config
405                 .getNumberOfConfigurations());
406         assertTrue("Still names contained", config.getConfigurationNames()
407                 .isEmpty());
408         assertTrue("Config is not empty", config.isEmpty());
409 
410         listener.checkEvent(3, 2);
411     }
412 
413     /***
414      * Tests if file-based configurations can be reloaded.
415      */
416     public void testReloading() throws Exception
417     {
418         config.setForceReloadCheck(true);
419         File testDir = new File("target");
420         File testXmlFile = new File(testDir, "reload.xml");
421         File testPropsFile = new File(testDir, "reload.properties");
422         writeFile(testXmlFile, "<xml><xmlReload>0</xmlReload></xml>");
423         writeFile(testPropsFile, "propsReload = 0");
424         XMLConfiguration c1 = new XMLConfiguration(testXmlFile);
425         c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
426         PropertiesConfiguration c2 = new PropertiesConfiguration(testPropsFile);
427         c2.setThrowExceptionOnMissing(true);
428         c2.setReloadingStrategy(new FileAlwaysReloadingStrategy());
429         config.addConfiguration(c1);
430         config.addConfiguration(c2);
431         assertEquals("Wrong xml reload value", 0, config.getInt("xmlReload"));
432         assertEquals("Wrong props reload value", 0, config
433                 .getInt("propsReload"));
434 
435         writeFile(testXmlFile, "<xml><xmlReload>1</xmlReload></xml>");
436         assertEquals("XML reload not detected", 1, config.getInt("xmlReload"));
437         config.setForceReloadCheck(false);
438         writeFile(testPropsFile, "propsReload = 1");
439         assertEquals("Props reload detected though check flag is false", 0, config
440                 .getInt("propsReload"));
441 
442         assertTrue("XML file cannot be removed", testXmlFile.delete());
443         assertTrue("Props file cannot be removed", testPropsFile.delete());
444     }
445 
446     /***
447      * Helper method for writing a file.
448      *
449      * @param file the file to be written
450      * @param content the file's content
451      * @throws IOException if an error occurs
452      */
453     private static void writeFile(File file, String content) throws IOException
454     {
455         PrintWriter out = null;
456         try
457         {
458             out = new PrintWriter(new FileWriter(file));
459             out.print(content);
460         }
461         finally
462         {
463             if (out != null)
464             {
465                 out.close();
466             }
467         }
468     }
469 
470     /***
471      * Helper method for creating a test configuration to be added to the
472      * combined configuration.
473      *
474      * @return the test configuration
475      */
476     private AbstractConfiguration setUpTestConfiguration()
477     {
478         HierarchicalConfiguration config = new HierarchicalConfiguration();
479         config.addProperty(TEST_KEY, Boolean.TRUE);
480         config.addProperty("test.comment", "This is a test");
481         return config;
482     }
483 
484     /***
485      * Test event listener class for checking if the expected invalidate events
486      * are fired.
487      */
488     static class CombinedListener implements ConfigurationListener
489     {
490         int invalidateEvents;
491 
492         int otherEvents;
493 
494         public void configurationChanged(ConfigurationEvent event)
495         {
496             if (event.getType() == CombinedConfiguration.EVENT_COMBINED_INVALIDATE)
497             {
498                 invalidateEvents++;
499             }
500             else
501             {
502                 otherEvents++;
503             }
504         }
505 
506         /***
507          * Checks if the expected number of events was fired.
508          *
509          * @param expectedInvalidate the expected number of invalidate events
510          * @param expectedOthers the expected number of other events
511          */
512         public void checkEvent(int expectedInvalidate, int expectedOthers)
513         {
514             Assert.assertEquals("Wrong number of invalidate events",
515                     expectedInvalidate, invalidateEvents);
516             Assert.assertEquals("Wrong number of other events", expectedOthers,
517                     otherEvents);
518         }
519     }
520 }