001    /*
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *     http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.commons.configuration;
018    
019    import static org.junit.Assert.assertEquals;
020    import static org.junit.Assert.assertFalse;
021    import static org.junit.Assert.assertNotNull;
022    import static org.junit.Assert.assertNull;
023    import static org.junit.Assert.assertSame;
024    import static org.junit.Assert.assertTrue;
025    
026    import java.io.File;
027    import java.io.FileReader;
028    import java.io.IOException;
029    import java.io.OutputStreamWriter;
030    import java.io.Reader;
031    import java.io.Writer;
032    import java.util.Collection;
033    import java.util.HashMap;
034    import java.util.Map;
035    import java.util.Set;
036    
037    import org.apache.commons.configuration.beanutils.BeanHelper;
038    import org.apache.commons.configuration.event.ConfigurationEvent;
039    import org.apache.commons.configuration.event.ConfigurationListener;
040    import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
041    import org.apache.commons.configuration.tree.DefaultConfigurationNode;
042    import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
043    import org.apache.commons.vfs2.FileName;
044    import org.apache.commons.vfs2.FileObject;
045    import org.apache.commons.vfs2.FileSystemManager;
046    import org.apache.commons.vfs2.FileSystemOptions;
047    import org.apache.commons.vfs2.VFS;
048    import org.junit.After;
049    import org.junit.Before;
050    import org.junit.Test;
051    
052    /**
053     * Test class for DefaultConfigurationBuilder.
054     *
055     * @author <a
056     * href="http://commons.apache.org/configuration/team-list.html">Commons
057     * Configuration team</a>
058     * @version $Id: TestWebdavConfigurationBuilder.java 1225331 2011-12-28 20:55:49Z oheger $
059     */
060    public class TestWebdavConfigurationBuilder
061            implements FileOptionsProvider, ConfigurationListener
062    {
063        /** Test configuration definition file. */
064        private static final String TEST_FILE =
065                "testDigesterConfiguration.xml";
066    
067        private static final String ADDITIONAL_FILE =
068                "testDigesterConfiguration2.xml";
069    
070        private static final String OPTIONAL_FILE =
071                "testDigesterOptionalConfiguration.xml";
072    
073        private static final String OPTIONALEX_FILE =
074                "testDigesterOptionalConfigurationEx.xml";
075    
076        private static final String MULTI_FILE =
077                "testDigesterConfiguration3.xml";
078    
079        private static final String INIT_FILE =
080                "testComplexInitialization.xml";
081    
082        private static final String CLASS_FILE =
083                "testExtendedClass.xml";
084    
085        private static final String PROVIDER_FILE =
086                "testConfigurationProvider.xml";
087    
088        private static final String EXTENDED_PROVIDER_FILE =
089                "testExtendedXMLConfigurationProvider.xml";
090    
091        private static final String GLOBAL_LOOKUP_FILE =
092                "testGlobalLookup.xml";
093    
094        private static final String SYSTEM_PROPS_FILE =
095                "testSystemProperties.xml";
096    
097        private static final String VALIDATION_FILE =
098                "testValidation.xml";
099    
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    }