View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *     http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.configuration;
18  
19  import static org.junit.Assert.assertEquals;
20  import static org.junit.Assert.assertNull;
21  import static org.junit.Assert.assertSame;
22  import static org.junit.Assert.assertTrue;
23  
24  import java.io.File;
25  import java.io.IOException;
26  import java.util.ArrayList;
27  import java.util.HashSet;
28  import java.util.List;
29  import java.util.NoSuchElementException;
30  import java.util.Set;
31  
32  import org.apache.commons.collections.CollectionUtils;
33  import org.apache.commons.configuration.event.ConfigurationEvent;
34  import org.apache.commons.configuration.event.ConfigurationListener;
35  import org.apache.commons.configuration.interpol.ConfigurationInterpolator;
36  import org.apache.commons.configuration.reloading.FileAlwaysReloadingStrategy;
37  import org.apache.commons.configuration.tree.ConfigurationNode;
38  import org.apache.commons.configuration.tree.xpath.XPathExpressionEngine;
39  import org.apache.commons.lang.text.StrLookup;
40  import org.junit.Before;
41  import org.junit.Rule;
42  import org.junit.Test;
43  import org.junit.rules.TemporaryFolder;
44  
45  /**
46   * Test case for SubnodeConfiguration.
47   *
48   * @author <a
49   * href="http://commons.apache.org/configuration/team-list.html">Commons
50   * Configuration team</a>
51   * @version $Id: TestSubnodeConfiguration.java 1225018 2011-12-27 21:14:59Z oheger $
52   */
53  public class TestSubnodeConfiguration
54  {
55      /** An array with names of tables (test data). */
56      private static final String[] TABLE_NAMES =
57      { "documents", "users" };
58  
59      /** An array with the fields of the test tables (test data). */
60      private static final String[][] TABLE_FIELDS =
61      {
62      { "docid", "docname", "author", "dateOfCreation", "version", "size" },
63      { "userid", "uname", "firstName", "lastName" } };
64  
65      /** Constant for an updated table name.*/
66      private static final String NEW_TABLE_NAME = "newTable";
67  
68      /** A helper object for creating temporary files. */
69      @Rule
70      public TemporaryFolder folder = new TemporaryFolder();
71  
72      /** The parent configuration. */
73      HierarchicalConfiguration parent;
74  
75      /** The subnode configuration to be tested. */
76      SubnodeConfiguration config;
77  
78      /** Stores a counter for the created nodes. */
79      int nodeCounter;
80  
81      @Before
82      public void setUp() throws Exception
83      {
84          parent = setUpParentConfig();
85          nodeCounter = 0;
86      }
87  
88      /**
89       * Tests creation of a subnode config.
90       */
91      @Test
92      public void testInitSubNodeConfig()
93      {
94          setUpSubnodeConfig();
95          assertSame("Wrong root node in subnode", getSubnodeRoot(parent), config
96                  .getRoot());
97          assertSame("Wrong parent config", parent, config.getParent());
98      }
99  
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 }