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.assertNull;
23  import static org.junit.Assert.assertSame;
24  import static org.junit.Assert.assertTrue;
25  
26  import java.io.File;
27  import java.io.FileReader;
28  import java.io.IOException;
29  import java.io.OutputStreamWriter;
30  import java.io.Reader;
31  import java.io.Writer;
32  import java.util.Collection;
33  import java.util.HashMap;
34  import java.util.Map;
35  import java.util.Set;
36  
37  import org.apache.commons.configuration.beanutils.BeanHelper;
38  import org.apache.commons.configuration.event.ConfigurationEvent;
39  import org.apache.commons.configuration.event.ConfigurationListener;
40  import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
41  import org.apache.commons.configuration.tree.DefaultConfigurationNode;
42  import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
43  import org.apache.commons.vfs2.FileName;
44  import org.apache.commons.vfs2.FileObject;
45  import org.apache.commons.vfs2.FileSystemManager;
46  import org.apache.commons.vfs2.FileSystemOptions;
47  import org.apache.commons.vfs2.VFS;
48  import org.junit.After;
49  import org.junit.Before;
50  import org.junit.Test;
51  
52  /**
53   * Test class for DefaultConfigurationBuilder.
54   *
55   * @author <a
56   * href="http://commons.apache.org/configuration/team-list.html">Commons
57   * Configuration team</a>
58   * @version $Id: TestWebdavConfigurationBuilder.java 1225331 2011-12-28 20:55:49Z oheger $
59   */
60  public class TestWebdavConfigurationBuilder
61          implements FileOptionsProvider, ConfigurationListener
62  {
63      /** Test configuration definition file. */
64      private static final String TEST_FILE =
65              "testDigesterConfiguration.xml";
66  
67      private static final String ADDITIONAL_FILE =
68              "testDigesterConfiguration2.xml";
69  
70      private static final String OPTIONAL_FILE =
71              "testDigesterOptionalConfiguration.xml";
72  
73      private static final String OPTIONALEX_FILE =
74              "testDigesterOptionalConfigurationEx.xml";
75  
76      private static final String MULTI_FILE =
77              "testDigesterConfiguration3.xml";
78  
79      private static final String INIT_FILE =
80              "testComplexInitialization.xml";
81  
82      private static final String CLASS_FILE =
83              "testExtendedClass.xml";
84  
85      private static final String PROVIDER_FILE =
86              "testConfigurationProvider.xml";
87  
88      private static final String EXTENDED_PROVIDER_FILE =
89              "testExtendedXMLConfigurationProvider.xml";
90  
91      private static final String GLOBAL_LOOKUP_FILE =
92              "testGlobalLookup.xml";
93  
94      private static final String SYSTEM_PROPS_FILE =
95              "testSystemProperties.xml";
96  
97      private static final String VALIDATION_FILE =
98              "testValidation.xml";
99  
100     private static final String MULTI_TENENT_FILE =
101             "testMultiTenentConfigurationBuilder.xml";
102 
103     private static final String FILERELOAD2_FILE =
104             "testFileReloadConfigurationBuilder2.xml";
105 
106     private static final String FILERELOAD_1001_FILE =
107             "testwrite/testMultiConfiguration_1001.xml";
108 
109     private static final String FILERELOAD_1002_FILE =
110             "testwrite/testMultiConfiguration_1002.xml";
111 
112     private static final String TEST_PROPERTIES = "test.properties.xml";
113 
114     private static final String TEST_SAVE = "testsave.xml";
115 
116     /** Constant for the name of an optional configuration.*/
117     private static final String OPTIONAL_NAME = "optionalConfig";
118 
119     private Map<String, Object> options;
120 
121     /** true when a file is changed */
122     private boolean configChanged = false;
123 
124     /** Stores the object to be tested. */
125     DefaultConfigurationBuilder factory;
126 
127     private String getBasePath()
128     {
129         String path = System.getProperty("test.webdav.base");
130         assertNotNull("No base url provided", path);
131         return path;
132     }
133 
134     @Before
135     public void setUp() throws Exception
136     {
137         System.setProperty("java.naming.factory.initial",
138                         "org.apache.commons.configuration.MockInitialContextFactory");
139         System.setProperty("test_file_xml", "test.xml");
140         System.setProperty("test_file_combine", "testcombine1.xml");
141         System.setProperty("basePath", getBasePath());
142         FileSystem fs = new VFSFileSystem();
143         fs.setFileOptionsProvider(this);
144         FileSystem.setDefaultFileSystem(fs);
145         factory = new DefaultConfigurationBuilder();
146         factory.setBasePath(getBasePath());
147         factory.clearErrorListeners();  // avoid exception messages
148     }
149 
150     @After
151     public void tearDown() throws Exception
152     {
153         FileSystem.resetDefaultFileSystem();
154     }
155 
156     /**
157      * Tests the isReservedNode() method of ConfigurationDeclaration.
158      */
159     @Test
160     public void testConfigurationDeclarationIsReserved()
161     {
162         DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
163                 factory, factory);
164         DefaultConfigurationNode parent = new DefaultConfigurationNode();
165         DefaultConfigurationNode nd = new DefaultConfigurationNode("at");
166         parent.addAttribute(nd);
167         assertTrue("Attribute at not recognized", decl.isReservedNode(nd));
168         nd = new DefaultConfigurationNode("optional");
169         parent.addAttribute(nd);
170         assertTrue("Attribute optional not recognized", decl.isReservedNode(nd));
171         nd = new DefaultConfigurationNode("config-class");
172         parent.addAttribute(nd);
173         assertTrue("Inherited attribute not recognized", decl
174                 .isReservedNode(nd));
175         nd = new DefaultConfigurationNode("different");
176         parent.addAttribute(nd);
177         assertFalse("Wrong reserved attribute", decl.isReservedNode(nd));
178         nd = new DefaultConfigurationNode("at");
179         parent.addChild(nd);
180         assertFalse("Node type not evaluated", decl.isReservedNode(nd));
181     }
182 
183     /**
184      * Tests if the at attribute is correctly detected as reserved attribute.
185      */
186     @Test
187     public void testConfigurationDeclarationIsReservedAt()
188     {
189         checkOldReservedAttribute("at");
190     }
191 
192     /**
193      * Tests if the optional attribute is correctly detected as reserved
194      * attribute.
195      */
196     @Test
197     public void testConfigurationDeclarationIsReservedOptional()
198     {
199         checkOldReservedAttribute("optional");
200     }
201 
202     /**
203      * Tests if special reserved attributes are recognized by the
204      * isReservedNode() method. For compatibility reasons the attributes "at"
205      * and "optional" are also treated as reserved attributes, but only if there
206      * are no corresponding attributes with the "config-" prefix.
207      *
208      * @param name the attribute name
209      */
210     private void checkOldReservedAttribute(String name)
211     {
212         DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
213                 factory, factory);
214         DefaultConfigurationNode parent = new DefaultConfigurationNode();
215         DefaultConfigurationNode nd = new DefaultConfigurationNode("config-"
216                 + name);
217         parent.addAttribute(nd);
218         assertTrue("config-" + name + " attribute not recognized", decl
219                 .isReservedNode(nd));
220         DefaultConfigurationNode nd2 = new DefaultConfigurationNode(name);
221         parent.addAttribute(nd2);
222         assertFalse(name + " is reserved though config- exists", decl
223                 .isReservedNode(nd2));
224         assertTrue("config- attribute not recognized when " + name + " exists",
225                 decl.isReservedNode(nd));
226     }
227 
228     /**
229      * Tests access to certain reserved attributes of a
230      * ConfigurationDeclaration.
231      */
232     @Test
233     public void testConfigurationDeclarationGetAttributes()
234     {
235         factory.addProperty("xml.fileName", "test.xml");
236         DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
237                 factory, factory.configurationAt("xml"));
238         assertNull("Found an at attribute", decl.getAt());
239         assertFalse("Found an optional attribute", decl.isOptional());
240         factory.addProperty("xml[@config-at]", "test1");
241         assertEquals("Wrong value of at attribute", "test1", decl.getAt());
242         factory.addProperty("xml[@at]", "test2");
243         assertEquals("Wrong value of config-at attribute", "test1", decl.getAt());
244         factory.clearProperty("xml[@config-at]");
245         assertEquals("Old at attribute not detected", "test2", decl.getAt());
246         factory.addProperty("xml[@config-optional]", "true");
247         assertTrue("Wrong value of optional attribute", decl.isOptional());
248         factory.addProperty("xml[@optional]", "false");
249         assertTrue("Wrong value of config-optional attribute", decl.isOptional());
250         factory.clearProperty("xml[@config-optional]");
251         factory.setProperty("xml[@optional]", Boolean.TRUE);
252         assertTrue("Old optional attribute not detected", decl.isOptional());
253     }
254 
255     /**
256      * Tests whether an invalid value of an optional attribute is detected.
257      */
258     @Test(expected = ConfigurationRuntimeException.class)
259     public void testConfigurationDeclarationOptionalAttributeInvalid()
260     {
261         factory.addProperty("xml.fileName", "test.xml");
262         DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
263                 factory, factory.configurationAt("xml"));
264         factory.setProperty("xml[@optional]", "invalid value");
265         decl.isOptional();
266     }
267 
268     /**
269      * Tests adding a new configuration provider.
270      */
271     @Test
272     public void testAddConfigurationProvider()
273     {
274         DefaultConfigurationBuilder.ConfigurationProvider provider = new DefaultConfigurationBuilder.ConfigurationProvider();
275         assertNull("Provider already registered", factory
276                 .providerForTag("test"));
277         factory.addConfigurationProvider("test", provider);
278         assertSame("Provider not registered", provider, factory
279                 .providerForTag("test"));
280     }
281 
282     /**
283      * Tries to register a null configuration provider. This should cause an
284      * exception.
285      */
286     @Test(expected = IllegalArgumentException.class)
287     public void testAddConfigurationProviderNull()
288     {
289         factory.addConfigurationProvider("test", null);
290     }
291 
292     /**
293      * Tries to register a configuration provider for a null tag. This should
294      * cause an exception to be thrown.
295      */
296     @Test(expected = IllegalArgumentException.class)
297     public void testAddConfigurationProviderNullTag()
298     {
299         factory.addConfigurationProvider(null,
300                 new DefaultConfigurationBuilder.ConfigurationProvider());
301     }
302 
303     /**
304      * Tests removing configuration providers.
305      */
306     @Test
307     public void testRemoveConfigurationProvider()
308     {
309         assertNull("Removing unknown provider", factory
310                 .removeConfigurationProvider("test"));
311         assertNull("Removing provider for null tag", factory
312                 .removeConfigurationProvider(null));
313         DefaultConfigurationBuilder.ConfigurationProvider provider = new DefaultConfigurationBuilder.ConfigurationProvider();
314         factory.addConfigurationProvider("test", provider);
315         assertSame("Failed to remove provider", provider, factory
316                 .removeConfigurationProvider("test"));
317         assertNull("Provider still registered", factory.providerForTag("test"));
318     }
319 
320     /**
321      * Tests creating a configuration object from a configuration declaration.
322      */
323     @Test
324     public void testConfigurationBeanFactoryCreateBean()
325     {
326         factory.addConfigurationProvider("test",
327                 new DefaultConfigurationBuilder.ConfigurationProvider(
328                         PropertiesConfiguration.class));
329         factory.addProperty("test[@throwExceptionOnMissing]", "true");
330         DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
331                 factory, factory.configurationAt("test"));
332         PropertiesConfiguration conf = (PropertiesConfiguration) BeanHelper
333                 .createBean(decl);
334         assertTrue("Property was not initialized", conf
335                 .isThrowExceptionOnMissing());
336     }
337 
338     /**
339      * Tests creating a configuration object from an unknown tag. This should
340      * cause an exception.
341      */
342     @Test(expected = ConfigurationRuntimeException.class)
343     public void testConfigurationBeanFactoryCreateUnknownTag()
344     {
345         factory.addProperty("test[@throwExceptionOnMissing]", "true");
346         DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
347                 factory, factory.configurationAt("test"));
348         BeanHelper.createBean(decl);
349     }
350 
351     /**
352      * Tests loading a simple configuration definition file.
353      */
354     @Test
355     public void testLoadConfiguration() throws ConfigurationException
356     {
357         factory.setFileName(TEST_FILE);
358         checkConfiguration();
359     }
360 
361     /**
362      * Tests the file constructor.
363      */
364     @Test
365     public void testLoadConfigurationFromFile() throws ConfigurationException
366     {
367         factory = new DefaultConfigurationBuilder(getBasePath() + TEST_FILE);
368         checkConfiguration();
369     }
370 
371     /**
372      * This test doesn't test DefaultConfigurationBuilder. It tests saving a file
373      * using Webdav file options.
374      */
375     @Test
376     public void testSaveConfiguration() throws ConfigurationException
377     {
378         options = new HashMap<String, Object>();
379         options.put(FileOptionsProvider.VERSIONING, Boolean.TRUE);
380         options.put(FileOptionsProvider.CURRENT_USER, "TestUser");
381         XMLConfiguration conf = new XMLConfiguration();
382         conf.setFileName(getBasePath() + TEST_PROPERTIES);
383         conf.load();
384         conf.save(getBasePath() + TEST_SAVE);
385         XMLConfiguration checkConfig = new XMLConfiguration();
386         checkConfig.setFileName(getBasePath() + TEST_SAVE);
387         checkSavedConfig(conf, checkConfig);
388         options = null;
389     }
390 
391     /**
392      * Tests if the configuration was correctly created by the factory.
393      */
394     private void checkConfiguration() throws ConfigurationException
395     {
396         CombinedConfiguration compositeConfiguration = (CombinedConfiguration) factory
397                 .getConfiguration();
398 
399         assertEquals("Number of configurations", 3, compositeConfiguration
400                 .getNumberOfConfigurations());
401         assertEquals(PropertiesConfiguration.class, compositeConfiguration
402                 .getConfiguration(0).getClass());
403         assertEquals(XMLPropertiesConfiguration.class, compositeConfiguration
404                 .getConfiguration(1).getClass());
405         assertEquals(XMLConfiguration.class, compositeConfiguration
406                 .getConfiguration(2).getClass());
407 
408         // check the first configuration
409         PropertiesConfiguration pc = (PropertiesConfiguration) compositeConfiguration
410                 .getConfiguration(0);
411         assertNotNull("Make sure we have a fileName: " + pc.getFileName(), pc
412                 .getFileName());
413 
414         // check some properties
415         checkProperties(compositeConfiguration);
416     }
417 
418     /**
419      * Checks if the passed in configuration contains the expected properties.
420      *
421      * @param compositeConfiguration the configuration to check
422      */
423     private void checkProperties(Configuration compositeConfiguration)
424     {
425         assertTrue("Make sure we have loaded our key", compositeConfiguration
426                 .getBoolean("test.boolean"));
427         assertEquals("I'm complex!", compositeConfiguration
428                 .getProperty("element2.subelement.subsubelement"));
429         assertEquals("property in the XMLPropertiesConfiguration", "value1",
430                 compositeConfiguration.getProperty("key1"));
431     }
432 
433     /**
434      * Tests loading a configuration definition file with an additional section.
435      */
436     @Test
437     public void testLoadAdditional() throws ConfigurationException
438     {
439         factory.setFileName(ADDITIONAL_FILE);
440         CombinedConfiguration compositeConfiguration = (CombinedConfiguration) factory
441                 .getConfiguration();
442         assertEquals("Verify how many configs", 2, compositeConfiguration
443                 .getNumberOfConfigurations());
444 
445         // Test if union was constructed correctly
446         Object prop = compositeConfiguration.getProperty("tables.table.name");
447         assertTrue(prop instanceof Collection);
448         assertEquals(3, ((Collection<?>) prop).size());
449         assertEquals("users", compositeConfiguration
450                 .getProperty("tables.table(0).name"));
451         assertEquals("documents", compositeConfiguration
452                 .getProperty("tables.table(1).name"));
453         assertEquals("tasks", compositeConfiguration
454                 .getProperty("tables.table(2).name"));
455 
456         prop = compositeConfiguration
457                 .getProperty("tables.table.fields.field.name");
458         assertTrue(prop instanceof Collection);
459         assertEquals(17, ((Collection<?>) prop).size());
460 
461         assertEquals("smtp.mydomain.org", compositeConfiguration
462                 .getString("mail.host.smtp"));
463         assertEquals("pop3.mydomain.org", compositeConfiguration
464                 .getString("mail.host.pop"));
465 
466         // This was overridden
467         assertEquals("masterOfPost", compositeConfiguration
468                 .getString("mail.account.user"));
469         assertEquals("topsecret", compositeConfiguration
470                 .getString("mail.account.psswd"));
471 
472         // This was overridden, too, but not in additional section
473         assertEquals("enhanced factory", compositeConfiguration
474                 .getString("test.configuration"));
475     }
476 
477     /**
478      * Tests whether a default log error listener is registered at the builder
479      * instance.
480      */
481     @Test
482     public void testLogErrorListener()
483     {
484         assertEquals("No default error listener registered", 1,
485                 new DefaultConfigurationBuilder().getErrorListeners().size());
486     }
487 
488     /**
489      * Tests loading a definition file that contains optional configurations.
490      */
491     @Test
492     public void testLoadOptional() throws Exception
493     {
494         factory.setFileName(OPTIONAL_FILE);
495         Configuration config = factory.getConfiguration();
496         assertTrue(config.getBoolean("test.boolean"));
497         assertEquals("value", config.getProperty("element"));
498     }
499 
500     /**
501      * Tests whether loading a failing optional configuration causes an error
502      * event.
503      */
504     @Test
505     public void testLoadOptionalErrorEvent() throws Exception
506     {
507         factory.clearErrorListeners();
508         ConfigurationErrorListenerImpl listener = new ConfigurationErrorListenerImpl();
509         factory.addErrorListener(listener);
510         prepareOptionalTest("configuration", false);
511         listener.verify(DefaultConfigurationBuilder.EVENT_ERR_LOAD_OPTIONAL,
512                 OPTIONAL_NAME, null);
513     }
514 
515     /**
516      * Tests loading a definition file with optional and non optional
517      * configuration sources. One non optional does not exist, so this should
518      * cause an exception.
519      */
520     @Test(expected = ConfigurationException.class)
521     public void testLoadOptionalWithException() throws ConfigurationException
522     {
523         factory.setFileName(OPTIONALEX_FILE);
524         factory.getConfiguration();
525     }
526 
527     /**
528      * Tries to load a configuration file with an optional, non file-based
529      * configuration. The optional attribute should work for other configuration
530      * classes, too.
531      */
532     @Test
533     public void testLoadOptionalNonFileBased() throws ConfigurationException
534     {
535         CombinedConfiguration config = prepareOptionalTest("configuration", false);
536         assertTrue("Configuration not empty", config.isEmpty());
537         assertEquals("Wrong number of configurations", 0, config
538                 .getNumberOfConfigurations());
539     }
540 
541     /**
542      * Tests loading an embedded optional configuration builder with the force
543      * create attribute.
544      */
545     @Test
546     public void testLoadOptionalBuilderForceCreate()
547             throws ConfigurationException
548     {
549         CombinedConfiguration config = prepareOptionalTest("configuration",
550                 true);
551         assertEquals("Wrong number of configurations", 1, config
552                 .getNumberOfConfigurations());
553         assertTrue(
554                 "Wrong optional configuration type",
555                 config.getConfiguration(OPTIONAL_NAME) instanceof CombinedConfiguration);
556     }
557 
558     /**
559      * Tests loading an optional configuration with the force create attribute
560      * set. The provider will always throw an exception. In this case the
561      * configuration will not be added to the resulting combined configuration.
562      */
563     @Test
564     public void testLoadOptionalForceCreateWithException()
565             throws ConfigurationException
566     {
567         factory.addConfigurationProvider("test",
568                 new DefaultConfigurationBuilder.ConfigurationBuilderProvider()
569                 {
570                     // Throw an exception here, too
571                     @Override
572                     public AbstractConfiguration getEmptyConfiguration(
573                             DefaultConfigurationBuilder.ConfigurationDeclaration decl) throws Exception
574                     {
575                         throw new Exception("Unable to create configuration!");
576                     }
577                 });
578         CombinedConfiguration config = prepareOptionalTest("test", true);
579         assertEquals("Optional configuration could be created", 0, config
580                 .getNumberOfConfigurations());
581     }
582 
583     /**
584      * Prepares a test for loading a configuration definition file with an
585      * optional configuration declaration.
586      *
587      * @param tag the tag name with the optional configuration
588      * @param force the forceCreate attribute
589      * @return the combined configuration obtained from the builder
590      * @throws ConfigurationException if an error occurs
591      */
592     private CombinedConfiguration prepareOptionalTest(String tag, boolean force)
593             throws ConfigurationException
594     {
595         String prefix = "override." + tag;
596         factory.addProperty(prefix + "[@fileName]", "nonExisting.xml");
597         factory.addProperty(prefix + "[@config-optional]", Boolean.TRUE);
598         factory.addProperty(prefix + "[@config-name]", OPTIONAL_NAME);
599         if (force)
600         {
601             factory.addProperty(prefix + "[@config-forceCreate]", Boolean.TRUE);
602         }
603         return factory.getConfiguration(false);
604     }
605 
606     /**
607      * Tests loading a definition file with multiple different sources.
608      */
609     @Test
610     public void testLoadDifferentSources() throws ConfigurationException
611     {
612         factory.setFileName(MULTI_FILE);
613         Configuration config = factory.getConfiguration();
614         assertFalse(config.isEmpty());
615         assertTrue(config instanceof CombinedConfiguration);
616         CombinedConfiguration cc = (CombinedConfiguration) config;
617         assertEquals("Wrong number of configurations", 1, cc
618                 .getNumberOfConfigurations());
619 
620         assertNotNull(config
621                 .getProperty("tables.table(0).fields.field(2).name"));
622         assertNotNull(config.getProperty("element2.subelement.subsubelement"));
623         assertEquals("value", config.getProperty("element3"));
624         assertEquals("foo", config.getProperty("element3[@name]"));
625         assertNotNull(config.getProperty("mail.account.user"));
626 
627         // test JNDIConfiguration
628         assertNotNull(config.getProperty("test.onlyinjndi"));
629         assertTrue(config.getBoolean("test.onlyinjndi"));
630 
631         Configuration subset = config.subset("test");
632         assertNotNull(subset.getProperty("onlyinjndi"));
633         assertTrue(subset.getBoolean("onlyinjndi"));
634 
635         // test SystemConfiguration
636         assertNotNull(config.getProperty("java.version"));
637         assertEquals(System.getProperty("java.version"), config
638                 .getString("java.version"));
639     }
640 
641     /**
642      * Tests if the base path is correctly evaluated.
643      */
644     @Test
645     public void testSetConfigurationBasePath() throws ConfigurationException
646     {
647         factory.addProperty("properties[@fileName]", "test.properties");
648         File deepDir = new File("conf/config/deep");
649         factory.setConfigurationBasePath(deepDir.getAbsolutePath());
650 
651         Configuration config = factory.getConfiguration(false);
652         assertEquals("Wrong property value", "somevalue", config
653                 .getString("somekey"));
654     }
655 
656     /**
657      * Tests reading a configuration definition file that contains complex
658      * initialization of properties of the declared configuration sources.
659      */
660     @Test
661     public void testComplexInitialization() throws ConfigurationException
662     {
663         factory.setFileName(INIT_FILE);
664         CombinedConfiguration cc = (CombinedConfiguration) factory
665                 .getConfiguration();
666 
667         assertEquals("System property not found", "test.xml",
668                 cc.getString("test_file_xml"));
669         PropertiesConfiguration c1 = (PropertiesConfiguration) cc
670                 .getConfiguration(1);
671         assertTrue(
672                 "Reloading strategy was not set",
673                 c1.getReloadingStrategy() instanceof FileChangedReloadingStrategy);
674         assertEquals("Refresh delay was not set", 10000,
675                 ((FileChangedReloadingStrategy) c1.getReloadingStrategy())
676                         .getRefreshDelay());
677 
678         Configuration xmlConf = cc.getConfiguration("xml");
679         assertEquals("Property not found", "I'm complex!", xmlConf
680                 .getString("element2/subelement/subsubelement"));
681         assertEquals("List index not found", "two", xmlConf
682                 .getString("list[0]/item[1]"));
683         assertEquals("Property in combiner file not found", "yellow", cc
684                 .getString("/gui/selcolor"));
685 
686         assertTrue("Delimiter flag was not set", cc
687                 .isDelimiterParsingDisabled());
688         assertTrue("Expression engine was not set",
689                 cc.getExpressionEngine() instanceof XPathExpressionEngine);
690     }
691 
692     /**
693      * Tests if the returned combined configuration has the expected structure.
694      */
695     @Test
696     public void testCombinedConfiguration() throws ConfigurationException
697     {
698         factory.setFileName(INIT_FILE);
699         CombinedConfiguration cc = (CombinedConfiguration) factory
700                 .getConfiguration();
701         assertNotNull("Properties configuration not found", cc
702                 .getConfiguration("properties"));
703         assertNotNull("XML configuration not found", cc.getConfiguration("xml"));
704         assertEquals("Wrong number of contained configs", 4, cc
705                 .getNumberOfConfigurations());
706 
707         CombinedConfiguration cc2 = (CombinedConfiguration) cc
708                 .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME);
709         assertNotNull("No additional configuration found", cc2);
710         Set<String> names = cc2.getConfigurationNames();
711         assertEquals("Wrong number of contained additional configs", 2, names
712                 .size());
713         assertTrue("Config 1 not contained", names.contains("combiner1"));
714         assertTrue("Config 2 not contained", names.contains("combiner2"));
715     }
716 
717     /**
718      * Tests the structure of the returned combined configuration if there is no
719      * additional section.
720      */
721     @Test
722     public void testCombinedConfigurationNoAdditional()
723             throws ConfigurationException
724     {
725         factory.setFileName(TEST_FILE);
726         CombinedConfiguration cc = factory.getConfiguration(true);
727         assertNull("Additional configuration was found", cc
728                 .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME));
729     }
730 
731     /**
732      * Tests whether the list node definition was correctly processed.
733      */
734     @Test
735     public void testCombinedConfigurationListNodes()
736             throws ConfigurationException
737     {
738         factory.setFileName(INIT_FILE);
739         CombinedConfiguration cc = factory.getConfiguration(true);
740         Set<String> listNodes = cc.getNodeCombiner().getListNodes();
741         assertEquals("Wrong number of list nodes", 2, listNodes.size());
742         assertTrue("table node not a list node", listNodes.contains("table"));
743         assertTrue("list node not a list node", listNodes.contains("list"));
744 
745         CombinedConfiguration cca = (CombinedConfiguration) cc
746                 .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME);
747         listNodes = cca.getNodeCombiner().getListNodes();
748         assertTrue("Found list nodes for additional combiner", listNodes
749                 .isEmpty());
750     }
751 
752     /**
753      * Tests whether XML settings can be inherited.
754      */
755     @Test
756     public void testLoadXMLWithSettings() throws ConfigurationException,
757             IOException
758     {
759         File confDir = new File("conf");
760         File targetDir = new File("target");
761         File testXMLSource = new File(confDir, "testDtd.xml");
762         File testXMLValidationSource = new File(confDir,
763                 "testValidateInvalid.xml");
764         File testSavedXML = new File(targetDir, "testSave.xml");
765         File testSavedFactory = new File(targetDir, "testSaveFactory.xml");
766         File dtdFile = new File(confDir, "properties.dtd");
767         final String publicId = "http://commons.apache.org/test.dtd";
768 
769         XMLConfiguration config = new XMLConfiguration(testXMLSource);
770         config.setPublicID(publicId);
771         config.save(testSavedXML);
772         factory.addProperty("xml[@fileName]", testSavedXML.getAbsolutePath());
773         factory.addProperty("xml(0)[@validating]", "true");
774         factory.addProperty("xml(-1)[@fileName]", testXMLValidationSource
775                 .getAbsolutePath());
776         factory.addProperty("xml(1)[@config-optional]", "true");
777         factory.addProperty("xml(1)[@validating]", "true");
778         factory.save(testSavedFactory);
779 
780         factory = new DefaultConfigurationBuilder();
781         factory.setFile(testSavedFactory);
782         factory.registerEntityId(publicId, dtdFile.toURI().toURL());
783         factory.clearErrorListeners();
784         Configuration c = factory.getConfiguration();
785         assertEquals("Wrong property value", "value1", c.getString("entry(0)"));
786         assertFalse("Invalid XML source was loaded", c
787                 .containsKey("table.name"));
788 
789         testSavedXML.delete();
790         testSavedFactory.delete();
791     }
792 
793     /**
794      * Tests loading a configuration definition file that defines a custom
795      * result class.
796      */
797     @Test
798     public void testExtendedClass() throws ConfigurationException
799     {
800         factory.setFileName(CLASS_FILE);
801         CombinedConfiguration cc = factory.getConfiguration(true);
802         String prop = (String)cc.getProperty("test");
803         assertEquals("Expected 'Extended', actual '" + prop + "'", "Extended", prop);
804         assertTrue("Wrong result class: " + cc.getClass(),
805                 cc instanceof TestDefaultConfigurationBuilder.ExtendedCombinedConfiguration);
806     }
807 
808     /**
809      * Tests loading a configuration definition file that defines new providers.
810      */
811     @Test
812     public void testConfigurationProvider() throws ConfigurationException
813     {
814         factory.setFileName(PROVIDER_FILE);
815         factory.getConfiguration(true);
816         DefaultConfigurationBuilder.ConfigurationProvider provider = factory
817                 .providerForTag("test");
818         assertNotNull("Provider 'test' not registered", provider);
819     }
820 
821     /**
822      * Tests loading a configuration definition file that defines new providers.
823      */
824     @Test
825     public void testExtendedXMLConfigurationProvider() throws ConfigurationException
826     {
827         factory.setFileName(EXTENDED_PROVIDER_FILE);
828         CombinedConfiguration cc = factory.getConfiguration(true);
829         DefaultConfigurationBuilder.ConfigurationProvider provider = factory
830                 .providerForTag("test");
831         assertNotNull("Provider 'test' not registered", provider);
832         Configuration config = cc.getConfiguration("xml");
833         assertNotNull("Test configuration not present", config);
834         assertTrue("Configuration is not ExtendedXMLConfiguration, is " +
835                 config.getClass().getName(),
836                 config instanceof TestDefaultConfigurationBuilder.ExtendedXMLConfiguration);
837     }
838 
839     @Test
840     public void testGlobalLookup() throws Exception
841     {
842         factory.setFileName(GLOBAL_LOOKUP_FILE);
843         CombinedConfiguration cc = factory.getConfiguration(true);
844         String value = cc.getInterpolator().lookup("test:test_key");
845         assertNotNull("The test key was not located", value);
846         assertEquals("Incorrect value retrieved","test.value",value);
847     }
848 
849     @Test
850     public void testSystemProperties() throws Exception
851     {
852         factory.setFileName(SYSTEM_PROPS_FILE);
853         factory.getConfiguration(true);
854         String value = System.getProperty("key1");
855         assertNotNull("The test key was not located", value);
856         assertEquals("Incorrect value retrieved","value1",value);
857     }
858 
859     @Test
860     public void testValidation() throws Exception
861     {
862         factory.setFileName(VALIDATION_FILE);
863         factory.getConfiguration(true);
864         String value = System.getProperty("key1");
865         assertNotNull("The test key was not located", value);
866         assertEquals("Incorrect value retrieved","value1",value);
867     }
868 
869     @Test
870     public void testMultiTenentConfiguration() throws Exception
871     {
872         factory.setFileName(MULTI_TENENT_FILE);
873         System.getProperties().remove("Id");
874 
875         CombinedConfiguration config = factory.getConfiguration(true);
876         assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
877 
878         verify("1001", config, 15);
879         verify("1002", config, 25);
880         verify("1003", config, 35);
881         verify("1004", config, 50);
882         verify("1005", config, 50);
883     }
884 
885     @Test
886     public void testMultiTenentConfiguration2() throws Exception
887     {
888         factory.setFileName(MULTI_TENENT_FILE);
889         System.setProperty("Id", "1004");
890 
891         CombinedConfiguration config = factory.getConfiguration(true);
892         assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
893 
894         verify("1001", config, 15);
895         verify("1002", config, 25);
896         verify("1003", config, 35);
897         verify("1004", config, 50);
898         verify("1005", config, 50);
899     }
900 
901     @Test
902     public void testMultiTenentConfiguration3() throws Exception
903     {
904         factory.setFileName(MULTI_TENENT_FILE);
905         System.setProperty("Id", "1005");
906 
907         CombinedConfiguration config = factory.getConfiguration(true);
908         assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
909 
910         verify("1001", config, 15);
911         verify("1002", config, 25);
912         verify("1003", config, 35);
913         verify("1004", config, 50);
914         verify("1005", config, 50);
915     }
916 
917     @Test
918     public void testFileChanged() throws Exception
919     {
920         // create a new configuration
921         File input = new File("target/test-classes/testMultiConfiguration_1001.xml");
922         FileObject output = getFile(getBasePath() + FILERELOAD_1001_FILE);
923         output.delete();
924         output.getParent().createFolder();
925         copyFile(input, output);
926 
927         factory.setFileName(getBasePath() + FILERELOAD2_FILE);
928         System.getProperties().remove("Id");
929 
930         CombinedConfiguration config = factory.getConfiguration(true);
931         assertNotNull(config);
932         config.addConfigurationListener(this);
933         verify("1001", config, 15);
934 
935         // Allow time for FileMonitor to set up.
936         Thread.sleep(1000);
937         XMLConfiguration x = new XMLConfiguration(getBasePath() + FILERELOAD_1001_FILE);
938         x.setProperty("rowsPerPage", "50");
939         x.save();
940         // Let FileMonitor detect the change.
941         //Thread.sleep(2000);
942         waitForChange();
943         verify("1001", config, 50);
944         output.delete();
945     }
946 
947     @Test
948     public void testFileChanged2() throws Exception
949     {
950         // create a new configuration
951         File input = new File("target/test-classes/testMultiConfiguration_1002.xml");
952         FileObject output = getFile(getBasePath() + FILERELOAD_1002_FILE);
953         output.delete();
954 
955         factory.setFileName(getBasePath() + FILERELOAD2_FILE);
956         System.getProperties().remove("Id");
957 
958         CombinedConfiguration config = factory.getConfiguration(true);
959         assertNotNull(config);
960         config.addConfigurationListener(this);
961 
962         verify("1002", config, 50);
963         Thread.sleep(1000);
964 
965         output.getParent().createFolder();
966         copyFile(input, output);
967 
968         // Allow time for the monitor to notice the change.
969         //Thread.sleep(2000);
970         waitForChange();
971         verify("1002", config, 25);
972         output.delete();
973     }
974 
975     private void verify(String key, CombinedConfiguration config, int rows)
976     {
977         System.setProperty("Id", key);
978         int actual = config.getInt("rowsPerPage");
979         assertTrue("expected: " + rows + " actual: " + actual, actual == rows);
980     }
981 
982     public Map<String, Object> getOptions()
983     {
984         return this.options;
985     }
986 
987     /**
988      * Helper method for checking if a save operation was successful. Loads a
989      * saved configuration and then tests against a reference configuration.
990      * @param conf the original configuration
991      * @param newConfig the configuration to check
992      * @throws ConfigurationException if an error occurs
993      */
994     private void checkSavedConfig(XMLConfiguration conf, FileConfiguration newConfig)
995         throws ConfigurationException
996     {
997         newConfig.load();
998         ConfigurationAssert.assertEquals(conf, newConfig);
999     }
1000 
1001     private FileObject getFile(String fileName) throws Exception
1002     {
1003         FileSystemManager manager = VFS.getManager();
1004         FileName file = manager.resolveURI(fileName);
1005         FileName base = file.getParent();
1006         FileName path = manager.resolveName(base, file.getBaseName());
1007         FileSystemOptions opts = new FileSystemOptions();
1008         return manager.resolveFile(path.getURI(), opts);
1009     }
1010 
1011     private void copyFile(File input, FileObject output) throws IOException
1012     {
1013         Reader reader = new FileReader(input);
1014         Writer writer = new OutputStreamWriter(output.getContent().getOutputStream());
1015         char[] buffer = new char[4096];
1016         int n = 0;
1017         while (-1 != (n = reader.read(buffer)))
1018         {
1019             writer.write(buffer, 0, n);
1020         }
1021         reader.close();
1022         writer.close();
1023     }
1024 
1025     private void waitForChange()
1026     {
1027         synchronized(this)
1028         {
1029             try
1030             {
1031                 int count = 0;
1032                 while (!configChanged && count++ <= 3)
1033                 {
1034                     this.wait(5000);
1035                 }
1036             }
1037             catch (InterruptedException ie)
1038             {
1039                 throw new IllegalStateException("wait timed out");
1040             }
1041             finally
1042             {
1043                 configChanged = false;
1044             }
1045         }
1046     }
1047 
1048     public void configurationChanged(ConfigurationEvent event)
1049     {
1050         if (event.getType() == AbstractFileConfiguration.EVENT_CONFIG_CHANGED)
1051         {
1052             synchronized(this)
1053             {
1054                 configChanged = true;
1055                 this.notify();
1056             }
1057         }
1058     }
1059 }