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.assertNull; 021 import static org.junit.Assert.assertSame; 022 import static org.junit.Assert.assertTrue; 023 024 import java.io.File; 025 import java.io.IOException; 026 import java.util.ArrayList; 027 import java.util.HashSet; 028 import java.util.List; 029 import java.util.NoSuchElementException; 030 import java.util.Set; 031 032 import org.apache.commons.collections.CollectionUtils; 033 import org.apache.commons.configuration.event.ConfigurationEvent; 034 import org.apache.commons.configuration.event.ConfigurationListener; 035 import org.apache.commons.configuration.interpol.ConfigurationInterpolator; 036 import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy; 037 import org.apache.commons.configuration.tree.ConfigurationNode; 038 import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine; 039 import org.apache.commons.lang.text.StrLookup; 040 import org.junit.Before; 041 import org.junit.Rule; 042 import org.junit.Test; 043 import org.junit.rules.TemporaryFolder; 044 045 /** 046 * Test case for SubnodeConfiguration. 047 * 048 * @author <a 049 * href="http://commons.apache.org/configuration/team-list.html">Commons 050 * Configuration team</a> 051 * @version $Id: TestSubnodeConfiguration.java 1225018 2011-12-27 21:14:59Z oheger $ 052 */ 053 public class TestSubnodeConfiguration 054 { 055 /** An array with names of tables (test data). */ 056 private static final String[] TABLE_NAMES = 057 { "documents", "users" }; 058 059 /** An array with the fields of the test tables (test data). */ 060 private static final String[][] TABLE_FIELDS = 061 { 062 { "docid", "docname", "author", "dateOfCreation", "version", "size" }, 063 { "userid", "uname", "firstName", "lastName" } }; 064 065 /** Constant for an updated table name.*/ 066 private static final String NEW_TABLE_NAME = "newTable"; 067 068 /** A helper object for creating temporary files. */ 069 @Rule 070 public TemporaryFolder folder = new TemporaryFolder(); 071 072 /** The parent configuration. */ 073 HierarchicalConfiguration parent; 074 075 /** The subnode configuration to be tested. */ 076 SubnodeConfiguration config; 077 078 /** Stores a counter for the created nodes. */ 079 int nodeCounter; 080 081 @Before 082 public void setUp() throws Exception 083 { 084 parent = setUpParentConfig(); 085 nodeCounter = 0; 086 } 087 088 /** 089 * Tests creation of a subnode config. 090 */ 091 @Test 092 public void testInitSubNodeConfig() 093 { 094 setUpSubnodeConfig(); 095 assertSame("Wrong root node in subnode", getSubnodeRoot(parent), config 096 .getRoot()); 097 assertSame("Wrong parent config", parent, config.getParent()); 098 } 099 100 /** 101 * Tests constructing a subnode configuration with a null parent. This 102 * should cause an exception. 103 */ 104 @Test(expected = IllegalArgumentException.class) 105 public void testInitSubNodeConfigWithNullParent() 106 { 107 config = new SubnodeConfiguration(null, getSubnodeRoot(parent)); 108 } 109 110 /** 111 * Tests constructing a subnode configuration with a null root node. This 112 * should cause an exception. 113 */ 114 @Test(expected = IllegalArgumentException.class) 115 public void testInitSubNodeConfigWithNullNode() 116 { 117 config = new SubnodeConfiguration(parent, null); 118 } 119 120 /** 121 * Tests if properties of the sub node can be accessed. 122 */ 123 @Test 124 public void testGetProperties() 125 { 126 setUpSubnodeConfig(); 127 assertEquals("Wrong table name", TABLE_NAMES[0], config 128 .getString("name")); 129 List<Object> fields = config.getList("fields.field.name"); 130 assertEquals("Wrong number of fields", TABLE_FIELDS[0].length, fields 131 .size()); 132 for (int i = 0; i < TABLE_FIELDS[0].length; i++) 133 { 134 assertEquals("Wrong field at position " + i, TABLE_FIELDS[0][i], 135 fields.get(i)); 136 } 137 } 138 139 /** 140 * Tests setting of properties in both the parent and the subnode 141 * configuration and whether the changes are visible to each other. 142 */ 143 @Test 144 public void testSetProperty() 145 { 146 setUpSubnodeConfig(); 147 config.setProperty(null, "testTable"); 148 config.setProperty("name", TABLE_NAMES[0] + "_tested"); 149 assertEquals("Root value was not set", "testTable", parent 150 .getString("tables.table(0)")); 151 assertEquals("Table name was not changed", TABLE_NAMES[0] + "_tested", 152 parent.getString("tables.table(0).name")); 153 154 parent.setProperty("tables.table(0).fields.field(1).name", "testField"); 155 assertEquals("Field name was not changed", "testField", config 156 .getString("fields.field(1).name")); 157 } 158 159 /** 160 * Tests adding of properties. 161 */ 162 @Test 163 public void testAddProperty() 164 { 165 setUpSubnodeConfig(); 166 config.addProperty("[@table-type]", "test"); 167 assertEquals("parent.createNode() was not called", 1, nodeCounter); 168 assertEquals("Attribute not set", "test", parent 169 .getString("tables.table(0)[@table-type]")); 170 171 parent.addProperty("tables.table(0).fields.field(-1).name", "newField"); 172 List<Object> fields = config.getList("fields.field.name"); 173 assertEquals("New field was not added", TABLE_FIELDS[0].length + 1, 174 fields.size()); 175 assertEquals("Wrong last field", "newField", fields 176 .get(fields.size() - 1)); 177 } 178 179 /** 180 * Tests listing the defined keys. 181 */ 182 @Test 183 public void testGetKeys() 184 { 185 setUpSubnodeConfig(); 186 Set<String> keys = new HashSet<String>(); 187 CollectionUtils.addAll(keys, config.getKeys()); 188 assertEquals("Incorrect number of keys", 2, keys.size()); 189 assertTrue("Key 1 not contained", keys.contains("name")); 190 assertTrue("Key 2 not contained", keys.contains("fields.field.name")); 191 } 192 193 /** 194 * Tests setting the exception on missing flag. The subnode config obtains 195 * this flag from its parent. 196 */ 197 @Test(expected = NoSuchElementException.class) 198 public void testSetThrowExceptionOnMissing() 199 { 200 parent.setThrowExceptionOnMissing(true); 201 setUpSubnodeConfig(); 202 assertTrue("Exception flag not fetchted from parent", config 203 .isThrowExceptionOnMissing()); 204 config.getString("non existing key"); 205 } 206 207 /** 208 * Tests whether the exception flag can be set independently from the parent. 209 */ 210 @Test 211 public void testSetThrowExceptionOnMissingAffectsParent() 212 { 213 parent.setThrowExceptionOnMissing(true); 214 setUpSubnodeConfig(); 215 config.setThrowExceptionOnMissing(false); 216 assertTrue("Exception flag reset on parent", parent 217 .isThrowExceptionOnMissing()); 218 } 219 220 /** 221 * Tests handling of the delimiter parsing disabled flag. This is shared 222 * with the parent, too. 223 */ 224 @Test 225 public void testSetDelimiterParsingDisabled() 226 { 227 parent.setDelimiterParsingDisabled(true); 228 setUpSubnodeConfig(); 229 parent.setDelimiterParsingDisabled(false); 230 assertTrue("Delimiter parsing flag was not received from parent", 231 config.isDelimiterParsingDisabled()); 232 config.addProperty("newProp", "test1,test2,test3"); 233 assertEquals("New property was splitted", "test1,test2,test3", parent 234 .getString("tables.table(0).newProp")); 235 parent.setDelimiterParsingDisabled(true); 236 config.setDelimiterParsingDisabled(false); 237 assertTrue("Delimiter parsing flag was reset on parent", parent 238 .isDelimiterParsingDisabled()); 239 } 240 241 /** 242 * Tests manipulating the list delimiter. This piece of data is derived from 243 * the parent. 244 */ 245 @Test 246 public void testSetListDelimiter() 247 { 248 parent.setListDelimiter('/'); 249 setUpSubnodeConfig(); 250 parent.setListDelimiter(';'); 251 assertEquals("List delimiter not obtained from parent", '/', config 252 .getListDelimiter()); 253 config.addProperty("newProp", "test1,test2/test3"); 254 assertEquals("List was incorrectly splitted", "test1,test2", parent 255 .getString("tables.table(0).newProp")); 256 config.setListDelimiter(','); 257 assertEquals("List delimiter changed on parent", ';', parent 258 .getListDelimiter()); 259 } 260 261 /** 262 * Tests changing the expression engine. 263 */ 264 @Test 265 public void testSetExpressionEngine() 266 { 267 parent.setExpressionEngine(new XPathExpressionEngine()); 268 setUpSubnodeConfig(); 269 assertEquals("Wrong field name", TABLE_FIELDS[0][1], config 270 .getString("fields/field[2]/name")); 271 Set<String> keys = new HashSet<String>(); 272 CollectionUtils.addAll(keys, config.getKeys()); 273 assertEquals("Wrong number of keys", 2, keys.size()); 274 assertTrue("Key 1 not contained", keys.contains("name")); 275 assertTrue("Key 2 not contained", keys.contains("fields/field/name")); 276 config.setExpressionEngine(null); 277 assertTrue("Expression engine reset on parent", parent 278 .getExpressionEngine() instanceof XPathExpressionEngine); 279 } 280 281 /** 282 * Tests the configurationAt() method. 283 */ 284 @Test 285 public void testConfiguarationAt() 286 { 287 setUpSubnodeConfig(); 288 SubnodeConfiguration sub2 = config 289 .configurationAt("fields.field(1)"); 290 assertEquals("Wrong value of property", TABLE_FIELDS[0][1], sub2 291 .getString("name")); 292 assertEquals("Wrong parent", config.getParent(), sub2.getParent()); 293 } 294 295 /** 296 * Tests interpolation features. The subnode config should use its parent 297 * for interpolation. 298 */ 299 @Test 300 public void testInterpolation() 301 { 302 parent.addProperty("tablespaces.tablespace.name", "default"); 303 parent.addProperty("tablespaces.tablespace(-1).name", "test"); 304 parent.addProperty("tables.table(0).tablespace", 305 "${tablespaces.tablespace(0).name}"); 306 assertEquals("Wrong interpolated tablespace", "default", parent 307 .getString("tables.table(0).tablespace")); 308 309 setUpSubnodeConfig(); 310 assertEquals("Wrong interpolated tablespace in subnode", "default", 311 config.getString("tablespace")); 312 } 313 314 /** 315 * An additional test for interpolation when the configurationAt() method is 316 * involved. 317 */ 318 @Test 319 public void testInterpolationFromConfigurationAt() 320 { 321 parent.addProperty("base.dir", "/home/foo"); 322 parent.addProperty("test.absolute.dir.dir1", "${base.dir}/path1"); 323 parent.addProperty("test.absolute.dir.dir2", "${base.dir}/path2"); 324 parent.addProperty("test.absolute.dir.dir3", "${base.dir}/path3"); 325 326 Configuration sub = parent.configurationAt("test.absolute.dir"); 327 for (int i = 1; i < 4; i++) 328 { 329 assertEquals("Wrong interpolation in parent", "/home/foo/path" + i, 330 parent.getString("test.absolute.dir.dir" + i)); 331 assertEquals("Wrong interpolation in subnode", 332 "/home/foo/path" + i, sub.getString("dir" + i)); 333 } 334 } 335 336 /** 337 * An additional test for interpolation when the configurationAt() method is 338 * involved for a local interpolation. 339 */ 340 @Test 341 public void testLocalInterpolationFromConfigurationAt() 342 { 343 parent.addProperty("base.dir", "/home/foo"); 344 parent.addProperty("test.absolute.dir.dir1", "${base.dir}/path1"); 345 parent.addProperty("test.absolute.dir.dir2", "${dir1}"); 346 347 Configuration sub = parent.configurationAt("test.absolute.dir"); 348 assertEquals("Wrong interpolation in subnode", 349 "/home/foo/path1", sub.getString("dir1")); 350 assertEquals("Wrong local interpolation in subnode", 351 "/home/foo/path1", sub.getString("dir2")); 352 } 353 354 /** 355 * Tests manipulating the interpolator. 356 */ 357 @Test 358 public void testInterpolator() 359 { 360 parent.addProperty("tablespaces.tablespace.name", "default"); 361 parent.addProperty("tablespaces.tablespace(-1).name", "test"); 362 363 setUpSubnodeConfig(); 364 InterpolationTestHelper.testGetInterpolator(config); 365 } 366 367 @Test 368 public void testLocalLookupsInInterpolatorAreInherited() { 369 parent.addProperty("tablespaces.tablespace.name", "default"); 370 parent.addProperty("tablespaces.tablespace(-1).name", "test"); 371 parent.addProperty("tables.table(0).var", "${brackets:x}"); 372 373 ConfigurationInterpolator interpolator = parent.getInterpolator(); 374 interpolator.registerLookup("brackets", new StrLookup(){ 375 376 @Override 377 public String lookup(String key) { 378 return "(" + key +")"; 379 } 380 381 }); 382 setUpSubnodeConfig(); 383 assertEquals("Local lookup was not inherited", "(x)", config.getString("var", "")); 384 } 385 386 /** 387 * Tests a reload operation for the parent configuration when the subnode 388 * configuration does not support reloads. Then the new value should not be 389 * detected. 390 */ 391 @Test 392 public void testParentReloadNotSupported() throws ConfigurationException 393 { 394 Configuration c = setUpReloadTest(false); 395 assertEquals("Name changed in sub config", TABLE_NAMES[1], config 396 .getString("name")); 397 assertEquals("Name not changed in parent", NEW_TABLE_NAME, c 398 .getString("tables.table(1).name")); 399 } 400 401 /** 402 * Tests a reload operation for the parent configuration when the subnode 403 * configuration does support reloads. The new value should be returned. 404 */ 405 @Test 406 public void testParentReloadSupported() throws ConfigurationException 407 { 408 Configuration c = setUpReloadTest(true); 409 assertEquals("Name not changed in sub config", NEW_TABLE_NAME, config 410 .getString("name")); 411 assertEquals("Name not changed in parent", NEW_TABLE_NAME, c 412 .getString("tables.table(1).name")); 413 } 414 415 /** 416 * Tests whether events are fired if a change of the parent is detected. 417 */ 418 @Test 419 public void testParentReloadEvents() throws ConfigurationException 420 { 421 setUpReloadTest(true); 422 ConfigurationListenerTestImpl l = new ConfigurationListenerTestImpl(); 423 config.addConfigurationListener(l); 424 config.getString("name"); 425 assertEquals("Wrong number of events", 2, l.events.size()); 426 boolean before = true; 427 for (ConfigurationEvent e : l.events) 428 { 429 assertEquals("Wrong configuration", config, e.getSource()); 430 assertEquals("Wrong event type", 431 HierarchicalConfiguration.EVENT_SUBNODE_CHANGED, e 432 .getType()); 433 assertNull("Got a property name", e.getPropertyName()); 434 assertNull("Got a property value", e.getPropertyValue()); 435 assertEquals("Wrong before flag", before, e.isBeforeUpdate()); 436 before = !before; 437 } 438 } 439 440 /** 441 * Tests a reload operation for the parent configuration when the subnode 442 * configuration is aware of reloads, and the parent configuration is 443 * accessed first. The new value should be returned. 444 */ 445 @Test 446 public void testParentReloadSupportAccessParent() 447 throws ConfigurationException 448 { 449 Configuration c = setUpReloadTest(true); 450 assertEquals("Name not changed in parent", NEW_TABLE_NAME, c 451 .getString("tables.table(1).name")); 452 assertEquals("Name not changed in sub config", NEW_TABLE_NAME, config 453 .getString("name")); 454 } 455 456 /** 457 * Tests whether reloads work with sub subnode configurations. 458 */ 459 @Test 460 public void testParentReloadSubSubnode() throws ConfigurationException 461 { 462 setUpReloadTest(true); 463 SubnodeConfiguration sub = config.configurationAt("fields", true); 464 assertEquals("Wrong subnode key", "tables.table(1).fields", sub 465 .getSubnodeKey()); 466 assertEquals("Changed field not detected", "newField", sub 467 .getString("field(0).name")); 468 } 469 470 /** 471 * Tests creating a sub sub config when the sub config is not aware of 472 * changes. Then the sub sub config shouldn't be either. 473 */ 474 @Test 475 public void testParentReloadSubSubnodeNoChangeSupport() 476 throws ConfigurationException 477 { 478 setUpReloadTest(false); 479 SubnodeConfiguration sub = config.configurationAt("fields", true); 480 assertNull("Sub sub config is attached to parent", sub.getSubnodeKey()); 481 assertEquals("Changed field name returned", TABLE_FIELDS[1][0], sub 482 .getString("field(0).name")); 483 } 484 485 /** 486 * Prepares a test for a reload operation. 487 * 488 * @param supportReload a flag whether the subnode configuration should 489 * support reload operations 490 * @return the parent configuration that can be used for testing 491 * @throws ConfigurationException if an error occurs 492 */ 493 private XMLConfiguration setUpReloadTest(boolean supportReload) 494 throws ConfigurationException 495 { 496 try 497 { 498 File testFile = folder.newFile(); 499 XMLConfiguration xmlConf = new XMLConfiguration(parent); 500 xmlConf.setFile(testFile); 501 xmlConf.save(); 502 config = xmlConf.configurationAt("tables.table(1)", supportReload); 503 assertEquals("Wrong table name", TABLE_NAMES[1], 504 config.getString("name")); 505 xmlConf.setReloadingStrategy(new FileAlwaysReloadingStrategy()); 506 // Now change the configuration file 507 XMLConfiguration confUpdate = new XMLConfiguration(testFile); 508 confUpdate.setProperty("tables.table(1).name", NEW_TABLE_NAME); 509 confUpdate.setProperty("tables.table(1).fields.field(0).name", 510 "newField"); 511 confUpdate.save(); 512 return xmlConf; 513 } 514 catch (IOException ioex) 515 { 516 throw new ConfigurationException(ioex); 517 } 518 } 519 520 /** 521 * Tests a manipulation of the parent configuration that causes the subnode 522 * configuration to become invalid. In this case the sub config should be 523 * detached and keep its old values. 524 */ 525 @Test 526 public void testParentChangeDetach() 527 { 528 final String key = "tables.table(1)"; 529 config = parent.configurationAt(key, true); 530 assertEquals("Wrong subnode key", key, config.getSubnodeKey()); 531 assertEquals("Wrong table name", TABLE_NAMES[1], config 532 .getString("name")); 533 parent.clearTree(key); 534 assertEquals("Wrong table name after change", TABLE_NAMES[1], config 535 .getString("name")); 536 assertNull("Sub config was not detached", config.getSubnodeKey()); 537 } 538 539 /** 540 * Tests detaching a subnode configuration when an exception is thrown 541 * during reconstruction. This can happen e.g. if the expression engine is 542 * changed for the parent. 543 */ 544 @Test 545 public void testParentChangeDetatchException() 546 { 547 config = parent.configurationAt("tables.table(1)", true); 548 parent.setExpressionEngine(new XPathExpressionEngine()); 549 assertEquals("Wrong name of table", TABLE_NAMES[1], config 550 .getString("name")); 551 assertNull("Sub config was not detached", config.getSubnodeKey()); 552 } 553 554 /** 555 * Initializes the parent configuration. This method creates the typical 556 * structure of tables and fields nodes. 557 * 558 * @return the parent configuration 559 */ 560 protected HierarchicalConfiguration setUpParentConfig() 561 { 562 HierarchicalConfiguration conf = new HierarchicalConfiguration() 563 { 564 /** 565 * Serial version UID. 566 */ 567 private static final long serialVersionUID = 1L; 568 569 // Provide a special implementation of createNode() to check 570 // if it is called by the subnode config 571 @Override 572 protected Node createNode(String name) 573 { 574 nodeCounter++; 575 return super.createNode(name); 576 } 577 }; 578 for (int i = 0; i < TABLE_NAMES.length; i++) 579 { 580 conf.addProperty("tables.table(-1).name", TABLE_NAMES[i]); 581 for (int j = 0; j < TABLE_FIELDS[i].length; j++) 582 { 583 conf.addProperty("tables.table.fields.field(-1).name", 584 TABLE_FIELDS[i][j]); 585 } 586 } 587 return conf; 588 } 589 590 /** 591 * Returns the root node for the subnode config. This method returns the 592 * first table node. 593 * 594 * @param conf the parent config 595 * @return the root node for the subnode config 596 */ 597 protected ConfigurationNode getSubnodeRoot(HierarchicalConfiguration conf) 598 { 599 ConfigurationNode root = conf.getRoot(); 600 return root.getChild(0).getChild(0); 601 } 602 603 /** 604 * Performs a standard initialization of the subnode config to test. 605 */ 606 protected void setUpSubnodeConfig() 607 { 608 config = new SubnodeConfiguration(parent, getSubnodeRoot(parent)); 609 } 610 611 /** 612 * A specialized configuration listener for testing whether the expected 613 * events are fired. 614 */ 615 private static class ConfigurationListenerTestImpl implements ConfigurationListener 616 { 617 /** Stores the events received.*/ 618 final List<ConfigurationEvent> events = new ArrayList<ConfigurationEvent>(); 619 620 public void configurationChanged(ConfigurationEvent event) 621 { 622 events.add(event); 623 } 624 } 625 }