    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 java.io.File;
    20  import java.util.HashSet;
    21  import java.util.List;
    22  import java.util.NoSuchElementException;
    23  import java.util.Set;
    24
    25  import org.apache.commons.collections.CollectionUtils;
    26  import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
    27  import org.apache.commons.configuration.tree.ConfigurationNode;
    28  import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
    29
    30  import junit.framework.TestCase;
    31
    32  /***
    33   * Test case for SubnodeConfiguration.
    34   *
    35   * @author Oliver Heger
    36   * @version $Id: TestSubnodeConfiguration.java 531038 2007-04-21 14:31:58Z oheger $
    37   */
    38  public class TestSubnodeConfiguration extends TestCase
    39  {
    40      /*** An array with names of tables (test data). */
    41      private static final String[] TABLE_NAMES =
    42      { "documents", "users" };
    43
    44      /*** An array with the fields of the test tables (test data). */
    45      private static final String[][] TABLE_FIELDS =
    46      {
    47      { "docid", "docname", "author", "dateOfCreation", "version", "size" },
    48      { "userid", "uname", "firstName", "lastName" } };
    49
    50      /*** Constant for a test output file.*/
    51      private static final File TEST_FILE = new File("target/test.xml");
    52
    53      /*** Constant for an updated table name.*/
    54      private static final String NEW_TABLE_NAME = "newTable";
    55
    56      /*** The parent configuration. */
    57      HierarchicalConfiguration parent;
    58
    59      /*** The subnode configuration to be tested. */
    60      SubnodeConfiguration config;
    61
    62      /*** Stores the root node of the subnode config. */
    63      ConfigurationNode subnode;
    64
    65      /*** Stores a counter for the created nodes. */
    66      int nodeCounter;
    67
    68      protected void setUp() throws Exception
    69      {
    70          super.setUp();
    71          parent = setUpParentConfig();
    72          nodeCounter = 0;
    73      }
    74
    75      protected void tearDown() throws Exception
    76      {
    77          // remove the test output file if necessary
    78          if (TEST_FILE.exists())
    79          {
    80              TEST_FILE.delete();
    81          }
    82      }
    83
    84      /***
    85       * Tests creation of a subnode config.
    86       */
    87      public void testInitSubNodeConfig()
    88      {
    89          setUpSubnodeConfig();
    90          assertSame("Wrong root node in subnode", getSubnodeRoot(parent), config
    91                  .getRoot());
    92          assertSame("Wrong parent config", parent, config.getParent());
    93      }
    94
    95      /***
    96       * Tests constructing a subnode configuration with a null parent. This
    97       * should cause an exception.
    98       */
    99      public void testInitSubNodeConfigWithNullParent()
    100     {
    101         try
    102         {
    103             config = new SubnodeConfiguration(null, getSubnodeRoot(parent));
    104             fail("Could set a null parent config!");
    105         }
    106         catch (IllegalArgumentException iex)
    107         {
    108             // ok
    109         }
    110     }
    111
    112     /***
    113      * Tests constructing a subnode configuration with a null root node. This
    114      * should cause an exception.
    115      */
    116     public void testInitSubNodeConfigWithNullNode()
    117     {
    118         try
    119         {
    120             config = new SubnodeConfiguration(parent, null);
    121             fail("Could set a null root node!");
    122         }
    123         catch (IllegalArgumentException iex)
    124         {
    125             // ok
    126         }
    127     }
    128
    129     /***
    130      * Tests if properties of the sub node can be accessed.
    131      */
    132     public void testGetProperties()
    133     {
    134         setUpSubnodeConfig();
    135         assertEquals("Wrong table name", TABLE_NAMES[0], config
    136                 .getString("name"));
    137         List fields = config.getList("fields.field.name");
    138         assertEquals("Wrong number of fields", TABLE_FIELDS[0].length, fields
    139                 .size());
    140         for (int i = 0; i < TABLE_FIELDS[0].length; i++)
    141         {
    142             assertEquals("Wrong field at position " + i, TABLE_FIELDS[0][i],
    143                     fields.get(i));
    144         }
    145     }
    146
    147     /***
    148      * Tests setting of properties in both the parent and the subnode
    149      * configuration and whether the changes are visible to each other.
    150      */
    151     public void testSetProperty()
    152     {
    153         setUpSubnodeConfig();
    154         config.setProperty(null, "testTable");
    155         config.setProperty("name", TABLE_NAMES[0] + "_tested");
    156         assertEquals("Root value was not set", "testTable", parent
    157                 .getString("tables.table(0)"));
    158         assertEquals("Table name was not changed", TABLE_NAMES[0] + "_tested",
    159                 parent.getString("tables.table(0).name"));
    160
    161         parent.setProperty("tables.table(0).fields.field(1).name", "testField");
    162         assertEquals("Field name was not changed", "testField", config
    163                 .getString("fields.field(1).name"));
    164     }
    165
    166     /***
    167      * Tests adding of properties.
    168      */
    169     public void testAddProperty()
    170     {
    171         setUpSubnodeConfig();
    172         config.addProperty("[@table-type]", "test");
    173         assertEquals("parent.createNode() was not called", 1, nodeCounter);
    174         assertEquals("Attribute not set", "test", parent
    175                 .getString("tables.table(0)[@table-type]"));
    176
    177         parent.addProperty("tables.table(0).fields.field(-1).name", "newField");
    178         List fields = config.getList("fields.field.name");
    179         assertEquals("New field was not added", TABLE_FIELDS[0].length + 1,
    180                 fields.size());
    181         assertEquals("Wrong last field", "newField", fields
    182                 .get(fields.size() - 1));
    183     }
    184
    185     /***
    186      * Tests listing the defined keys.
    187      */
    188     public void testGetKeys()
    189     {
    190         setUpSubnodeConfig();
    191         Set keys = new HashSet();
    192         CollectionUtils.addAll(keys, config.getKeys());
    193         assertEquals("Incorrect number of keys", 2, keys.size());
    194         assertTrue("Key 1 not contained", keys.contains("name"));
    195         assertTrue("Key 2 not contained", keys.contains("fields.field.name"));
    196     }
    197
    198     /***
    199      * Tests setting the exception on missing flag. The subnode config obtains
    200      * this flag from its parent.
    201      */
    202     public void testSetThrowExceptionOnMissing()
    203     {
    204         parent.setThrowExceptionOnMissing(true);
    205         setUpSubnodeConfig();
    206         assertTrue("Exception flag not fetchted from parent", config
    207                 .isThrowExceptionOnMissing());
    208         try
    209         {
    210             config.getString("non existing key");
    211             fail("Could fetch non existing key!");
    212         }
    213         catch (NoSuchElementException nex)
    214         {
    215             // ok
    216         }
    217
    218         config.setThrowExceptionOnMissing(false);
    219         assertTrue("Exception flag reset on parent", parent
    220                 .isThrowExceptionOnMissing());
    221     }
    222
    223     /***
    224      * Tests handling of the delimiter parsing disabled flag. This is shared
    225      * with the parent, too.
    226      */
    227     public void testSetDelimiterParsingDisabled()
    228     {
    229         parent.setDelimiterParsingDisabled(true);
    230         setUpSubnodeConfig();
    231         parent.setDelimiterParsingDisabled(false);
    232         assertTrue("Delimiter parsing flag was not received from parent",
    233                 config.isDelimiterParsingDisabled());
    234         config.addProperty("newProp", "test1,test2,test3");
    235         assertEquals("New property was splitted", "test1,test2,test3", parent
    236                 .getString("tables.table(0).newProp"));
    237         parent.setDelimiterParsingDisabled(true);
    238         config.setDelimiterParsingDisabled(false);
    239         assertTrue("Delimiter parsing flag was reset on parent", parent
    240                 .isDelimiterParsingDisabled());
    241     }
    242
    243     /***
    244      * Tests manipulating the list delimiter. This piece of data is derived from
    245      * the parent.
    246      */
    247     public void testSetListDelimiter()
    248     {
    249         parent.setListDelimiter('/');
    250         setUpSubnodeConfig();
    251         parent.setListDelimiter(';');
    252         assertEquals("List delimiter not obtained from parent", '/', config
    253                 .getListDelimiter());
    254         config.addProperty("newProp", "test1,test2/test3");
    255         assertEquals("List was incorrectly splitted", "test1,test2", parent
    256                 .getString("tables.table(0).newProp"));
    257         config.setListDelimiter(',');
    258         assertEquals("List delimiter changed on parent", ';', parent
    259                 .getListDelimiter());
    260     }
    261
    262     /***
    263      * Tests changing the expression engine.
    264      */
    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 keys = new HashSet();
    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     public void testConfiguarationAt()
    285     {
    286         setUpSubnodeConfig();
    287         SubnodeConfiguration sub2 = (SubnodeConfiguration) config
    288                 .configurationAt("fields.field(1)");
    289         assertEquals("Wrong value of property", TABLE_FIELDS[0][1], sub2
    290                 .getString("name"));
    291         assertEquals("Wrong parent", config.getParent(), sub2.getParent());
    292     }
    293
    294     /***
    295      * Tests interpolation features. The subnode config should use its parent
    296      * for interpolation.
    297      */
    298     public void testInterpolation()
    299     {
    300         parent.addProperty("tablespaces.tablespace.name", "default");
    301         parent.addProperty("tablespaces.tablespace(-1).name", "test");
    302         parent.addProperty("tables.table(0).tablespace",
    303                 "${tablespaces.tablespace(0).name}");
    304         assertEquals("Wrong interpolated tablespace", "default", parent
    305                 .getString("tables.table(0).tablespace"));
    306
    307         setUpSubnodeConfig();
    308         assertEquals("Wrong interpolated tablespace in subnode", "default",
    309                 config.getString("tablespace"));
    310     }
    311
    312     /***
    313      * An additional test for interpolation when the configurationAt() method is
    314      * involved.
    315      */
    316     public void testInterpolationFromConfigurationAt()
    317     {
    318         parent.addProperty("base.dir", "/home/foo");
    319         parent.addProperty("test.absolute.dir.dir1", "${base.dir}/path1");
    320         parent.addProperty("test.absolute.dir.dir2", "${base.dir}/path2");
    321         parent.addProperty("test.absolute.dir.dir3", "${base.dir}/path3");
    322
    323         Configuration sub = parent.configurationAt("test.absolute.dir");
    324         for (int i = 1; i < 4; i++)
    325         {
    326             assertEquals("Wrong interpolation in parent", "/home/foo/path" + i,
    327                     parent.getString("test.absolute.dir.dir" + i));
    328             assertEquals("Wrong interpolation in subnode",
    329                     "/home/foo/path" + i, sub.getString("dir" + i));
    330         }
    331     }
    332
    333     /***
    334      * Tests a reload operation for the parent configuration when the subnode
    335      * configuration does not support reloads. Then the new value should not be
    336      * detected.
    337      */
    338     public void testParentReloadNotSupported() throws ConfigurationException
    339     {
    340         Configuration c = setUpReloadTest(false);
    341         assertEquals("Name changed in sub config", TABLE_NAMES[1], config
    342                 .getString("name"));
    343         assertEquals("Name not changed in parent", NEW_TABLE_NAME, c
    344                 .getString("tables.table(1).name"));
    345     }
    346
    347     /***
    348      * Tests a reload operation for the parent configuration when the subnode
    349      * configuration does support reloads. The new value should be returned.
    350      */
    351     public void testParentReloadSupported() throws ConfigurationException
    352     {
    353         Configuration c = setUpReloadTest(true);
    354         assertEquals("Name not changed in sub config", NEW_TABLE_NAME, config
    355                 .getString("name"));
    356         assertEquals("Name not changed in parent", NEW_TABLE_NAME, c
    357                 .getString("tables.table(1).name"));
    358     }
    359
    360     /***
    361      * Tests a reload operation for the parent configuration when the subnode
    362      * configuration is aware of reloads, and the parent configuration is
    363      * accessed first. The new value should be returned.
    364      */
    365     public void testParentReloadSupportAccessParent()
    366             throws ConfigurationException
    367     {
    368         Configuration c = setUpReloadTest(true);
    369         assertEquals("Name not changed in parent", NEW_TABLE_NAME, c
    370                 .getString("tables.table(1).name"));
    371         assertEquals("Name not changed in sub config", NEW_TABLE_NAME, config
    372                 .getString("name"));
    373     }
    374
    375     /***
    376      * Tests whether reloads work with sub subnode configurations.
    377      */
    378     public void testParentReloadSubSubnode() throws ConfigurationException
    379     {
    380         setUpReloadTest(true);
    381         SubnodeConfiguration sub = config.configurationAt("fields", true);
    382         assertEquals("Wrong subnode key", "tables.table(1).fields", sub
    383                 .getSubnodeKey());
    384         assertEquals("Changed field not detected", "newField", sub
    385                 .getString("field(0).name"));
    386     }
    387
    388     /***
    389      * Tests creating a sub sub config when the sub config is not aware of
    390      * changes. Then the sub sub config shouldn't be either.
    391      */
    392     public void testParentReloadSubSubnodeNoChangeSupport()
    393             throws ConfigurationException
    394     {
    395         setUpReloadTest(false);
    396         SubnodeConfiguration sub = config.configurationAt("fields", true);
    397         assertNull("Sub sub config is attached to parent", sub.getSubnodeKey());
    398         assertEquals("Changed field name returned", TABLE_FIELDS[1][0], sub
    399                 .getString("field(0).name"));
    400     }
    401
    402     /***
    403      * Prepares a test for a reload operation.
    404      *
    405      * @param supportReload a flag whether the subnode configuration should
    406      * support reload operations
    407      * @return the parent configuration that can be used for testing
    408      * @throws ConfigurationException if an error occurs
    409      */
    410     private XMLConfiguration setUpReloadTest(boolean supportReload)
    411             throws ConfigurationException
    412     {
    413         XMLConfiguration xmlConf = new XMLConfiguration(parent);
    414         xmlConf.setFile(TEST_FILE);
    415         xmlConf.save();
    416         config = xmlConf.configurationAt("tables.table(1)", supportReload);
    417         assertEquals("Wrong table name", TABLE_NAMES[1], config
    418                 .getString("name"));
    419         xmlConf.setReloadingStrategy(new FileAlwaysReloadingStrategy());
    420         // Now change the configuration file
    421         XMLConfiguration confUpdate = new XMLConfiguration(TEST_FILE);
    422         confUpdate.setProperty("tables.table(1).name", NEW_TABLE_NAME);
    423         confUpdate.setProperty("tables.table(1).fields.field(0).name",
    424                 "newField");
    425         confUpdate.save();
    426         return xmlConf;
    427     }
    428
    429     /***
    430      * Tests a manipulation of the parent configuration that causes the subnode
    431      * configuration to become invalid. In this case the sub config should be
    432      * detached and keep its old values.
    433      */
    434     public void testParentChangeDetach()
    435     {
    436         final String key = "tables.table(1)";
    437         config = parent.configurationAt(key, true);
    438         assertEquals("Wrong subnode key", key, config.getSubnodeKey());
    439         assertEquals("Wrong table name", TABLE_NAMES[1], config
    440                 .getString("name"));
    441         parent.clearTree(key);
    442         assertEquals("Wrong table name after change", TABLE_NAMES[1], config
    443                 .getString("name"));
    444         assertNull("Sub config was not detached", config.getSubnodeKey());
    445     }
    446
    447     /***
    448      * Tests detaching a subnode configuration when an exception is thrown
    449      * during reconstruction. This can happen e.g. if the expression engine is
    450      * changed for the parent.
    451      */
    452     public void testParentChangeDetatchException()
    453     {
    454         config = parent.configurationAt("tables.table(1)", true);
    455         parent.setExpressionEngine(new XPathExpressionEngine());
    456         assertEquals("Wrong name of table", TABLE_NAMES[1], config
    457                 .getString("name"));
    458         assertNull("Sub config was not detached", config.getSubnodeKey());
    459     }
    460
    461     /***
    462      * Initializes the parent configuration. This method creates the typical
    463      * structure of tables and fields nodes.
    464      *
    465      * @return the parent configuration
    466      */
    467     protected HierarchicalConfiguration setUpParentConfig()
    468     {
    469         HierarchicalConfiguration conf = new HierarchicalConfiguration()
    470         {
    471             // Provide a special implementation of createNode() to check
    472             // if it is called by the subnode config
    473             protected Node createNode(String name)
    474             {
    475                 nodeCounter++;
    476                 return super.createNode(name);
    477             }
    478         };
    479         for (int i = 0; i < TABLE_NAMES.length; i++)
    480         {
    481             conf.addProperty("tables.table(-1).name", TABLE_NAMES[i]);
    482             for (int j = 0; j < TABLE_FIELDS[i].length; j++)
    483             {
    484                 conf.addProperty("tables.table.fields.field(-1).name",
    485                         TABLE_FIELDS[i][j]);
    486             }
    487         }
    488         return conf;
    489     }
    490
    491     /***
    492      * Returns the root node for the subnode config. This method returns the
    493      * first table node.
    494      *
    495      * @param conf the parent config
    496      * @return the root node for the subnode config
    497      */
    498     protected ConfigurationNode getSubnodeRoot(HierarchicalConfiguration conf)
    499     {
    500         ConfigurationNode root = conf.getRoot();
    501         return root.getChild(0).getChild(0);
    502     }
    503
    504     /***
    505      * Performs a standard initialization of the subnode config to test.
    506      */
    507     protected void setUpSubnodeConfig()
    508     {
    509         config = new SubnodeConfiguration(parent, getSubnodeRoot(parent));
    510     }
    511 }