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.File;
21  import java.io.FileWriter;
22  import java.io.IOException;
23  import java.io.PrintWriter;
24  import java.util.ArrayList;
25  import java.util.Iterator;
26  import java.util.List;
27  import java.util.NoSuchElementException;
28  import java.util.Collection;
29  
30  import org.apache.commons.configuration.event.ConfigurationEvent;
31  import org.apache.commons.configuration.event.ConfigurationListener;
32  import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
33  
34  import junit.framework.TestCase;
35  
36  /***
37   * Test loading multiple configurations.
38   *
39   * @version $Id: TestCompositeConfiguration.java 524000 2007-03-30 09:14:03Z oheger $
40   */
41  public class TestCompositeConfiguration extends TestCase
42  {
43      protected PropertiesConfiguration conf1;
44      protected PropertiesConfiguration conf2;
45      protected XMLConfiguration xmlConf;
46      protected CompositeConfiguration cc;
47  
48      /***
49       * The File that we test with
50       */
51      private String testProperties = new File("conf/test.properties").getAbsolutePath();
52      private String testProperties2 = new File("conf/test2.properties").getAbsolutePath();
53      private String testPropertiesXML = new File("conf/test.xml").getAbsolutePath();
54  
55      protected void setUp() throws Exception
56      {
57          cc = new CompositeConfiguration();
58          conf1 = new PropertiesConfiguration(testProperties);
59          conf2 = new PropertiesConfiguration(testProperties2);
60          xmlConf = new XMLConfiguration(new File(testPropertiesXML));
61  
62          cc.setThrowExceptionOnMissing(true);
63      }
64  
65      public void testThrowExceptionOnMissing()
66      {
67          assertTrue("Throw Exception Property is not set!", cc.isThrowExceptionOnMissing());
68      }
69  
70      public void testAddRemoveConfigurations() throws Exception
71      {
72          cc.addConfiguration(conf1);
73          assertEquals("Number of configurations", 2, cc.getNumberOfConfigurations());
74          cc.addConfiguration(conf1);
75          assertEquals("Number of configurations", 2, cc.getNumberOfConfigurations());
76          cc.addConfiguration(conf2);
77          assertEquals("Number of configurations", 3, cc.getNumberOfConfigurations());
78          cc.removeConfiguration(conf1);
79          assertEquals("Number of configurations", 2, cc.getNumberOfConfigurations());
80          cc.clear();
81          assertEquals("Number of configurations", 1, cc.getNumberOfConfigurations());
82      }
83  
84      public void testGetPropertyWIncludes() throws Exception
85      {
86          cc.addConfiguration(conf1);
87          cc.addConfiguration(conf2);
88          List l = cc.getList("packages");
89          assertTrue(l.contains("packagea"));
90      }
91  
92      public void testGetProperty() throws Exception
93      {
94          cc.addConfiguration(conf1);
95          cc.addConfiguration(conf2);
96          assertEquals("Make sure we get the property from conf1 first", "test.properties", cc.getString("propertyInOrder"));
97          cc.clear();
98  
99          cc.addConfiguration(conf2);
100         cc.addConfiguration(conf1);
101         assertEquals("Make sure we get the property from conf2 first", "test2.properties", cc.getString("propertyInOrder"));
102     }
103 
104     public void testCantRemoveMemoryConfig() throws Exception
105     {
106         cc.clear();
107         assertEquals(1, cc.getNumberOfConfigurations());
108 
109         Configuration internal = cc.getConfiguration(0);
110         cc.removeConfiguration(internal);
111 
112         assertEquals(1, cc.getNumberOfConfigurations());
113     }
114 
115     public void testGetPropertyMissing() throws Exception
116     {
117         cc.addConfiguration(conf1);
118         cc.addConfiguration(conf2);
119         try
120         {
121             assertNull(cc.getString("bogus.property"));
122             fail("Should have thrown a NoSuchElementException");
123         }
124         catch (NoSuchElementException nsee)
125         {
126             assertTrue(nsee.getMessage().indexOf("bogus.property") > -1);
127         }
128 
129         assertTrue("Should be false", !cc.getBoolean("test.missing.boolean", false));
130         assertTrue("Should be true", cc.getBoolean("test.missing.boolean.true", true));
131     }
132 
133     /***
134      * Tests <code>List</code> parsing.
135      */
136     public void testMultipleTypesOfConfigs() throws Exception
137     {
138         cc.addConfiguration(conf1);
139         cc.addConfiguration(xmlConf);
140         assertEquals("Make sure we get the property from conf1 first", 1, cc.getInt("test.short"));
141         cc.clear();
142 
143         cc.addConfiguration(xmlConf);
144         cc.addConfiguration(conf1);
145         assertEquals("Make sure we get the property from xml", 8, cc.getInt("test.short"));
146     }
147 
148     /***
149      * Tests <code>List</code> parsing.
150      */
151     public void testPropertyExistsInOnlyOneConfig() throws Exception
152     {
153         cc.addConfiguration(conf1);
154         cc.addConfiguration(xmlConf);
155         assertEquals("value", cc.getString("element"));
156     }
157 
158     /***
159      * Tests getting a default when the key doesn't exist
160      */
161     public void testDefaultValueWhenKeyMissing() throws Exception
162     {
163         cc.addConfiguration(conf1);
164         cc.addConfiguration(xmlConf);
165         assertEquals("default", cc.getString("bogus", "default"));
166         assertTrue(1.4 == cc.getDouble("bogus", 1.4));
167         assertTrue(1.4 == cc.getDouble("bogus", 1.4));
168     }
169 
170     /***
171      * Tests <code>List</code> parsing.
172      */
173     public void testGettingConfiguration() throws Exception
174     {
175         cc.addConfiguration(conf1);
176         cc.addConfiguration(xmlConf);
177         assertEquals(PropertiesConfiguration.class, cc.getConfiguration(0).getClass());
178         assertEquals(XMLConfiguration.class, cc.getConfiguration(1).getClass());
179     }
180 
181     /***
182      * Tests setting values.  These are set in memory mode only!
183      */
184     public void testClearingProperty() throws Exception
185     {
186         cc.addConfiguration(conf1);
187         cc.addConfiguration(xmlConf);
188         cc.clearProperty("test.short");
189         assertTrue("Make sure test.short is gone!", !cc.containsKey("test.short"));
190     }
191 
192     /***
193      * Tests adding values.  Make sure they _DON'T_ override any other properties but add to the
194      * existing properties  and keep sequence
195      */
196     public void testAddingProperty() throws Exception
197     {
198         cc.addConfiguration(conf1);
199         cc.addConfiguration(xmlConf);
200 
201         String[] values = cc.getStringArray("test.short");
202 
203         assertEquals("Number of values before add is wrong!", 1, values.length);
204         assertEquals("First Value before add is wrong", "1", values[0]);
205 
206         cc.addProperty("test.short", "88");
207 
208         values = cc.getStringArray("test.short");
209 
210         assertEquals("Number of values is wrong!", 2, values.length);
211         assertEquals("First Value is wrong", "1", values[0]);
212         assertEquals("Third Value is wrong", "88", values[1]);
213     }
214 
215     /***
216      * Tests setting values.  These are set in memory mode only!
217      */
218     public void testSettingMissingProperty() throws Exception
219     {
220         cc.addConfiguration(conf1);
221         cc.addConfiguration(xmlConf);
222         cc.setProperty("my.new.property", "supernew");
223         assertEquals("supernew", cc.getString("my.new.property"));
224     }
225 
226     /***
227      * Tests retrieving subsets of configurations
228      */
229     public void testGettingSubset() throws Exception
230     {
231         cc.addConfiguration(conf1);
232         cc.addConfiguration(xmlConf);
233 
234         Configuration subset = cc.subset("test");
235         assertNotNull(subset);
236         assertFalse("Shouldn't be empty", subset.isEmpty());
237         assertEquals("Make sure the initial loaded configs subset overrides any later add configs subset", "1", subset.getString("short"));
238 
239         cc.setProperty("test.short", "43");
240         subset = cc.subset("test");
241         assertEquals("Make sure the initial loaded configs subset overrides any later add configs subset", "43", subset.getString("short"));
242     }
243 
244     /***
245      * Tests subsets and still can resolve elements
246      */
247     public void testSubsetCanResolve() throws Exception
248     {
249         cc = new CompositeConfiguration();
250         final BaseConfiguration config = new BaseConfiguration();
251         config.addProperty("subset.tempfile", "${java.io.tmpdir}/file.tmp");
252         cc.addConfiguration(config);
253         cc.addConfiguration(ConfigurationConverter.getConfiguration(System.getProperties()));
254 
255         Configuration subset = cc.subset("subset");
256         assertEquals(System.getProperty("java.io.tmpdir") + "/file.tmp", subset.getString("tempfile"));
257     }
258 
259     /***
260      * Tests <code>List</code> parsing.
261      */
262     public void testList() throws Exception
263     {
264         cc.addConfiguration(conf1);
265         cc.addConfiguration(xmlConf);
266 
267         List packages = cc.getList("packages");
268         // we should get 3 packages here
269         assertEquals(3, packages.size());
270 
271         List defaultList = new ArrayList();
272         defaultList.add("1");
273         defaultList.add("2");
274 
275         packages = cc.getList("packages.which.dont.exist", defaultList);
276         // we should get 2 packages here
277         assertEquals(2, packages.size());
278 
279     }
280 
281     /***
282      * Tests <code>String</code> array parsing.
283      */
284     public void testStringArray() throws Exception
285     {
286         cc.addConfiguration(conf1);
287         cc.addConfiguration(xmlConf);
288 
289         String[] packages = cc.getStringArray("packages");
290         // we should get 3 packages here
291         assertEquals(3, packages.length);
292 
293         packages = cc.getStringArray("packages.which.dont.exist");
294         // we should get 0 packages here
295         assertEquals(0, packages.length);
296     }
297 
298     public void testGetList()
299     {
300         Configuration conf1 = new BaseConfiguration();
301         conf1.addProperty("array", "value1");
302         conf1.addProperty("array", "value2");
303 
304         Configuration conf2 = new BaseConfiguration();
305         conf2.addProperty("array", "value3");
306         conf2.addProperty("array", "value4");
307 
308         cc.addConfiguration(conf1);
309         cc.addConfiguration(conf2);
310 
311         // check the composite 'array' property
312         List list = cc.getList("array");
313         assertNotNull("null list", list);
314         assertEquals("list size", 2, list.size());
315         assertTrue("'value1' not found in the list", list.contains("value1"));
316         assertTrue("'value2' not found in the list", list.contains("value2"));
317 
318         // add an element to the list in the composite configuration
319         cc.addProperty("array", "value5");
320 
321         // test the new list
322         list = cc.getList("array");
323         assertNotNull("null list", list);
324         assertEquals("list size", 3, list.size());
325         assertTrue("'value1' not found in the list", list.contains("value1"));
326         assertTrue("'value2' not found in the list", list.contains("value2"));
327         assertTrue("'value5' not found in the list", list.contains("value5"));
328     }
329 
330     /***
331      * Tests <code>getKeys</code> preserves the order
332      */
333     public void testGetKeysPreservesOrder() throws Exception
334     {
335         cc.addConfiguration(conf1);
336         List orderedList = new ArrayList();
337         for (Iterator keys = conf1.getKeys(); keys.hasNext();)
338         {
339             orderedList.add(keys.next());
340         }
341         List iteratedList = new ArrayList();
342         for (Iterator keys = cc.getKeys(); keys.hasNext();)
343         {
344             iteratedList.add(keys.next());
345         }
346         assertEquals(orderedList.size(), iteratedList.size());
347         for (int i = 0; i < orderedList.size(); i++)
348         {
349             assertEquals(orderedList.get(i), iteratedList.get(i));
350         }
351     }
352 
353     /***
354      * Tests <code>getKeys(String key)</code> preserves the order
355      */
356     public void testGetKeys2PreservesOrder() throws Exception
357     {
358         cc.addConfiguration(conf1);
359         List orderedList = new ArrayList();
360         for (Iterator keys = conf1.getKeys("test"); keys.hasNext();)
361         {
362             orderedList.add(keys.next());
363         }
364         List iteratedList = new ArrayList();
365         for (Iterator keys = cc.getKeys("test"); keys.hasNext();)
366         {
367             iteratedList.add(keys.next());
368         }
369         assertEquals(orderedList.size(), iteratedList.size());
370         for (int i = 0; i < orderedList.size(); i++)
371         {
372             assertEquals(orderedList.get(i), iteratedList.get(i));
373         }
374     }
375 
376     public void testGetStringWithDefaults()
377     {
378         BaseConfiguration defaults = new BaseConfiguration();
379         defaults.addProperty("default", "default string");
380 
381         CompositeConfiguration c = new CompositeConfiguration(defaults);
382         c.setThrowExceptionOnMissing(cc.isThrowExceptionOnMissing());
383         c.addProperty("string", "test string");
384 
385         assertEquals("test string", c.getString("string"));
386         try
387         {
388             c.getString("XXX");
389             fail("Should throw NoSuchElementException exception");
390         }
391         catch (NoSuchElementException e)
392         {
393             //ok
394         }
395         catch (Exception e)
396         {
397             fail("Should throw NoSuchElementException exception, not " + e);
398         }
399 
400         //test defaults
401         assertEquals("test string", c.getString("string", "some default value"));
402         assertEquals("default string", c.getString("default"));
403         assertEquals("default string", c.getString("default", "some default value"));
404         assertEquals("some default value", c.getString("XXX", "some default value"));
405     }
406 
407     public void testCheckingInMemoryConfiguration() throws Exception
408     {
409         String TEST_KEY = "testKey";
410         Configuration defaults = new PropertiesConfiguration();
411         defaults.setProperty(TEST_KEY, "testValue");
412         Configuration testConfiguration = new CompositeConfiguration(defaults);
413         assertTrue(testConfiguration.containsKey(TEST_KEY));
414         assertFalse(testConfiguration.isEmpty());
415         boolean foundTestKey = false;
416         Iterator i = testConfiguration.getKeys();
417         //assertTrue(i instanceof IteratorChain);
418         //IteratorChain ic = (IteratorChain)i;
419         //assertEquals(2,i.size());
420         for (; i.hasNext();)
421         {
422             String key = (String) i.next();
423             if (key.equals(TEST_KEY))
424             {
425                 foundTestKey = true;
426             }
427         }
428         assertTrue(foundTestKey);
429         testConfiguration.clearProperty(TEST_KEY);
430         assertFalse(testConfiguration.containsKey(TEST_KEY));
431     }
432 
433     public void testStringArrayInterpolation()
434     {
435         CompositeConfiguration config = new CompositeConfiguration();
436         config.addProperty("base", "foo");
437         config.addProperty("list", "${base}.bar1");
438         config.addProperty("list", "${base}.bar2");
439         config.addProperty("list", "${base}.bar3");
440 
441         String[] array = config.getStringArray("list");
442         assertEquals("size", 3, array.length);
443         assertEquals("1st element", "foo.bar1", array[0]);
444         assertEquals("2nd element", "foo.bar2", array[1]);
445         assertEquals("3rd element", "foo.bar3", array[2]);
446     }
447 
448     /***
449      * Tests whether global interpolation works with lists.
450      */
451     public void testListInterpolation()
452     {
453         PropertiesConfiguration c1 = new PropertiesConfiguration();
454         c1.addProperty("c1.value", "test1");
455         c1.addProperty("c1.value", "${c2.value}");
456         cc.addConfiguration(c1);
457         PropertiesConfiguration c2 = new PropertiesConfiguration();
458         c2.addProperty("c2.value", "test2");
459         cc.addConfiguration(c2);
460         List lst = cc.getList("c1.value");
461         assertEquals("Wrong list size", 2, lst.size());
462         assertEquals("Wrong first element", "test1", lst.get(0));
463         assertEquals("Wrong second element", "test2", lst.get(1));
464     }
465 
466     /***
467      * Tests interpolation in combination with reloading.
468      */
469     public void testInterpolationWithReload() throws IOException,
470             ConfigurationException
471     {
472         File testFile = new File("target/testConfig.properties");
473         final String propFirst = "first.name";
474         final String propFull = "full.name";
475 
476         try
477         {
478             writeTestConfig(testFile, propFirst, "John");
479             PropertiesConfiguration c1 = new PropertiesConfiguration(testFile);
480             c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
481             PropertiesConfiguration c2 = new PropertiesConfiguration();
482             c2.addProperty(propFull, "${" + propFirst + "} Doe");
483             CompositeConfiguration cc = new CompositeConfiguration();
484             cc.addConfiguration(c1);
485             cc.addConfiguration(c2);
486             assertEquals("Wrong name", "John Doe", cc.getString(propFull));
487 
488             writeTestConfig(testFile, propFirst, "Jane");
489             assertEquals("First name not changed", "Jane", c1
490                     .getString(propFirst));
491             assertEquals("First name not changed in composite", "Jane", cc
492                     .getString(propFirst));
493             assertEquals("Full name not changed", "Jane Doe", cc
494                     .getString(propFull));
495         }
496         finally
497         {
498             if (testFile.exists())
499             {
500                 testFile.delete();
501             }
502         }
503     }
504 
505     /***
506      * Writes a test properties file containing a single property definition.
507      * 
508      * @param f the file to write
509      * @param prop the property name
510      * @param value the property value
511      * @throws IOException if an error occurs
512      */
513     private void writeTestConfig(File f, String prop, String value)
514             throws IOException
515     {
516         PrintWriter out = new PrintWriter(new FileWriter(f));
517         out.print(prop);
518         out.print("=");
519         out.println(value);
520         out.close();
521     }
522 
523     public void testInstanciateWithCollection()
524     {
525         Collection configs = new ArrayList();
526         configs.add(xmlConf);
527         configs.add(conf1);
528         configs.add(conf2);
529 
530         CompositeConfiguration config = new CompositeConfiguration(configs);
531         assertEquals("Number of configurations", 4, config.getNumberOfConfigurations());
532         assertTrue("The in memory configuration is not empty", config.getInMemoryConfiguration().isEmpty());
533     }
534 
535     public void testClone()
536     {
537         CompositeConfiguration cc2 = (CompositeConfiguration) cc.clone();
538         assertEquals("Wrong number of contained configurations", cc
539                 .getNumberOfConfigurations(), cc2.getNumberOfConfigurations());
540 
541         StrictConfigurationComparator comp = new StrictConfigurationComparator();
542         for (int i = 0; i < cc.getNumberOfConfigurations(); i++)
543         {
544             assertEquals("Wrong configuration class at " + i, cc
545                     .getConfiguration(i).getClass(), cc2.getConfiguration(i)
546                     .getClass());
547             assertNotSame("Configuration was not cloned", cc
548                     .getConfiguration(i), cc2.getConfiguration(i));
549             assertTrue("Configurations at " + i + " not equal", comp.compare(cc
550                     .getConfiguration(i), cc2.getConfiguration(i)));
551         }
552 
553         assertTrue("Configurations are not equal", comp.compare(cc, cc2));
554     }
555 
556     /***
557      * Tests cloning if one of the contained configurations does not support
558      * this operation. This should cause an exception.
559      */
560     public void testCloneNotSupported()
561     {
562         cc.addConfiguration(new NonCloneableConfiguration());
563         try
564         {
565             cc.clone();
566             fail("Could clone non cloneable configuration!");
567         }
568         catch (ConfigurationRuntimeException crex)
569         {
570             // ok
571         }
572     }
573 
574     /***
575      * Ensures that event listeners are not cloned.
576      */
577     public void testCloneEventListener()
578     {
579         cc.addConfigurationListener(new TestEventListenerImpl());
580         CompositeConfiguration cc2 = (CompositeConfiguration) cc.clone();
581         assertTrue("Listeners have been cloned", cc2
582                 .getConfigurationListeners().isEmpty());
583     }
584 
585     /***
586      * Tests whether add property events are triggered.
587      */
588     public void testEventAddProperty()
589     {
590         TestEventListenerImpl l = new TestEventListenerImpl();
591         cc.addConfigurationListener(l);
592         cc.addProperty("test", "value");
593         assertEquals("No add events received", 2, l.eventCount);
594     }
595 
596     /***
597      * Tests whether set property events are triggered.
598      */
599     public void testEventSetProperty()
600     {
601         TestEventListenerImpl l = new TestEventListenerImpl();
602         cc.addConfigurationListener(l);
603         cc.setProperty("test", "value");
604         assertEquals("No set events received", 2, l.eventCount);
605     }
606 
607     /***
608      * Tests whether clear property events are triggered.
609      */
610     public void testEventClearProperty()
611     {
612         cc.addConfiguration(conf1);
613         assertTrue("Wrong value for property", cc
614                 .getBoolean("configuration.loaded"));
615         TestEventListenerImpl l = new TestEventListenerImpl();
616         cc.addConfigurationListener(l);
617         cc.clearProperty("configuration.loaded");
618         assertFalse("Key still present", cc.containsKey("configuration.loaded"));
619         assertEquals("No clear events received", 2, l.eventCount);
620     }
621 
622     /***
623      * Tests chaning the list delimiter character.
624      */
625     public void testSetListDelimiter()
626     {
627         cc.setListDelimiter('/');
628         checkSetListDelimiter();
629     }
630 
631     /***
632      * Tests whether the correct list delimiter is set after a clear operation.
633      */
634     public void testSetListDelimiterAfterClear()
635     {
636         cc.setListDelimiter('/');
637         cc.clear();
638         checkSetListDelimiter();
639     }
640 
641     /***
642      * Helper method for testing whether the list delimiter is correctly
643      * handled.
644      */
645     private void checkSetListDelimiter()
646     {
647         cc.addProperty("test.list", "a/b/c");
648         cc.addProperty("test.property", "a,b,c");
649         assertEquals("Wrong number of list elements", 3, cc
650                 .getList("test.list").size());
651         assertEquals("Wrong value of property", "a,b,c", cc
652                 .getString("test.property"));
653     }
654 
655     /***
656      * Tests whether list splitting can be disabled.
657      */
658     public void testSetDelimiterParsingDisabled()
659     {
660         cc.setDelimiterParsingDisabled(true);
661         checkSetListDelimiterParsingDisabled();
662     }
663 
664     /***
665      * Tests whether the list parsing flag is correctly handled after a clear()
666      * operation.
667      */
668     public void testSetDelimiterParsingDisabledAfterClear()
669     {
670         cc.setDelimiterParsingDisabled(true);
671         cc.clear();
672         checkSetListDelimiterParsingDisabled();
673     }
674 
675     /***
676      * Helper method for checking whether the list parsing flag is correctly
677      * handled.
678      */
679     private void checkSetListDelimiterParsingDisabled()
680     {
681         cc.addProperty("test.property", "a,b,c");
682         assertEquals("Wrong value of property", "a,b,c", cc
683                 .getString("test.property"));
684     }
685 
686     /***
687      * A test configuration event listener that counts the number of received
688      * events. Used for testing the event facilities.
689      */
690     static class TestEventListenerImpl implements ConfigurationListener
691     {
692         /*** The number of received events.*/
693         int eventCount;
694 
695         public void configurationChanged(ConfigurationEvent event)
696         {
697             eventCount++;
698         }
699     }
700 }