View Javadoc
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 static org.junit.Assert.assertEquals;
20  import static org.junit.Assert.assertFalse;
21  import static org.junit.Assert.assertNotNull;
22  import static org.junit.Assert.assertNotSame;
23  import static org.junit.Assert.assertNull;
24  import static org.junit.Assert.assertSame;
25  import static org.junit.Assert.assertTrue;
26  
27  import java.io.File;
28  import java.io.FileWriter;
29  import java.io.IOException;
30  import java.io.PrintWriter;
31  import java.io.StringReader;
32  import java.io.StringWriter;
33  import java.text.MessageFormat;
34  import java.util.Collection;
35  import java.util.List;
36  import java.util.NoSuchElementException;
37  import java.util.Set;
38  
39  import junit.framework.Assert;
40  
41  import org.apache.commons.configuration.event.ConfigurationEvent;
42  import org.apache.commons.configuration.event.ConfigurationListener;
43  import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
44  import org.apache.commons.configuration.reloading.FileRandomReloadingStrategy;
45  import org.apache.commons.configuration.tree.DefaultExpressionEngine;
46  import org.apache.commons.configuration.tree.MergeCombiner;
47  import org.apache.commons.configuration.tree.NodeCombiner;
48  import org.apache.commons.configuration.tree.OverrideCombiner;
49  import org.apache.commons.configuration.tree.UnionCombiner;
50  import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
51  import org.junit.Before;
52  import org.junit.Rule;
53  import org.junit.Test;
54  import org.junit.rules.TemporaryFolder;
55  
56  /**
57   * Test class for CombinedConfiguration.
58   *
59   * @version $Id: TestCombinedConfiguration.java 1327061 2012-04-17 12:18:27Z rgoers $
60   */
61  public class TestCombinedConfiguration
62  {
63      /** Constant for the name of a sub configuration. */
64      private static final String TEST_NAME = "SUBCONFIG";
65  
66      /** Constant for a test key. */
67      private static final String TEST_KEY = "test.value";
68  
69      /** Constant for the name of the first child configuration.*/
70      private static final String CHILD1 = TEST_NAME + "1";
71  
72      /** Constant for the name of the second child configuration.*/
73      private static final String CHILD2 = TEST_NAME + "2";
74  
75      /** Constant for the name of the XML reload test file.*/
76      private static final String RELOAD_XML_NAME = "reload.xml";
77  
78      /** Constant for the content of a XML reload test file.*/
79      private static final String RELOAD_XML_CONTENT = "<xml><xmlReload>{0}</xmlReload></xml>";
80  
81      /** Constant for the name of the properties reload test file.*/
82      private static final String RELOAD_PROPS_NAME = "reload.properties";
83  
84      /** Constant for the content of a properties reload test file.*/
85      private static final String RELOAD_PROPS_CONTENT = "propsReload = {0}";
86  
87      /** Helper object for managing temporary files. */
88      @Rule
89      public TemporaryFolder folder = new TemporaryFolder();
90  
91      /** The configuration to be tested. */
92      private CombinedConfiguration config;
93  
94      /** The test event listener. */
95      private CombinedListener listener;
96  
97      @Before
98      public void setUp() throws Exception
99      {
100         config = new CombinedConfiguration();
101         listener = new CombinedListener();
102         config.addConfigurationListener(listener);
103     }
104 
105     /**
106      * Tests accessing a newly created combined configuration.
107      */
108     @Test
109     public void testInit()
110     {
111         assertEquals("Already configurations contained", 0, config
112                 .getNumberOfConfigurations());
113         assertTrue("Set of names is not empty", config.getConfigurationNames()
114                 .isEmpty());
115         assertTrue("Wrong node combiner",
116                 config.getNodeCombiner() instanceof UnionCombiner);
117         assertNull("Test config was found", config.getConfiguration(TEST_NAME));
118         assertFalse("Force reload check flag is set", config.isForceReloadCheck());
119     }
120 
121     /**
122      * Tests adding a configuration (without further information).
123      */
124     @Test
125     public void testAddConfiguration()
126     {
127         AbstractConfiguration c = setUpTestConfiguration();
128         config.addConfiguration(c);
129         checkAddConfig(c);
130         assertEquals("Wrong number of configs", 1, config
131                 .getNumberOfConfigurations());
132         assertTrue("Name list is not empty", config.getConfigurationNames()
133                 .isEmpty());
134         assertSame("Added config not found", c, config.getConfiguration(0));
135         assertTrue("Wrong property value", config.getBoolean(TEST_KEY));
136         listener.checkEvent(1, 0);
137     }
138 
139     /**
140      * Tests adding a configuration with a name.
141      */
142     @Test
143     public void testAddConfigurationWithName()
144     {
145         AbstractConfiguration c = setUpTestConfiguration();
146         config.addConfiguration(c, TEST_NAME);
147         checkAddConfig(c);
148         assertEquals("Wrong number of configs", 1, config
149                 .getNumberOfConfigurations());
150         assertSame("Added config not found", c, config.getConfiguration(0));
151         assertSame("Added config not found by name", c, config
152                 .getConfiguration(TEST_NAME));
153         Set<String> names = config.getConfigurationNames();
154         assertEquals("Wrong number of config names", 1, names.size());
155         assertTrue("Name not found", names.contains(TEST_NAME));
156         assertTrue("Wrong property value", config.getBoolean(TEST_KEY));
157         listener.checkEvent(1, 0);
158     }
159 
160     /**
161      * Tests adding a configuration with a name when this name already exists.
162      * This should cause an exception.
163      */
164     @Test(expected = ConfigurationRuntimeException.class)
165     public void testAddConfigurationWithNameTwice()
166     {
167         config.addConfiguration(setUpTestConfiguration(), TEST_NAME);
168         config.addConfiguration(setUpTestConfiguration(), TEST_NAME,
169                 "prefix");
170     }
171 
172     /**
173      * Tests adding a configuration and specifying an at position.
174      */
175     @Test
176     public void testAddConfigurationAt()
177     {
178         AbstractConfiguration c = setUpTestConfiguration();
179         config.addConfiguration(c, null, "my");
180         checkAddConfig(c);
181         assertTrue("Wrong property value", config.getBoolean("my." + TEST_KEY));
182     }
183 
184     /**
185      * Tests adding a configuration with a complex at position. Here the at path
186      * contains a dot, which must be escaped.
187      */
188     @Test
189     public void testAddConfigurationComplexAt()
190     {
191         AbstractConfiguration c = setUpTestConfiguration();
192         config.addConfiguration(c, null, "This..is.a.complex");
193         checkAddConfig(c);
194         assertTrue("Wrong property value", config
195                 .getBoolean("This..is.a.complex." + TEST_KEY));
196     }
197 
198     /**
199      * Checks if a configuration was correctly added to the combined config.
200      *
201      * @param c the config to check
202      */
203     private void checkAddConfig(AbstractConfiguration c)
204     {
205         Collection<ConfigurationListener> listeners = c.getConfigurationListeners();
206         assertEquals("Wrong number of configuration listeners", 1, listeners
207                 .size());
208         assertTrue("Combined config is no listener", listeners.contains(config));
209     }
210 
211     /**
212      * Tests adding a null configuration. This should cause an exception to be
213      * thrown.
214      */
215     @Test(expected = IllegalArgumentException.class)
216     public void testAddNullConfiguration()
217     {
218         config.addConfiguration(null);
219     }
220 
221     /**
222      * Tests accessing properties if no configurations have been added.
223      */
224     @Test
225     public void testAccessPropertyEmpty()
226     {
227         assertFalse("Found a key", config.containsKey(TEST_KEY));
228         assertNull("Key has a value", config.getString("test.comment"));
229         assertTrue("Config is not empty", config.isEmpty());
230     }
231 
232     /**
233      * Tests accessing properties if multiple configurations have been added.
234      */
235     @Test
236     public void testAccessPropertyMulti()
237     {
238         config.addConfiguration(setUpTestConfiguration());
239         config.addConfiguration(setUpTestConfiguration(), null, "prefix1");
240         config.addConfiguration(setUpTestConfiguration(), null, "prefix2");
241         assertTrue("Prop1 not found", config.getBoolean(TEST_KEY));
242         assertTrue("Prop 2 not found", config.getBoolean("prefix1." + TEST_KEY));
243         assertTrue("Prop 3 not found", config.getBoolean("prefix2." + TEST_KEY));
244         assertFalse("Configuration is empty", config.isEmpty());
245         listener.checkEvent(3, 0);
246     }
247 
248     /**
249      * Tests removing a configuration.
250      */
251     @Test
252     public void testRemoveConfiguration()
253     {
254         AbstractConfiguration c = setUpTestConfiguration();
255         config.addConfiguration(c);
256         checkAddConfig(c);
257         assertTrue("Config could not be removed", config.removeConfiguration(c));
258         checkRemoveConfig(c);
259     }
260 
261     /**
262      * Tests removing a configuration by index.
263      */
264     @Test
265     public void testRemoveConfigurationAt()
266     {
267         AbstractConfiguration c = setUpTestConfiguration();
268         config.addConfiguration(c);
269         assertSame("Wrong config removed", c, config.removeConfigurationAt(0));
270         checkRemoveConfig(c);
271     }
272 
273     /**
274      * Tests removing a configuration by name.
275      */
276     @Test
277     public void testRemoveConfigurationByName()
278     {
279         AbstractConfiguration c = setUpTestConfiguration();
280         config.addConfiguration(c, TEST_NAME);
281         assertSame("Wrong config removed", c, config
282                 .removeConfiguration(TEST_NAME));
283         checkRemoveConfig(c);
284     }
285 
286     /**
287      * Tests removing a configuration with a name.
288      */
289     @Test
290     public void testRemoveNamedConfiguration()
291     {
292         AbstractConfiguration c = setUpTestConfiguration();
293         config.addConfiguration(c, TEST_NAME);
294         config.removeConfiguration(c);
295         checkRemoveConfig(c);
296     }
297 
298     /**
299      * Tests removing a named configuration by index.
300      */
301     @Test
302     public void testRemoveNamedConfigurationAt()
303     {
304         AbstractConfiguration c = setUpTestConfiguration();
305         config.addConfiguration(c, TEST_NAME);
306         assertSame("Wrong config removed", c, config.removeConfigurationAt(0));
307         checkRemoveConfig(c);
308     }
309 
310     /**
311      * Tests removing a configuration that was not added prior.
312      */
313     @Test
314     public void testRemoveNonContainedConfiguration()
315     {
316         assertFalse("Could remove non contained config", config
317                 .removeConfiguration(setUpTestConfiguration()));
318         listener.checkEvent(0, 0);
319     }
320 
321     /**
322      * Tests removing a configuration by name, which is not contained.
323      */
324     @Test
325     public void testRemoveConfigurationByUnknownName()
326     {
327         assertNull("Could remove configuration by unknown name", config
328                 .removeConfiguration("unknownName"));
329         listener.checkEvent(0, 0);
330     }
331 
332     /**
333      * Tests whether a configuration was completely removed.
334      *
335      * @param c the removed configuration
336      */
337     private void checkRemoveConfig(AbstractConfiguration c)
338     {
339         assertTrue("Listener was not removed", c.getConfigurationListeners()
340                 .isEmpty());
341         assertEquals("Wrong number of contained configs", 0, config
342                 .getNumberOfConfigurations());
343         assertTrue("Name was not removed", config.getConfigurationNames()
344                 .isEmpty());
345         listener.checkEvent(2, 0);
346     }
347 
348     /**
349      * Tests if an update of a contained configuration leeds to an invalidation
350      * of the combined configuration.
351      */
352     @Test
353     public void testUpdateContainedConfiguration()
354     {
355         AbstractConfiguration c = setUpTestConfiguration();
356         config.addConfiguration(c);
357         c.addProperty("test.otherTest", "yes");
358         assertEquals("New property not found", "yes", config
359                 .getString("test.otherTest"));
360         listener.checkEvent(2, 0);
361     }
362 
363     /**
364      * Tests if setting a node combiner causes an invalidation.
365      */
366     @Test
367     public void testSetNodeCombiner()
368     {
369         NodeCombiner combiner = new UnionCombiner();
370         config.setNodeCombiner(combiner);
371         assertSame("Node combiner was not set", combiner, config
372                 .getNodeCombiner());
373         listener.checkEvent(1, 0);
374     }
375 
376     /**
377      * Tests setting a null node combiner. This should cause an exception.
378      */
379     @Test(expected = IllegalArgumentException.class)
380     public void testSetNullNodeCombiner()
381     {
382         config.setNodeCombiner(null);
383     }
384 
385     /**
386      * Tests cloning a combined configuration.
387      */
388     @Test
389     public void testClone()
390     {
391         config.addConfiguration(setUpTestConfiguration());
392         config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2");
393         config.addConfiguration(new PropertiesConfiguration(), "props");
394 
395         CombinedConfiguration cc2 = (CombinedConfiguration) config.clone();
396         assertEquals("Wrong number of contained configurations", config
397                 .getNumberOfConfigurations(), cc2.getNumberOfConfigurations());
398         assertSame("Wrong node combiner", config.getNodeCombiner(), cc2
399                 .getNodeCombiner());
400         assertEquals("Wrong number of names", config.getConfigurationNames()
401                 .size(), cc2.getConfigurationNames().size());
402         assertTrue("Event listeners were cloned", cc2
403                 .getConfigurationListeners().isEmpty());
404 
405         StrictConfigurationComparator comp = new StrictConfigurationComparator();
406         for (int i = 0; i < config.getNumberOfConfigurations(); i++)
407         {
408             assertNotSame("Configuration at " + i + " was not cloned", config
409                     .getConfiguration(i), cc2.getConfiguration(i));
410             assertEquals("Wrong config class at " + i, config.getConfiguration(
411                     i).getClass(), cc2.getConfiguration(i).getClass());
412             assertTrue("Configs not equal at " + i, comp.compare(config
413                     .getConfiguration(i), cc2.getConfiguration(i)));
414         }
415 
416         assertTrue("Combined configs not equal", comp.compare(config, cc2));
417     }
418 
419     /**
420      * Tests if the cloned configuration is decoupled from the original.
421      */
422     @Test
423     public void testCloneModify()
424     {
425         config.addConfiguration(setUpTestConfiguration(), TEST_NAME);
426         CombinedConfiguration cc2 = (CombinedConfiguration) config.clone();
427         assertTrue("Name is missing", cc2.getConfigurationNames().contains(
428                 TEST_NAME));
429         cc2.removeConfiguration(TEST_NAME);
430         assertFalse("Names in original changed", config.getConfigurationNames()
431                 .isEmpty());
432     }
433 
434     /**
435      * Tests clearing a combined configuration. This should remove all contained
436      * configurations.
437      */
438     @Test
439     public void testClear()
440     {
441         config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "test");
442         config.addConfiguration(setUpTestConfiguration());
443 
444         config.clear();
445         assertEquals("Still configs contained", 0, config
446                 .getNumberOfConfigurations());
447         assertTrue("Still names contained", config.getConfigurationNames()
448                 .isEmpty());
449         assertTrue("Config is not empty", config.isEmpty());
450 
451         listener.checkEvent(3, 2);
452     }
453 
454     /**
455      * Tests if file-based configurations can be reloaded.
456      */
457     @Test
458     public void testReloading() throws Exception
459     {
460         config.setForceReloadCheck(true);
461         File testXmlFile = writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 0);
462         File testPropsFile = writeReloadFile(RELOAD_PROPS_NAME, RELOAD_PROPS_CONTENT, 0);
463         XMLConfiguration c1 = new XMLConfiguration(testXmlFile);
464         c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
465         PropertiesConfiguration c2 = new PropertiesConfiguration(testPropsFile);
466         c2.setThrowExceptionOnMissing(true);
467         c2.setReloadingStrategy(new FileAlwaysReloadingStrategy());
468         config.addConfiguration(c1);
469         config.addConfiguration(c2);
470         assertEquals("Wrong xml reload value", 0, config.getInt("xmlReload"));
471         assertEquals("Wrong props reload value", 0, config
472                 .getInt("propsReload"));
473 
474         writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 1);
475         assertEquals("XML reload not detected", 1, config.getInt("xmlReload"));
476         config.setForceReloadCheck(false);
477         writeReloadFile(RELOAD_PROPS_NAME, RELOAD_PROPS_CONTENT, 1);
478         assertEquals("Props reload detected though check flag is false", 0, config
479                 .getInt("propsReload"));
480     }
481 
482     /**
483      * Tests whether the reload check works with a subnode configuration. This
484      * test is related to CONFIGURATION-341.
485      */
486     @Test
487     public void testReloadingSubnodeConfig() throws IOException,
488             ConfigurationException
489     {
490         config.setForceReloadCheck(true);
491         File testXmlFile = writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT,
492                 0);
493         XMLConfiguration c1 = new XMLConfiguration(testXmlFile);
494         c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
495         final String prefix = "reloadCheck";
496         config.addConfiguration(c1, CHILD1, prefix);
497         SubnodeConfiguration sub = config.configurationAt(prefix, true);
498         writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 1);
499         assertEquals("Reload not detected", 1, sub.getInt("xmlReload"));
500     }
501 
502     /**
503      * Tests whether reloading works for a combined configuration nested in
504      * another combined configuration.
505      */
506     @Test
507     public void testReloadingNestedCC() throws IOException,
508             ConfigurationException
509     {
510         config.setForceReloadCheck(true);
511         File testXmlFile =
512                 writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 0);
513         File testPropsFile =
514                 writeReloadFile(RELOAD_PROPS_NAME, RELOAD_PROPS_CONTENT, 0);
515         XMLConfiguration c1 = new XMLConfiguration(testXmlFile);
516         c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
517         PropertiesConfiguration c2 = new PropertiesConfiguration(testPropsFile);
518         c2.setReloadingStrategy(new FileAlwaysReloadingStrategy());
519         config.addConfiguration(c2);
520         CombinedConfiguration cc2 = new CombinedConfiguration();
521         cc2.setForceReloadCheck(true);
522         cc2.addConfiguration(c1);
523         config.addConfiguration(cc2);
524         assertEquals("Wrong xml reload value", 0, config.getInt("xmlReload"));
525         writeReloadFile(RELOAD_XML_NAME, RELOAD_XML_CONTENT, 1);
526         assertEquals("XML reload not detected", 1, config.getInt("xmlReload"));
527     }
528 
529     /**
530      * Prepares a test of the getSource() method.
531      */
532     private void setUpSourceTest()
533     {
534         HierarchicalConfiguration c1 = new HierarchicalConfiguration();
535         PropertiesConfiguration c2 = new PropertiesConfiguration();
536         c1.addProperty(TEST_KEY, TEST_NAME);
537         c2.addProperty("another.key", "test");
538         config.addConfiguration(c1, CHILD1);
539         config.addConfiguration(c2, CHILD2);
540     }
541 
542     /**
543      * Tests the gestSource() method when the source property is defined in a
544      * hierarchical configuration.
545      */
546     @Test
547     public void testGetSourceHierarchical()
548     {
549         setUpSourceTest();
550         assertEquals("Wrong source configuration", config
551                 .getConfiguration(CHILD1), config.getSource(TEST_KEY));
552     }
553 
554     /**
555      * Tests whether the source configuration can be detected for non
556      * hierarchical configurations.
557      */
558     @Test
559     public void testGetSourceNonHierarchical()
560     {
561         setUpSourceTest();
562         assertEquals("Wrong source configuration", config
563                 .getConfiguration(CHILD2), config.getSource("another.key"));
564     }
565 
566     /**
567      * Tests the getSource() method when the passed in key is not contained.
568      * Result should be null in this case.
569      */
570     @Test
571     public void testGetSourceUnknown()
572     {
573         setUpSourceTest();
574         assertNull("Wrong result for unknown key", config
575                 .getSource("an.unknown.key"));
576     }
577 
578     /**
579      * Tests the getSource() method when a null key is passed in. This should
580      * cause an exception.
581      */
582     @Test(expected = IllegalArgumentException.class)
583     public void testGetSourceNull()
584     {
585         config.getSource(null);
586     }
587 
588     /**
589      * Tests the getSource() method when the passed in key belongs to the
590      * combined configuration itself.
591      */
592     @Test
593     public void testGetSourceCombined()
594     {
595         setUpSourceTest();
596         final String key = "yet.another.key";
597         config.addProperty(key, Boolean.TRUE);
598         assertEquals("Wrong source for key", config, config.getSource(key));
599     }
600 
601     /**
602      * Tests the getSource() method when the passed in key refers to multiple
603      * values, which are all defined in the same source configuration.
604      */
605     @Test
606     public void testGetSourceMulti()
607     {
608         setUpSourceTest();
609         final String key = "list.key";
610         config.getConfiguration(CHILD1).addProperty(key, "1,2,3");
611         assertEquals("Wrong source for multi-value property", config
612                 .getConfiguration(CHILD1), config.getSource(key));
613     }
614 
615     /**
616      * Tests the getSource() method when the passed in key refers to multiple
617      * values defined by different sources. This should cause an exception.
618      */
619     @Test(expected = IllegalArgumentException.class)
620     public void testGetSourceMultiSources()
621     {
622         setUpSourceTest();
623         final String key = "list.key";
624         config.getConfiguration(CHILD1).addProperty(key, "1,2,3");
625         config.getConfiguration(CHILD2).addProperty(key, "a,b,c");
626         config.getSource(key);
627     }
628 
629     /**
630      * Tests whether escaped list delimiters are treated correctly.
631      */
632     @Test
633     public void testEscapeListDelimiters()
634     {
635         PropertiesConfiguration sub = new PropertiesConfiguration();
636         sub.addProperty("test.pi", "3\\,1415");
637         config.addConfiguration(sub);
638         assertEquals("Wrong value", "3,1415", config.getString("test.pi"));
639     }
640 
641     /**
642      * Tests whether an invalidate event is fired only after a change. This test
643      * is related to CONFIGURATION-315.
644      */
645     @Test
646     public void testInvalidateAfterChange()
647     {
648         ConfigurationEvent event = new ConfigurationEvent(config, 0, null,
649                 null, true);
650         config.configurationChanged(event);
651         assertEquals("Invalidate event fired", 0, listener.invalidateEvents);
652         event = new ConfigurationEvent(config, 0, null, null, false);
653         config.configurationChanged(event);
654         assertEquals("No invalidate event fired", 1, listener.invalidateEvents);
655     }
656 
657     /**
658      * Tests using a conversion expression engine for child configurations with
659      * strange keys. This test is related to CONFIGURATION-336.
660      */
661     @Test
662     public void testConversionExpressionEngine()
663     {
664         PropertiesConfiguration child = new PropertiesConfiguration();
665         child.addProperty("test(a)", "1,2,3");
666         config.addConfiguration(child);
667         DefaultExpressionEngine engineQuery = new DefaultExpressionEngine();
668         engineQuery.setIndexStart("<");
669         engineQuery.setIndexEnd(">");
670         config.setExpressionEngine(engineQuery);
671         DefaultExpressionEngine engineConvert = new DefaultExpressionEngine();
672         engineConvert.setIndexStart("[");
673         engineConvert.setIndexEnd("]");
674         config.setConversionExpressionEngine(engineConvert);
675         assertEquals("Wrong property 1", "1", config.getString("test(a)<0>"));
676         assertEquals("Wrong property 2", "2", config.getString("test(a)<1>"));
677         assertEquals("Wrong property 3", "3", config.getString("test(a)<2>"));
678     }
679 
680     /**
681      * Tests whether reload operations can cause a deadlock when the combined
682      * configuration is accessed concurrently. This test is related to
683      * CONFIGURATION-344.
684      */
685     @Test
686     public void testDeadlockWithReload() throws ConfigurationException,
687             InterruptedException
688     {
689         final PropertiesConfiguration child = new PropertiesConfiguration(
690                 "test.properties");
691         child.setReloadingStrategy(new FileAlwaysReloadingStrategy());
692         config.addConfiguration(child);
693         final int count = 1000;
694 
695         class TestDeadlockReloadThread extends Thread
696         {
697             boolean error = false;
698 
699             @Override
700             public void run()
701             {
702                 for (int i = 0; i < count && !error; i++)
703                 {
704                     try
705                     {
706                         if (!child.getBoolean("configuration.loaded"))
707                         {
708                             error = true;
709                         }
710                     }
711                     catch (NoSuchElementException nsex)
712                     {
713                         error = true;
714                     }
715                 }
716             }
717         }
718 
719         TestDeadlockReloadThread reloadThread = new TestDeadlockReloadThread();
720         reloadThread.start();
721         for (int i = 0; i < count; i++)
722         {
723             assertEquals("Wrong value of combined property", 10, config
724                     .getInt("test.integer"));
725         }
726         reloadThread.join();
727         assertFalse("Failure in thread", reloadThread.error);
728     }
729 
730     @Test
731     public void testGetConfigurations() throws Exception
732     {
733         config.addConfiguration(setUpTestConfiguration());
734         config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2");
735         AbstractConfiguration pc = new PropertiesConfiguration();
736         config.addConfiguration(pc, "props");
737         List<AbstractConfiguration> list = config.getConfigurations();
738         assertNotNull("No list of configurations returned", list);
739         assertTrue("Incorrect number of configurations", list.size() == 3);
740         AbstractConfiguration c = list.get(2);
741         assertTrue("Incorrect configuration", c == pc);
742     }
743 
744     @Test
745     public void testGetConfigurationNameList() throws Exception
746     {
747         config.addConfiguration(setUpTestConfiguration());
748         config.addConfiguration(setUpTestConfiguration(), TEST_NAME, "conf2");
749         AbstractConfiguration pc = new PropertiesConfiguration();
750         config.addConfiguration(pc, "props");
751         List<String> list = config.getConfigurationNameList();
752         assertNotNull("No list of configurations returned", list);
753         assertTrue("Incorrect number of configurations", list.size() == 3);
754         String name = list.get(1);
755         assertNotNull("No name returned", name);
756         assertTrue("Incorrect configuration name", TEST_NAME.equals(name));
757     }
758 
759     /**
760      * Tests whether changes on a sub node configuration that is part of a
761      * combined configuration are detected. This test is related to
762      * CONFIGURATION-368.
763      */
764     @Test
765     public void testReloadWithSubNodeConfig() throws Exception
766     {
767         final String reloadContent = "<config><default><xmlReload1>{0}</xmlReload1></default></config>";
768         config.setForceReloadCheck(true);
769         config.setNodeCombiner(new OverrideCombiner());
770         File testXmlFile1 = writeReloadFile(RELOAD_XML_NAME, reloadContent, 0);
771         final String prefix1 = "default";
772         XMLConfiguration c1 = new XMLConfiguration(testXmlFile1);
773         SubnodeConfiguration sub1 = c1.configurationAt(prefix1, true);
774         assertEquals("Inital test for sub config 1 failed", 0, sub1
775                 .getInt("xmlReload1"));
776         config.addConfiguration(sub1);
777         assertEquals(
778                 "Could not get value for sub config 1 from combined config", 0,
779                 config.getInt("xmlReload1"));
780         c1.setReloadingStrategy(new FileAlwaysReloadingStrategy());
781         writeReloadFile(RELOAD_XML_NAME, reloadContent, 1);
782         assertEquals("Reload of sub config 1 not detected", 1, config
783                 .getInt("xmlReload1"));
784     }
785 
786     @Test
787     public void testConcurrentGetAndReload() throws Exception
788     {
789         final int threadCount = 5;
790         final int loopCount = 1000;
791         config.setForceReloadCheck(true);
792         config.setNodeCombiner(new MergeCombiner());
793         final XMLConfiguration xml = new XMLConfiguration("configA.xml");
794         xml.setReloadingStrategy(new FileRandomReloadingStrategy());
795         config.addConfiguration(xml);
796         final XMLConfiguration xml2 = new XMLConfiguration("configB.xml");
797         xml2.setReloadingStrategy(new FileRandomReloadingStrategy());
798         config.addConfiguration(xml2);
799         config.setExpressionEngine(new XPathExpressionEngine());
800 
801         assertEquals(config.getString("/property[@name='config']/@value"), "100");
802 
803         Thread testThreads[] = new Thread[threadCount];
804         int failures[] = new int[threadCount];
805 
806         for (int i = 0; i < testThreads.length; ++i)
807         {
808             testThreads[i] = new ReloadThread(config, failures, i, loopCount);
809             testThreads[i].start();
810         }
811 
812         int totalFailures = 0;
813         for (int i = 0; i < testThreads.length; ++i)
814         {
815             testThreads[i].join();
816             totalFailures += failures[i];
817         }
818         assertTrue(totalFailures + " failures Occurred", totalFailures == 0);
819     }
820 
821     /**
822      * Tests whether a combined configuration can be copied to an XML
823      * configuration. This test is related to CONFIGURATION-445.
824      */
825     @Test
826     public void testCombinedCopyToXML() throws ConfigurationException
827     {
828         XMLConfiguration x1 = new XMLConfiguration();
829         x1.addProperty("key1", "value1");
830         x1.addProperty("key1[@override]", "USER1");
831         x1.addProperty("key2", "value2");
832         x1.addProperty("key2[@override]", "USER2");
833         XMLConfiguration x2 = new XMLConfiguration();
834         x2.addProperty("key2", "value2.2");
835         x2.addProperty("key2[@override]", "USER2");
836         config.setNodeCombiner(new OverrideCombiner());
837         config.addConfiguration(x2);
838         config.addConfiguration(x1);
839         XMLConfiguration x3 = new XMLConfiguration(config);
840         assertEquals("Wrong element value", "value2.2", x3.getString("key2"));
841         assertEquals("Wrong attribute value", "USER2",
842                 x3.getString("key2[@override]"));
843         StringWriter w = new StringWriter();
844         x3.save(w);
845         String s = w.toString();
846         x3 = new XMLConfiguration();
847         x3.load(new StringReader(s));
848         assertEquals("Wrong element value after load", "value2.2",
849                 x3.getString("key2"));
850         assertEquals("Wrong attribute value after load", "USER2",
851                 x3.getString("key2[@override]"));
852     }
853 
854     private class ReloadThread extends Thread
855     {
856         CombinedConfiguration combined;
857         int[] failures;
858         int index;
859         int count;
860 
861         ReloadThread(CombinedConfiguration config, int[] failures, int index, int count)
862         {
863             combined = config;
864             this.failures = failures;
865             this.index = index;
866             this.count = count;
867         }
868         @Override
869         public void run()
870         {
871             failures[index] = 0;
872             for (int i = 0; i < count; i++)
873             {
874                 try
875                 {
876                     String value = combined.getString("/property[@name='config']/@value");
877                     if (value == null || !value.equals("100"))
878                     {
879                         ++failures[index];
880                     }
881                 }
882                 catch (Exception ex)
883                 {
884                     ++failures[index];
885                 }
886             }
887         }
888     }
889 
890     /**
891      * Helper method for writing a file. The file is also added to a list and
892      * will be deleted in teadDown() automatically.
893      *
894      * @param file the file to be written
895      * @param content the file's content
896      * @throws IOException if an error occurs
897      */
898     private void writeFile(File file, String content) throws IOException
899     {
900         PrintWriter out = null;
901         try
902         {
903             out = new PrintWriter(new FileWriter(file));
904             out.print(content);
905         }
906         finally
907         {
908             if (out != null)
909             {
910                 out.close();
911             }
912         }
913     }
914 
915     /**
916      * Helper method for writing a test file. The file will be created in the
917      * test directory. It is also scheduled for automatic deletion after the
918      * test.
919      *
920      * @param fileName the name of the test file
921      * @param content the content of the file
922      * @return the <code>File</code> object for the test file
923      * @throws IOException if an error occurs
924      */
925     private File writeFile(String fileName, String content) throws IOException
926     {
927         File file = new File(folder.getRoot(), fileName);
928         writeFile(file, content);
929         return file;
930     }
931 
932     /**
933      * Writes a file for testing reload operations.
934      *
935      * @param name the name of the reload test file
936      * @param content the content of the file
937      * @param value the value of the reload test property
938      * @return the file that was written
939      * @throws IOException if an error occurs
940      */
941     private File writeReloadFile(String name, String content, int value)
942             throws IOException
943     {
944         return writeFile(name, MessageFormat.format(content, new Object[] {
945             new Integer(value)
946         }));
947     }
948 
949     /**
950      * Helper method for creating a test configuration to be added to the
951      * combined configuration.
952      *
953      * @return the test configuration
954      */
955     private AbstractConfiguration setUpTestConfiguration()
956     {
957         HierarchicalConfiguration config = new HierarchicalConfiguration();
958         config.addProperty(TEST_KEY, Boolean.TRUE);
959         config.addProperty("test.comment", "This is a test");
960         return config;
961     }
962 
963     /**
964      * Test event listener class for checking if the expected invalidate events
965      * are fired.
966      */
967     static class CombinedListener implements ConfigurationListener
968     {
969         int invalidateEvents;
970 
971         int otherEvents;
972 
973         public void configurationChanged(ConfigurationEvent event)
974         {
975             if (event.getType() == CombinedConfiguration.EVENT_COMBINED_INVALIDATE)
976             {
977                 invalidateEvents++;
978             }
979             else
980             {
981                 otherEvents++;
982             }
983         }
984 
985         /**
986          * Checks if the expected number of events was fired.
987          *
988          * @param expectedInvalidate the expected number of invalidate events
989          * @param expectedOthers the expected number of other events
990          */
991         public void checkEvent(int expectedInvalidate, int expectedOthers)
992         {
993             Assert.assertEquals("Wrong number of invalidate events",
994                     expectedInvalidate, invalidateEvents);
995             Assert.assertEquals("Wrong number of other events", expectedOthers,
996                     otherEvents);
997         }
998     }
999 }