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 }