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  import static org.junit.Assert.fail;
26  
27  import java.io.File;
28  import java.io.StringWriter;
29  import java.lang.reflect.InvocationHandler;
30  import java.lang.reflect.Method;
31  import java.lang.reflect.Proxy;
32  import java.net.URL;
33  import java.util.Collection;
34  import java.util.HashMap;
35  import java.util.Iterator;
36  import java.util.List;
37  import java.util.Map;
38  import java.util.Set;
39  
40  import org.apache.commons.configuration.beanutils.BeanHelper;
41  import org.apache.commons.configuration.event.ConfigurationListenerTestImpl;
42  import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy;
43  import org.apache.commons.configuration.tree.ConfigurationNode;
44  import org.apache.commons.configuration.tree.DefaultConfigurationNode;
45  import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
46  import org.apache.commons.lang.SystemUtils;
47  import org.apache.commons.lang.text.StrLookup;
48  import org.apache.commons.logging.Log;
49  import org.apache.commons.logging.LogFactory;
50  import org.apache.commons.logging.impl.Log4JLogger;
51  import org.apache.log4j.Level;
52  import org.apache.log4j.Logger;
53  import org.apache.log4j.SimpleLayout;
54  import org.apache.log4j.WriterAppender;
55  import org.junit.Before;
56  import org.junit.Test;
57  
58  /**
59   * Test class for DefaultConfigurationBuilder.
60   *
61   * @author <a
62   * href="http://commons.apache.org/configuration/team-list.html">Commons
63   * Configuration team</a>
64   * @version $Id: TestDefaultConfigurationBuilder.java 1301997 2012-03-17 20:32:02Z sebb $
65   */
66  public class TestDefaultConfigurationBuilder
67  {
68      /** Test configuration definition file. */
69      private static final File TEST_FILE = ConfigurationAssert
70              .getTestFile("testDigesterConfiguration.xml");
71  
72      private static final File ADDITIONAL_FILE = ConfigurationAssert
73              .getTestFile("testDigesterConfiguration2.xml");
74  
75      private static final File OPTIONAL_FILE = ConfigurationAssert
76              .getTestFile("testDigesterOptionalConfiguration.xml");
77  
78      private static final File OPTIONALEX_FILE = ConfigurationAssert
79              .getTestFile("testDigesterOptionalConfigurationEx.xml");
80  
81      private static final File MULTI_FILE = ConfigurationAssert
82              .getTestFile("testDigesterConfiguration3.xml");
83  
84      private static final File INIT_FILE = ConfigurationAssert
85              .getTestFile("testComplexInitialization.xml");
86  
87      private static final File CLASS_FILE = ConfigurationAssert
88              .getTestFile("testExtendedClass.xml");
89  
90      private static final File PROVIDER_FILE = ConfigurationAssert
91              .getTestFile("testConfigurationProvider.xml");
92  
93      private static final File EXTENDED_PROVIDER_FILE = ConfigurationAssert
94              .getTestFile("testExtendedXMLConfigurationProvider.xml");
95  
96      private static final File GLOBAL_LOOKUP_FILE = ConfigurationAssert
97              .getTestFile("testGlobalLookup.xml");
98  
99      private static final File SYSTEM_PROPS_FILE = ConfigurationAssert
100             .getTestFile("testSystemProperties.xml");
101 
102     private static final File VALIDATION_FILE = ConfigurationAssert
103             .getTestFile("testValidation.xml");
104 
105     private static final File VALIDATION3_FILE = ConfigurationAssert
106             .getTestFile("testValidation3.xml");
107 
108     private static final File MULTI_TENENT_FILE = ConfigurationAssert
109             .getTestFile("testMultiTenentConfigurationBuilder.xml");
110 
111     private static final File EXPRESSION_FILE = ConfigurationAssert
112             .getTestFile("testExpression.xml");
113 
114     /** Constant for the name of an optional configuration.*/
115     private static final String OPTIONAL_NAME = "optionalConfig";
116 
117     /** Stores the object to be tested. */
118     DefaultConfigurationBuilder factory;
119 
120     @Before
121     public void setUp() throws Exception
122     {
123         System
124                 .setProperty("java.naming.factory.initial",
125                         "org.apache.commons.configuration.MockInitialContextFactory");
126         System.setProperty("test_file_xml", "test.xml");
127         System.setProperty("test_file_combine", "testcombine1.xml");
128         factory = new DefaultConfigurationBuilder();
129         factory.clearErrorListeners();  // avoid exception messages
130     }
131 
132     /**
133      * Tests the isReservedNode() method of ConfigurationDeclaration.
134      */
135     @Test
136     public void testConfigurationDeclarationIsReserved()
137     {
138         DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
139                 factory, factory);
140         DefaultConfigurationNode parent = new DefaultConfigurationNode();
141         DefaultConfigurationNode nd = new DefaultConfigurationNode("at");
142         parent.addAttribute(nd);
143         assertTrue("Attribute at not recognized", decl.isReservedNode(nd));
144         nd = new DefaultConfigurationNode("optional");
145         parent.addAttribute(nd);
146         assertTrue("Attribute optional not recognized", decl.isReservedNode(nd));
147         nd = new DefaultConfigurationNode("config-class");
148         parent.addAttribute(nd);
149         assertTrue("Inherited attribute not recognized", decl
150                 .isReservedNode(nd));
151         nd = new DefaultConfigurationNode("different");
152         parent.addAttribute(nd);
153         assertFalse("Wrong reserved attribute", decl.isReservedNode(nd));
154         nd = new DefaultConfigurationNode("at");
155         parent.addChild(nd);
156         assertFalse("Node type not evaluated", decl.isReservedNode(nd));
157     }
158 
159     /**
160      * Tests if the at attribute is correctly detected as reserved attribute.
161      */
162     @Test
163     public void testConfigurationDeclarationIsReservedAt()
164     {
165         checkOldReservedAttribute("at");
166     }
167 
168     /**
169      * Tests if the optional attribute is correctly detected as reserved
170      * attribute.
171      */
172     @Test
173     public void testConfigurationDeclarationIsReservedOptional()
174     {
175         checkOldReservedAttribute("optional");
176     }
177 
178     /**
179      * Tests if special reserved attributes are recognized by the
180      * isReservedNode() method. For compatibility reasons the attributes "at"
181      * and "optional" are also treated as reserved attributes, but only if there
182      * are no corresponding attributes with the "config-" prefix.
183      *
184      * @param name the attribute name
185      */
186     private void checkOldReservedAttribute(String name)
187     {
188         DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
189                 factory, factory);
190         DefaultConfigurationNode parent = new DefaultConfigurationNode();
191         DefaultConfigurationNode nd = new DefaultConfigurationNode("config-"
192                 + name);
193         parent.addAttribute(nd);
194         assertTrue("config-" + name + " attribute not recognized", decl
195                 .isReservedNode(nd));
196         DefaultConfigurationNode nd2 = new DefaultConfigurationNode(name);
197         parent.addAttribute(nd2);
198         assertFalse(name + " is reserved though config- exists", decl
199                 .isReservedNode(nd2));
200         assertTrue("config- attribute not recognized when " + name + " exists",
201                 decl.isReservedNode(nd));
202     }
203 
204     /**
205      * Tests access to certain reserved attributes of a
206      * ConfigurationDeclaration.
207      */
208     public void testConfigurationDeclarationGetAttributes()
209     {
210         factory.addProperty("xml.fileName", "test.xml");
211         DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
212                 factory, factory.configurationAt("xml"));
213         assertNull("Found an at attribute", decl.getAt());
214         assertFalse("Found an optional attribute", decl.isOptional());
215         factory.addProperty("xml[@config-at]", "test1");
216         assertEquals("Wrong value of at attribute", "test1", decl.getAt());
217         factory.addProperty("xml[@at]", "test2");
218         assertEquals("Wrong value of config-at attribute", "test1", decl.getAt());
219         factory.clearProperty("xml[@config-at]");
220         assertEquals("Old at attribute not detected", "test2", decl.getAt());
221         factory.addProperty("xml[@config-optional]", "true");
222         assertTrue("Wrong value of optional attribute", decl.isOptional());
223         factory.addProperty("xml[@optional]", "false");
224         assertTrue("Wrong value of config-optional attribute", decl.isOptional());
225         factory.clearProperty("xml[@config-optional]");
226         factory.setProperty("xml[@optional]", Boolean.TRUE);
227         assertTrue("Old optional attribute not detected", decl.isOptional());
228     }
229 
230     /**
231      * Tests whether an invalid value of an optional attribute is detected.
232      */
233     @Test(expected = ConfigurationRuntimeException.class)
234     public void testConfigurationDeclarationOptionalAttributeInvalid()
235     {
236         factory.addProperty("xml.fileName", "test.xml");
237         DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
238                 factory, factory.configurationAt("xml"));
239         factory.setProperty("xml[@optional]", "invalid value");
240         decl.isOptional();
241     }
242 
243     /**
244      * Tests adding a new configuration provider.
245      */
246     @Test
247     public void testAddConfigurationProvider()
248     {
249         DefaultConfigurationBuilder.ConfigurationProvider provider = new DefaultConfigurationBuilder.ConfigurationProvider();
250         assertNull("Provider already registered", factory
251                 .providerForTag("test"));
252         factory.addConfigurationProvider("test", provider);
253         assertSame("Provider not registered", provider, factory
254                 .providerForTag("test"));
255     }
256 
257     /**
258      * Tries to register a null configuration provider. This should cause an
259      * exception.
260      */
261     @Test(expected = IllegalArgumentException.class)
262     public void testAddConfigurationProviderNull()
263     {
264         factory.addConfigurationProvider("test", null);
265     }
266 
267     /**
268      * Tries to register a configuration provider for a null tag. This should
269      * cause an exception to be thrown.
270      */
271     @Test(expected = IllegalArgumentException.class)
272     public void testAddConfigurationProviderNullTag()
273     {
274         factory.addConfigurationProvider(null,
275                 new DefaultConfigurationBuilder.ConfigurationProvider());
276     }
277 
278     /**
279      * Tests removing configuration providers.
280      */
281     @Test
282     public void testRemoveConfigurationProvider()
283     {
284         assertNull("Removing unknown provider", factory
285                 .removeConfigurationProvider("test"));
286         assertNull("Removing provider for null tag", factory
287                 .removeConfigurationProvider(null));
288         DefaultConfigurationBuilder.ConfigurationProvider provider = new DefaultConfigurationBuilder.ConfigurationProvider();
289         factory.addConfigurationProvider("test", provider);
290         assertSame("Failed to remove provider", provider, factory
291                 .removeConfigurationProvider("test"));
292         assertNull("Provider still registered", factory.providerForTag("test"));
293     }
294 
295     /**
296      * Tests creating a configuration object from a configuration declaration.
297      */
298     @Test
299     public void testConfigurationBeanFactoryCreateBean()
300     {
301         factory.addConfigurationProvider("test",
302                 new DefaultConfigurationBuilder.ConfigurationProvider(
303                         PropertiesConfiguration.class));
304         factory.addProperty("test[@throwExceptionOnMissing]", "true");
305         DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
306                 factory, factory.configurationAt("test"));
307         PropertiesConfiguration conf = (PropertiesConfiguration) BeanHelper
308                 .createBean(decl);
309         assertTrue("Property was not initialized", conf
310                 .isThrowExceptionOnMissing());
311     }
312 
313     /**
314      * Tests creating a configuration object from an unknown tag. This should
315      * cause an exception.
316      */
317     @Test(expected = ConfigurationRuntimeException.class)
318     public void testConfigurationBeanFactoryCreateUnknownTag()
319     {
320         factory.addProperty("test[@throwExceptionOnMissing]", "true");
321         DefaultConfigurationBuilder.ConfigurationDeclaration decl = new DefaultConfigurationBuilder.ConfigurationDeclaration(
322                 factory, factory.configurationAt("test"));
323         BeanHelper.createBean(decl);
324     }
325 
326     /**
327      * Tests loading a simple configuration definition file.
328      */
329     @Test
330     public void testLoadConfiguration() throws ConfigurationException
331     {
332         factory.setFile(TEST_FILE);
333         checkConfiguration();
334     }
335 
336     /**
337      * Tests the file constructor.
338      */
339     @Test
340     public void testLoadConfigurationFromFile() throws ConfigurationException
341     {
342         factory = new DefaultConfigurationBuilder(TEST_FILE);
343         checkConfiguration();
344     }
345 
346     /**
347      * Tests the file name constructor.
348      */
349     @Test
350     public void testLoadConfigurationFromFileName()
351             throws ConfigurationException
352     {
353         factory = new DefaultConfigurationBuilder(TEST_FILE.getAbsolutePath());
354         checkConfiguration();
355     }
356 
357     /**
358      * Tests the URL constructor.
359      */
360     @Test
361     public void testLoadConfigurationFromURL() throws Exception
362     {
363         factory = new DefaultConfigurationBuilder(TEST_FILE.toURI().toURL());
364         checkConfiguration();
365     }
366 
367     /**
368      * Tests if the configuration was correctly created by the factory.
369      */
370     private void checkConfiguration() throws ConfigurationException
371     {
372         CombinedConfiguration compositeConfiguration = (CombinedConfiguration) factory
373                 .getConfiguration();
374 
375         assertEquals("Number of configurations", 3, compositeConfiguration
376                 .getNumberOfConfigurations());
377         assertEquals(PropertiesConfiguration.class, compositeConfiguration
378                 .getConfiguration(0).getClass());
379         assertEquals(XMLPropertiesConfiguration.class, compositeConfiguration
380                 .getConfiguration(1).getClass());
381         assertEquals(XMLConfiguration.class, compositeConfiguration
382                 .getConfiguration(2).getClass());
383 
384         // check the first configuration
385         PropertiesConfiguration pc = (PropertiesConfiguration) compositeConfiguration
386                 .getConfiguration(0);
387         assertNotNull("Make sure we have a fileName: " + pc.getFileName(), pc
388                 .getFileName());
389 
390         // check some properties
391         checkProperties(compositeConfiguration);
392     }
393 
394     /**
395      * Checks if the passed in configuration contains the expected properties.
396      *
397      * @param compositeConfiguration the configuration to check
398      */
399     private void checkProperties(Configuration compositeConfiguration)
400     {
401         assertTrue("Make sure we have loaded our key", compositeConfiguration
402                 .getBoolean("test.boolean"));
403         assertEquals("I'm complex!", compositeConfiguration
404                 .getProperty("element2.subelement.subsubelement"));
405         assertEquals("property in the XMLPropertiesConfiguration", "value1",
406                 compositeConfiguration.getProperty("key1"));
407     }
408 
409     /**
410      * Tests loading a configuration definition file with an additional section.
411      */
412     @Test
413     public void testLoadAdditional() throws ConfigurationException
414     {
415         factory.setFile(ADDITIONAL_FILE);
416         CombinedConfiguration compositeConfiguration = (CombinedConfiguration) factory
417                 .getConfiguration();
418         assertEquals("Verify how many configs", 2, compositeConfiguration
419                 .getNumberOfConfigurations());
420 
421         // Test if union was constructed correctly
422         Object prop = compositeConfiguration.getProperty("tables.table.name");
423         assertTrue(prop instanceof Collection);
424         assertEquals(3, ((Collection<?>) prop).size());
425         assertEquals("users", compositeConfiguration
426                 .getProperty("tables.table(0).name"));
427         assertEquals("documents", compositeConfiguration
428                 .getProperty("tables.table(1).name"));
429         assertEquals("tasks", compositeConfiguration
430                 .getProperty("tables.table(2).name"));
431 
432         prop = compositeConfiguration
433                 .getProperty("tables.table.fields.field.name");
434         assertTrue(prop instanceof Collection);
435         assertEquals(17, ((Collection<?>) prop).size());
436 
437         assertEquals("smtp.mydomain.org", compositeConfiguration
438                 .getString("mail.host.smtp"));
439         assertEquals("pop3.mydomain.org", compositeConfiguration
440                 .getString("mail.host.pop"));
441 
442         // This was overriden
443         assertEquals("masterOfPost", compositeConfiguration
444                 .getString("mail.account.user"));
445         assertEquals("topsecret", compositeConfiguration
446                 .getString("mail.account.psswd"));
447 
448         // This was overriden, too, but not in additional section
449         assertEquals("enhanced factory", compositeConfiguration
450                 .getString("test.configuration"));
451     }
452 
453     /**
454      * Tests whether a default log error listener is registered at the builder
455      * instance.
456      */
457     @Test
458     public void testLogErrorListener()
459     {
460         assertEquals("No default error listener registered", 1,
461                 new DefaultConfigurationBuilder().getErrorListeners().size());
462     }
463 
464     /**
465      * Tests loading a definition file that contains optional configurations.
466      */
467     @Test
468     public void testLoadOptional() throws Exception
469     {
470         factory.setURL(OPTIONAL_FILE.toURI().toURL());
471         Configuration config = factory.getConfiguration();
472         assertTrue(config.getBoolean("test.boolean"));
473         assertEquals("value", config.getProperty("element"));
474     }
475 
476     /**
477      * Tests whether loading a failing optional configuration causes an error
478      * event.
479      */
480     @Test
481     public void testLoadOptionalErrorEvent() throws Exception
482     {
483         factory.clearErrorListeners();
484         ConfigurationErrorListenerImpl listener = new ConfigurationErrorListenerImpl();
485         factory.addErrorListener(listener);
486         prepareOptionalTest("configuration", false);
487         listener.verify(DefaultConfigurationBuilder.EVENT_ERR_LOAD_OPTIONAL,
488                 OPTIONAL_NAME, null);
489     }
490 
491     /**
492      * Tests loading a definition file with optional and non optional
493      * configuration sources. One non optional does not exist, so this should
494      * cause an exception.
495      */
496     @Test(expected = ConfigurationException.class)
497     public void testLoadOptionalWithException() throws ConfigurationException
498     {
499         factory.setFile(OPTIONALEX_FILE);
500         factory.getConfiguration();
501     }
502 
503     /**
504      * Tries to load a configuration file with an optional, non file-based
505      * configuration. The optional attribute should work for other configuration
506      * classes, too.
507      */
508     @Test
509     public void testLoadOptionalNonFileBased() throws ConfigurationException
510     {
511         CombinedConfiguration config = prepareOptionalTest("configuration", false);
512         assertTrue("Configuration not empty", config.isEmpty());
513         assertEquals("Wrong number of configurations", 0, config
514                 .getNumberOfConfigurations());
515     }
516 
517     /**
518      * Tests an optional, non existing configuration with the forceCreate
519      * attribute. This configuration should be added to the resulting
520      * configuration.
521      */
522     @Test
523     public void testLoadOptionalForceCreate() throws ConfigurationException
524     {
525         factory.setBasePath(TEST_FILE.getParent());
526         CombinedConfiguration config = prepareOptionalTest("xml", true);
527         assertEquals("Wrong number of configurations", 1, config
528                 .getNumberOfConfigurations());
529         FileConfiguration fc = (FileConfiguration) config
530                 .getConfiguration(OPTIONAL_NAME);
531         assertNotNull("Optional config not found", fc);
532         assertEquals("File name was not set", "nonExisting.xml", fc
533                 .getFileName());
534         assertNotNull("Base path was not set", fc.getBasePath());
535     }
536 
537     /**
538      * Tests loading an embedded optional configuration builder with the force
539      * create attribute.
540      */
541     @Test
542     public void testLoadOptionalBuilderForceCreate()
543             throws ConfigurationException
544     {
545         CombinedConfiguration config = prepareOptionalTest("configuration",
546                 true);
547         assertEquals("Wrong number of configurations", 1, config
548                 .getNumberOfConfigurations());
549         assertTrue(
550                 "Wrong optional configuration type",
551                 config.getConfiguration(OPTIONAL_NAME) instanceof CombinedConfiguration);
552     }
553 
554     /**
555      * Tests loading an optional configuration with the force create attribute
556      * set. The provider will always throw an exception. In this case the
557      * configuration will not be added to the resulting combined configuration.
558      */
559     @Test
560     public void testLoadOptionalForceCreateWithException()
561             throws ConfigurationException
562     {
563         factory.addConfigurationProvider("test",
564                 new DefaultConfigurationBuilder.ConfigurationBuilderProvider()
565                 {
566                     // Throw an exception here, too
567                     @Override
568                     public AbstractConfiguration getEmptyConfiguration(
569                             DefaultConfigurationBuilder.ConfigurationDeclaration decl) throws Exception
570                     {
571                         throw new Exception("Unable to create configuration!");
572                     }
573                 });
574         CombinedConfiguration config = prepareOptionalTest("test", true);
575         assertEquals("Optional configuration could be created", 0, config
576                 .getNumberOfConfigurations());
577     }
578 
579     /**
580      * Prepares a test for loading a configuration definition file with an
581      * optional configuration declaration.
582      *
583      * @param tag the tag name with the optional configuration
584      * @param force the forceCreate attribute
585      * @return the combined configuration obtained from the builder
586      * @throws ConfigurationException if an error occurs
587      */
588     private CombinedConfiguration prepareOptionalTest(String tag, boolean force)
589             throws ConfigurationException
590     {
591         String prefix = "override." + tag;
592         factory.addProperty(prefix + "[@fileName]", "nonExisting.xml");
593         factory.addProperty(prefix + "[@config-optional]", Boolean.TRUE);
594         factory.addProperty(prefix + "[@config-name]", OPTIONAL_NAME);
595         if (force)
596         {
597             factory.addProperty(prefix + "[@config-forceCreate]", Boolean.TRUE);
598         }
599         return factory.getConfiguration(false);
600     }
601 
602     /**
603      * Tests whether the error log message caused by an optional configuration
604      * can be suppressed if a child builder is involved.
605      */
606     @Test
607     public void testLoadOptionalChildBuilderSuppressErrorLog()
608             throws ConfigurationException
609     {
610         factory.addProperty("override.configuration[@fileName]",
611                 OPTIONAL_FILE.getAbsolutePath());
612         // a special invocation handler which checks that the warn() method of
613         // a logger is not called
614         InvocationHandler handler = new InvocationHandler()
615         {
616             public Object invoke(Object proxy, Method method, Object[] args)
617                     throws Throwable
618             {
619                 String methodName = method.getName();
620                 if (methodName.startsWith("is"))
621                 {
622                     return Boolean.TRUE;
623                 }
624                 if ("warn".equals(methodName))
625                 {
626                     fail("Unexpected log output!");
627                 }
628                 return null;
629             }
630         };
631         factory.setLogger((Log) Proxy.newProxyInstance(getClass()
632                 .getClassLoader(), new Class[] {
633             Log.class
634         }, handler));
635         factory.getConfiguration(false);
636     }
637 
638     /**
639      * Tests loading a definition file with multiple different sources.
640      */
641     @Test
642     public void testLoadDifferentSources() throws ConfigurationException
643     {
644         factory.setFile(MULTI_FILE);
645         Configuration config = factory.getConfiguration();
646         assertFalse(config.isEmpty());
647         assertTrue(config instanceof CombinedConfiguration);
648         CombinedConfiguration cc = (CombinedConfiguration) config;
649         assertEquals("Wrong number of configurations", 1, cc
650                 .getNumberOfConfigurations());
651 
652         assertNotNull(config
653                 .getProperty("tables.table(0).fields.field(2).name"));
654         assertNotNull(config.getProperty("element2.subelement.subsubelement"));
655         assertEquals("value", config.getProperty("element3"));
656         assertEquals("foo", config.getProperty("element3[@name]"));
657         assertNotNull(config.getProperty("mail.account.user"));
658 
659         // test JNDIConfiguration
660         assertNotNull(config.getProperty("test.onlyinjndi"));
661         assertTrue(config.getBoolean("test.onlyinjndi"));
662 
663         Configuration subset = config.subset("test");
664         assertNotNull(subset.getProperty("onlyinjndi"));
665         assertTrue(subset.getBoolean("onlyinjndi"));
666 
667         // test SystemConfiguration
668         assertNotNull(config.getProperty("java.version"));
669         assertEquals(System.getProperty("java.version"), config
670                 .getString("java.version"));
671 
672         // test INIConfiguration
673         assertEquals("Property from ini file not found", "yes",
674                 config.getString("testini.loaded"));
675 
676         // test environment configuration
677         EnvironmentConfiguration envConf = new EnvironmentConfiguration();
678         for (Iterator<String> it = envConf.getKeys(); it.hasNext();)
679         {
680             String key = it.next();
681             String combinedKey = "env." + key;
682             assertEquals("Wrong value for env property " + key,
683                     envConf.getString(key), config.getString(combinedKey));
684         }
685     }
686 
687     /**
688      * Tests if the base path is correctly evaluated.
689      */
690     @Test
691     public void testSetConfigurationBasePath() throws ConfigurationException
692     {
693         factory.addProperty("properties[@fileName]", "test.properties");
694         File deepDir = new File(ConfigurationAssert.TEST_DIR, "config/deep");
695         factory.setConfigurationBasePath(deepDir.getAbsolutePath());
696 
697         Configuration config = factory.getConfiguration(false);
698         assertEquals("Wrong property value", "somevalue", config
699                 .getString("somekey"));
700     }
701 
702     /**
703      * Tests reading a configuration definition file that contains complex
704      * initialization of properties of the declared configuration sources.
705      */
706     @Test
707     public void testComplexInitialization() throws ConfigurationException
708     {
709         factory.setFile(INIT_FILE);
710         CombinedConfiguration cc = (CombinedConfiguration) factory
711                 .getConfiguration();
712 
713         assertEquals("System property not found", "test.xml",
714                 cc.getString("test_file_xml"));
715         PropertiesConfiguration c1 = (PropertiesConfiguration) cc
716                 .getConfiguration(1);
717         assertTrue(
718                 "Reloading strategy was not set",
719                 c1.getReloadingStrategy() instanceof FileChangedReloadingStrategy);
720         assertEquals("Refresh delay was not set", 10000,
721                 ((FileChangedReloadingStrategy) c1.getReloadingStrategy())
722                         .getRefreshDelay());
723 
724         Configuration xmlConf = cc.getConfiguration("xml");
725         assertEquals("Property not found", "I'm complex!", xmlConf
726                 .getString("element2/subelement/subsubelement"));
727         assertEquals("List index not found", "two", xmlConf
728                 .getString("list[0]/item[1]"));
729         assertEquals("Property in combiner file not found", "yellow", cc
730                 .getString("/gui/selcolor"));
731 
732         assertTrue("Delimiter flag was not set", cc
733                 .isDelimiterParsingDisabled());
734         assertTrue("Expression engine was not set",
735                 cc.getExpressionEngine() instanceof XPathExpressionEngine);
736     }
737 
738     /**
739      * Tests if the returned combined configuration has the expected structure.
740      */
741     @Test
742     public void testCombinedConfigurationStructure() throws ConfigurationException
743     {
744         factory.setFile(INIT_FILE);
745         CombinedConfiguration cc = (CombinedConfiguration) factory
746                 .getConfiguration();
747         assertNotNull("Properties configuration not found", cc
748                 .getConfiguration("properties"));
749         assertNotNull("XML configuration not found", cc.getConfiguration("xml"));
750         assertEquals("Wrong number of contained configs", 4, cc
751                 .getNumberOfConfigurations());
752 
753         CombinedConfiguration cc2 = (CombinedConfiguration) cc
754                 .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME);
755         assertNotNull("No additional configuration found", cc2);
756         Set<String> names = cc2.getConfigurationNames();
757         assertEquals("Wrong number of contained additional configs", 2, names
758                 .size());
759         assertTrue("Config 1 not contained", names.contains("combiner1"));
760         assertTrue("Config 2 not contained", names.contains("combiner2"));
761     }
762 
763     /**
764      * Helper method for testing the attributes of a combined configuration
765      * created by the builder.
766      *
767      * @param cc the configuration to be checked
768      */
769     private void checkCombinedConfigAttrs(CombinedConfiguration cc)
770     {
771         assertTrue("Wrong delimiter parsing flag",
772                 cc.isDelimiterParsingDisabled());
773         assertTrue("Wrong reload check", cc.isForceReloadCheck());
774         assertTrue("Wrong ignore reload ex flag", cc.isIgnoreReloadExceptions());
775     }
776 
777     /**
778      * Tests whether attributes are correctly set on the combined configurations
779      * for the override and additional sections.
780      */
781     @Test
782     public void testCombinedConfigurationAttributes() throws ConfigurationException
783     {
784         factory.setFile(INIT_FILE);
785         CombinedConfiguration cc = (CombinedConfiguration) factory
786                 .getConfiguration();
787         checkCombinedConfigAttrs(cc);
788         CombinedConfiguration cc2 = (CombinedConfiguration) cc
789                 .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME);
790         checkCombinedConfigAttrs(cc2);
791     }
792 
793     /**
794      * Tests the structure of the returned combined configuration if there is no
795      * additional section.
796      */
797     @Test
798     public void testCombinedConfigurationNoAdditional()
799             throws ConfigurationException
800     {
801         factory.setFile(TEST_FILE);
802         CombinedConfiguration cc = factory.getConfiguration(true);
803         assertNull("Additional configuration was found", cc
804                 .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME));
805     }
806 
807     /**
808      * Tests whether the list node definition was correctly processed.
809      */
810     @Test
811     public void testCombinedConfigurationListNodes()
812             throws ConfigurationException
813     {
814         factory.setFile(INIT_FILE);
815         CombinedConfiguration cc = factory.getConfiguration(true);
816         Set<String> listNodes = cc.getNodeCombiner().getListNodes();
817         assertEquals("Wrong number of list nodes", 2, listNodes.size());
818         assertTrue("table node not a list node", listNodes.contains("table"));
819         assertTrue("list node not a list node", listNodes.contains("list"));
820 
821         CombinedConfiguration cca = (CombinedConfiguration) cc
822                 .getConfiguration(DefaultConfigurationBuilder.ADDITIONAL_NAME);
823         listNodes = cca.getNodeCombiner().getListNodes();
824         assertTrue("Found list nodes for additional combiner", listNodes
825                 .isEmpty());
826     }
827 
828     /**
829      * Tests whether a configuration builder can itself be declared in a
830      * configuration definition file.
831      */
832     @Test
833     public void testConfigurationBuilderProvider()
834             throws ConfigurationException
835     {
836         factory.addProperty("override.configuration[@fileName]", TEST_FILE
837                 .getAbsolutePath());
838         CombinedConfiguration cc = factory.getConfiguration(false);
839         assertEquals("Wrong number of configurations", 1, cc
840                 .getNumberOfConfigurations());
841         checkProperties(cc);
842     }
843 
844     /**
845      * Tests whether settings of the builder are propagated to child builders.
846      */
847     @Test
848     public void testConfigurationBuilderProviderInheritProperties()
849             throws Exception
850     {
851         factory.addProperty("override.configuration[@fileName]",
852                 TEST_FILE.getAbsolutePath());
853         factory.setBasePath("conf");
854         factory.setAttributeSplittingDisabled(true);
855         factory.setDelimiterParsingDisabled(true);
856         factory.setListDelimiter('/');
857         factory.setThrowExceptionOnMissing(true);
858         Log log = LogFactory.getLog(getClass());
859         factory.setLogger(log);
860         factory.clearErrorListeners();
861         factory.clearConfigurationListeners();
862         ConfigurationListenerTestImpl l =
863                 new ConfigurationListenerTestImpl(factory);
864         factory.addConfigurationListener(l);
865         DefaultConfigurationBuilder.ConfigurationDeclaration decl =
866                 new DefaultConfigurationBuilder.ConfigurationDeclaration(
867                         factory,
868                         factory.configurationAt("override.configuration"));
869         DefaultConfigurationBuilder.ConfigurationBuilderProvider provider =
870                 new DefaultConfigurationBuilder.ConfigurationBuilderProvider();
871         DefaultConfigurationBuilder child =
872                 (DefaultConfigurationBuilder) provider.createBean(
873                         provider.fetchConfigurationClass(), decl, null);
874         assertEquals("Wrong base path", factory.getBasePath(),
875                 child.getBasePath());
876         assertEquals("Wrong attribute splitting flag",
877                 factory.isAttributeSplittingDisabled(),
878                 child.isAttributeSplittingDisabled());
879         assertEquals("Wrong delimiter parsing flag",
880                 factory.isDelimiterParsingDisabled(),
881                 child.isDelimiterParsingDisabled());
882         assertEquals("Wrong list delimiter", factory.getListDelimiter(),
883                 child.getListDelimiter());
884         assertEquals("Wrong exception flag",
885                 factory.isThrowExceptionOnMissing(),
886                 child.isThrowExceptionOnMissing());
887         assertSame("Wrong logger", log, child.getLogger());
888         assertTrue("Got error listeners", child.getErrorListeners().isEmpty());
889         assertEquals("Wrong number of listeners", 1, child
890                 .getConfigurationListeners().size());
891         assertEquals("Wrong listener", l, child.getConfigurationListeners()
892                 .iterator().next());
893     }
894 
895     /**
896      * Tests whether properties of the parent configuration can be overridden.
897      */
898     @Test
899     public void testConfigurationBuilderProviderOverrideProperties()
900             throws Exception
901     {
902         factory.addProperty("override.configuration[@fileName]",
903                 TEST_FILE.getAbsolutePath());
904         factory.addProperty("override.configuration[@basePath]", "base");
905         factory.addProperty("override.configuration[@throwExceptionOnMissing]",
906                 "false");
907         factory.setBasePath("conf");
908         factory.setThrowExceptionOnMissing(true);
909         DefaultConfigurationBuilder.ConfigurationDeclaration decl =
910                 new DefaultConfigurationBuilder.ConfigurationDeclaration(
911                         factory,
912                         factory.configurationAt("override.configuration"));
913         DefaultConfigurationBuilder.ConfigurationBuilderProvider provider =
914                 new DefaultConfigurationBuilder.ConfigurationBuilderProvider();
915         DefaultConfigurationBuilder child =
916                 (DefaultConfigurationBuilder) provider.createBean(
917                         provider.fetchConfigurationClass(), decl, null);
918         assertEquals("Wrong base path", "base", child.getBasePath());
919         assertFalse("Wrong exception flag", child.isThrowExceptionOnMissing());
920     }
921 
922     /**
923      * Tests whether XML settings can be inherited.
924      */
925     @Test
926     public void testLoadXMLWithSettings() throws Exception
927     {
928         File confDir = new File("conf");
929         File targetDir = new File("target");
930         File testXMLValidationSource = new File(confDir,
931                 "testValidateInvalid.xml");
932         File testSavedXML = new File(targetDir, "testSave.xml");
933         File testSavedFactory = new File(targetDir, "testSaveFactory.xml");
934         URL dtdFile = getClass().getResource("/properties.dtd");
935         final String publicId = "http://commons.apache.org/test.dtd";
936 
937         XMLConfiguration config = new XMLConfiguration("testDtd.xml");
938         config.setPublicID(publicId);
939         config.save(testSavedXML);
940         factory.addProperty("xml[@fileName]", testSavedXML.getAbsolutePath());
941         factory.addProperty("xml(0)[@validating]", "true");
942         factory.addProperty("xml(-1)[@fileName]", testXMLValidationSource
943                 .getAbsolutePath());
944         factory.addProperty("xml(1)[@config-optional]", "true");
945         factory.addProperty("xml(1)[@validating]", "true");
946         factory.save(testSavedFactory);
947 
948         factory = new DefaultConfigurationBuilder();
949         factory.setFile(testSavedFactory);
950         factory.registerEntityId(publicId, dtdFile);
951         factory.clearErrorListeners();
952         Configuration c = factory.getConfiguration();
953         assertEquals("Wrong property value", "value1", c.getString("entry(0)"));
954         assertFalse("Invalid XML source was loaded", c
955                 .containsKey("table.name"));
956 
957         testSavedXML.delete();
958         testSavedFactory.delete();
959     }
960 
961     /**
962      * Tests loading a configuration definition file that defines a custom
963      * result class.
964      */
965     @Test
966     public void testExtendedClass() throws ConfigurationException
967     {
968         factory.setFile(CLASS_FILE);
969         CombinedConfiguration cc = factory.getConfiguration(true);
970         assertEquals("Extended", cc.getProperty("test"));
971         assertTrue("Wrong result class: " + cc.getClass(),
972                 cc instanceof ExtendedCombinedConfiguration);
973     }
974 
975     /**
976      * Tests loading a configuration definition file that defines new providers.
977      */
978     @Test
979     public void testConfigurationProvider() throws ConfigurationException
980     {
981         factory.setFile(PROVIDER_FILE);
982         factory.getConfiguration(true);
983         DefaultConfigurationBuilder.ConfigurationProvider provider = factory
984                 .providerForTag("test");
985         assertNotNull("Provider 'test' not registered", provider);
986     }
987 
988     /**
989      * Tests loading a configuration definition file that defines new providers.
990      */
991     @Test
992     public void testExtendedXMLConfigurationProvider() throws ConfigurationException
993     {
994         factory.setFile(EXTENDED_PROVIDER_FILE);
995         CombinedConfiguration cc = factory.getConfiguration(true);
996         DefaultConfigurationBuilder.ConfigurationProvider provider = factory
997                 .providerForTag("test");
998         assertNotNull("Provider 'test' not registered", provider);
999         Configuration config = cc.getConfiguration("xml");
1000         assertNotNull("Test configuration not present", config);
1001         assertTrue("Configuration is not ExtendedXMLConfiguration, is " +
1002                 config.getClass().getName(), config instanceof ExtendedXMLConfiguration);
1003     }
1004 
1005     @Test
1006     public void testGlobalLookup() throws Exception
1007     {
1008         factory.setFile(GLOBAL_LOOKUP_FILE);
1009         CombinedConfiguration cc = factory.getConfiguration(true);
1010         String value = cc.getInterpolator().lookup("test:test_key");
1011         assertNotNull("The test key was not located", value);
1012         assertEquals("Incorrect value retrieved","test.value",value);
1013     }
1014 
1015     @Test
1016     public void testSystemProperties() throws Exception
1017     {
1018         factory.setFile(SYSTEM_PROPS_FILE);
1019         factory.getConfiguration(true);
1020         String value = System.getProperty("key1");
1021         assertNotNull("The test key was not located", value);
1022         assertEquals("Incorrect value retrieved","value1",value);
1023     }
1024 
1025     @Test
1026     public void testValidation() throws Exception
1027     {
1028         factory.setFile(VALIDATION_FILE);
1029         factory.getConfiguration(true);
1030         String value = System.getProperty("key1");
1031         assertNotNull("The test key was not located", value);
1032         assertEquals("Incorrect value retrieved","value1",value);
1033     }
1034 
1035     @Test
1036     public void testValidation3() throws Exception
1037     {
1038         System.getProperties().remove("Id");
1039         factory.setFile(VALIDATION3_FILE);
1040         CombinedConfiguration config = factory.getConfiguration(true);
1041         String value = config.getString("Employee/Name");
1042         assertNotNull("The test key was not located", value);
1043         assertEquals("Incorrect value retrieved","John Doe",value);
1044         System.setProperty("Id", "1001");
1045         value = config.getString("Employee/Name");
1046         assertNotNull("The test key was not located", value);
1047         assertEquals("Incorrect value retrieved","Jane Doe",value);
1048     }
1049 
1050     @Test
1051     public void testMultiTenentConfiguration() throws Exception
1052     {
1053         factory.setFile(MULTI_TENENT_FILE);
1054         System.getProperties().remove("Id");
1055 
1056         CombinedConfiguration config = factory.getConfiguration(true);
1057         assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
1058 
1059         verify("1001", config, 15);
1060         verify("1002", config, 25);
1061         verify("1003", config, 35);
1062         verify("1004", config, 50);
1063         verify("1005", config, 50);
1064     }
1065 
1066     @Test
1067     public void testMultiTenentConfiguration2() throws Exception
1068     {
1069         factory.setFile(MULTI_TENENT_FILE);
1070         System.setProperty("Id", "1004");
1071 
1072         CombinedConfiguration config = factory.getConfiguration(true);
1073         assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
1074 
1075         verify("1001", config, 15);
1076         verify("1002", config, 25);
1077         verify("1003", config, 35);
1078         verify("1004", config, 50);
1079         verify("1005", config, 50);
1080     }
1081 
1082     @Test
1083     public void testMultiTenentConfiguration3() throws Exception
1084     {
1085         factory.setFile(MULTI_TENENT_FILE);
1086         StringWriter writer = new StringWriter();
1087         WriterAppender app = new WriterAppender(new SimpleLayout(), writer);
1088         Log log = LogFactory.getLog("TestLogger");
1089         Logger logger = ((Log4JLogger)log).getLogger();
1090         logger.addAppender(app);
1091         logger.setLevel(Level.DEBUG);
1092         logger.setAdditivity(false);
1093 
1094         System.setProperty("Id", "1005");
1095 
1096         CombinedConfiguration config = factory.getConfiguration(true);
1097         assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
1098 
1099         verify("1001", config, 15);
1100         String xml = writer.getBuffer().toString();
1101         assertNotNull("No XML returned", xml);
1102         assertTrue("Incorect configuration data", xml.indexOf("<rowsPerPage>15</rowsPerPage>") >= 0);
1103         logger.removeAppender(app);
1104         logger.setLevel(Level.OFF);
1105         verify("1002", config, 25);
1106         verify("1003", config, 35);
1107         verify("1004", config, 50);
1108         verify("1005", config, 50);
1109     }
1110 
1111     @Test
1112     public void testMultiTenantConfigurationAt() throws Exception
1113     {
1114         factory.setFile(MULTI_TENENT_FILE);
1115         System.setProperty("Id", "1001");
1116         CombinedConfiguration config = factory.getConfiguration(true);
1117         HierarchicalConfiguration sub1 = config.configurationAt("Channels/Channel[@id='1']");
1118         assertEquals("My Channel", sub1.getString("Name"));
1119         assertEquals("test 1 data", sub1.getString("ChannelData"));
1120         HierarchicalConfiguration sub2 = config.configurationAt("Channels/Channel[@id='2']");
1121         assertEquals("Channel 2", sub2.getString("Name"));
1122         assertEquals("more test 2 data", sub2.getString("MoreChannelData"));
1123     }
1124 
1125     @Test
1126     public void testMerge() throws Exception
1127     {
1128         factory.setFile(MULTI_TENENT_FILE);
1129         System.setProperty("Id", "1004");
1130         Map<String, String> map = new HashMap<String, String>();
1131         map.put("default", "${colors.header4}");
1132         map.put("background", "#40404040");
1133         map.put("text", "#000000");
1134         map.put("header", "#444444");
1135 
1136         CombinedConfiguration config = factory.getConfiguration(true);
1137         assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
1138 
1139         List<HierarchicalConfiguration> list = config.configurationsAt("colors/*");
1140         Iterator<HierarchicalConfiguration> iter = list.iterator();
1141         while (iter.hasNext())
1142         {
1143             SubnodeConfiguration sub = (SubnodeConfiguration)iter.next();
1144             ConfigurationNode node = sub.getRootNode();
1145             String value = (node.getValue() == null) ? "null" : node.getValue().toString();
1146             if (map.containsKey(node.getName()))
1147             {
1148                 assertEquals(map.get(node.getName()), value);
1149             }
1150         }
1151 
1152     }
1153 
1154     @Test
1155     public void testDelimiterParsingDisabled() throws Exception
1156     {
1157         factory.setFile(MULTI_TENENT_FILE);
1158         System.setProperty("Id", "1004");
1159 
1160         CombinedConfiguration config = factory.getConfiguration(true);
1161         assertTrue("Incorrect configuration", config instanceof DynamicCombinedConfiguration);
1162 
1163         assertEquals("a,b,c", config.getString("split/list3/@values"));
1164         assertEquals(0, config.getMaxIndex("split/list3/@values"));
1165         assertEquals("a\\,b\\,c", config.getString("split/list4/@values"));
1166         assertEquals("a,b,c", config.getString("split/list1"));
1167         assertEquals(0, config.getMaxIndex("split/list1"));
1168         assertEquals("a\\,b\\,c", config.getString("split/list2"));
1169     }
1170 
1171     @Test
1172     public void testExpression() throws Exception
1173     {
1174         if (SystemUtils.isJavaVersionAtLeast(150))
1175         {
1176             factory.setFile(EXPRESSION_FILE);
1177             factory.setAttributeSplittingDisabled(true);
1178             System.getProperties().remove("Id");
1179             org.slf4j.MDC.clear();
1180 
1181             CombinedConfiguration config = factory.getConfiguration(true);
1182             assertTrue("Incorrect configuration",
1183                     config instanceof DynamicCombinedConfiguration);
1184 
1185             verify("1001", config, 15);
1186         }
1187     }
1188 
1189     /**
1190      * Tests whether variable substitution works across multiple child
1191      * configurations. This test is related to CONFIGURATION-481.
1192      */
1193     @Test
1194     public void testInterpolationOverMultipleSources()
1195             throws ConfigurationException
1196     {
1197         File testFile =
1198                 ConfigurationAssert.getTestFile("testInterpolationBuilder.xml");
1199         factory.setFile(testFile);
1200         CombinedConfiguration combConfig = factory.getConfiguration(true);
1201         assertEquals("Wrong value", "abc-product",
1202                 combConfig.getString("products.product.desc"));
1203         XMLConfiguration xmlConfig =
1204                 (XMLConfiguration) combConfig.getConfiguration("test");
1205         assertEquals("Wrong value from XML config", "abc-product",
1206                 xmlConfig.getString("products/product/desc"));
1207         SubnodeConfiguration subConfig =
1208                 xmlConfig
1209                         .configurationAt("products/product[@name='abc']", true);
1210         assertEquals("Wrong value from sub config", "abc-product",
1211                 subConfig.getString("desc"));
1212     }
1213 
1214     private void verify(String key, CombinedConfiguration config, int rows)
1215     {
1216         System.setProperty("Id", key);
1217         org.slf4j.MDC.put("Id", key);
1218         int actual = config.getInt("rowsPerPage");
1219         assertTrue("expected: " + rows + " actual: " + actual, actual == rows);
1220     }
1221 
1222 
1223     /**
1224      * A specialized combined configuration implementation used for testing
1225      * custom result classes.
1226      */
1227     public static class ExtendedCombinedConfiguration extends
1228             CombinedConfiguration
1229     {
1230         /**
1231          * The serial version UID.
1232          */
1233         private static final long serialVersionUID = 4678031745085083392L;
1234 
1235         @Override
1236         public Object getProperty(String key)
1237         {
1238             if (key.equals("test"))
1239             {
1240                 return "Extended";
1241             }
1242             return super.getProperty(key);
1243         }
1244     }
1245 
1246     public static class ExtendedXMLConfiguration extends XMLConfiguration
1247     {
1248         private static final long serialVersionUID = 1L;
1249 
1250         public ExtendedXMLConfiguration()
1251         {
1252         }
1253 
1254     }
1255 
1256     public static class TestLookup extends StrLookup
1257     {
1258         Map<String, String> map = new HashMap<String, String>();
1259 
1260         public TestLookup()
1261         {
1262             map.put("test_file_xml", "test.xml");
1263             map.put("test_file_combine", "testcombine1.xml");
1264             map.put("test_key", "test.value");
1265         }
1266 
1267         @Override
1268         public String lookup(String key)
1269         {
1270             if (key == null)
1271             {
1272                 return null;
1273             }
1274             return map.get(key);
1275 
1276         }
1277     }
1278 }
1279