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 import static org.junit.Assert.fail; 026 027 import java.io.File; 028 import java.io.StringWriter; 029 import java.lang.reflect.InvocationHandler; 030 import java.lang.reflect.Method; 031 import java.lang.reflect.Proxy; 032 import java.net.URL; 033 import java.util.Collection; 034 import java.util.HashMap; 035 import java.util.Iterator; 036 import java.util.List; 037 import java.util.Map; 038 import java.util.Set; 039 040 import org.apache.commons.configuration.beanutils.BeanHelper; 041 import org.apache.commons.configuration.event.ConfigurationListenerTestImpl; 042 import org.apache.commons.configuration.reloading.FileChangedReloadingStrategy; 043 import org.apache.commons.configuration.tree.ConfigurationNode; 044 import org.apache.commons.configuration.tree.DefaultConfigurationNode; 045 import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine; 046 import org.apache.commons.lang.SystemUtils; 047 import org.apache.commons.lang.text.StrLookup; 048 import org.apache.commons.logging.Log; 049 import org.apache.commons.logging.LogFactory; 050 import org.apache.commons.logging.impl.Log4JLogger; 051 import org.apache.log4j.Level; 052 import org.apache.log4j.Logger; 053 import org.apache.log4j.SimpleLayout; 054 import org.apache.log4j.WriterAppender; 055 import org.junit.Before; 056 import org.junit.Test; 057 058 /** 059 * Test class for DefaultConfigurationBuilder. 060 * 061 * @author <a 062 * href="http://commons.apache.org/configuration/team-list.html">Commons 063 * Configuration team</a> 064 * @version $Id: TestDefaultConfigurationBuilder.java 1301997 2012-03-17 20:32:02Z sebb $ 065 */ 066 public class TestDefaultConfigurationBuilder 067 { 068 /** Test configuration definition file. */ 069 private static final File TEST_FILE = ConfigurationAssert 070 .getTestFile("testDigesterConfiguration.xml"); 071 072 private static final File ADDITIONAL_FILE = ConfigurationAssert 073 .getTestFile("testDigesterConfiguration2.xml"); 074 075 private static final File OPTIONAL_FILE = ConfigurationAssert 076 .getTestFile("testDigesterOptionalConfiguration.xml"); 077 078 private static final File OPTIONALEX_FILE = ConfigurationAssert 079 .getTestFile("testDigesterOptionalConfigurationEx.xml"); 080 081 private static final File MULTI_FILE = ConfigurationAssert 082 .getTestFile("testDigesterConfiguration3.xml"); 083 084 private static final File INIT_FILE = ConfigurationAssert 085 .getTestFile("testComplexInitialization.xml"); 086 087 private static final File CLASS_FILE = ConfigurationAssert 088 .getTestFile("testExtendedClass.xml"); 089 090 private static final File PROVIDER_FILE = ConfigurationAssert 091 .getTestFile("testConfigurationProvider.xml"); 092 093 private static final File EXTENDED_PROVIDER_FILE = ConfigurationAssert 094 .getTestFile("testExtendedXMLConfigurationProvider.xml"); 095 096 private static final File GLOBAL_LOOKUP_FILE = ConfigurationAssert 097 .getTestFile("testGlobalLookup.xml"); 098 099 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