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 }