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    *     https://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.configuration2;
18  
19  import static org.junit.jupiter.api.Assertions.assertEquals;
20  import static org.junit.jupiter.api.Assertions.assertFalse;
21  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
22  import static org.junit.jupiter.api.Assertions.assertNotSame;
23  import static org.junit.jupiter.api.Assertions.assertSame;
24  import static org.junit.jupiter.api.Assertions.assertTrue;
25  import static org.junit.jupiter.api.Assertions.fail;
26  
27  import java.io.File;
28  import java.util.ArrayList;
29  import java.util.Collection;
30  import java.util.Collections;
31  import java.util.List;
32  import java.util.concurrent.CountDownLatch;
33  
34  import org.apache.commons.configuration2.SynchronizerTestImpl.Methods;
35  import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
36  import org.apache.commons.configuration2.builder.fluent.Parameters;
37  import org.apache.commons.configuration2.ex.ConfigurationException;
38  import org.apache.commons.configuration2.io.FileHandler;
39  import org.apache.commons.configuration2.tree.ImmutableNode;
40  import org.apache.commons.configuration2.tree.InMemoryNodeModel;
41  import org.apache.commons.configuration2.tree.NodeStructureHelper;
42  import org.junit.jupiter.api.BeforeEach;
43  import org.junit.jupiter.api.Test;
44  
45  /**
46   * A test class for {@code BaseHierarchicalConfiguration} which checks whether the Synchronizer is called correctly by
47   * the methods specific for hierarchical configurations.
48   */
49  public class TestBaseHierarchicalConfigurationSynchronization {
50  
51      /**
52       * A thread class for testing concurrent access to SubNode configurations.
53       */
54      private static final class SubNodeAccessThread extends Thread {
55  
56          /** The test configuration. */
57          private final HierarchicalConfiguration<ImmutableNode> config;
58  
59          /** The latch for synchronizing thread start. */
60          private final CountDownLatch latch;
61  
62          /** The key for the sub configuration. */
63          private final String keySub;
64  
65          /** The key for the property. */
66          private final String keyProp;
67  
68          /** The value read by this thread. */
69          private String value;
70  
71          /**
72           * Creates a new instance of {@code SubNodeAccessThread}
73           *
74           * @param c the test configuration
75           * @param startLatch the start latch
76           * @param keySubConfig the key for the sub configuration
77           * @param keyProperty the key for the property
78           */
79          public SubNodeAccessThread(final HierarchicalConfiguration<ImmutableNode> c, final CountDownLatch startLatch, final String keySubConfig,
80              final String keyProperty) {
81              config = c;
82              latch = startLatch;
83              keySub = keySubConfig;
84              keyProp = keyProperty;
85          }
86  
87          @Override
88          public void run() {
89              try {
90                  latch.await();
91                  final HierarchicalConfiguration<ImmutableNode> subConfig = config.configurationAt(keySub, true);
92                  value = subConfig.getString(keyProp);
93              } catch (final InterruptedException iex) {
94                  // ignore
95              }
96          }
97  
98          /**
99           * Verifies that the correct value was read.
100          */
101         public void verify() {
102             try {
103                 join();
104             } catch (final InterruptedException e) {
105                 fail("Waiting was interrupted: " + e);
106             }
107             assertEquals("I'm complex!", value);
108         }
109     }
110 
111     /**
112      * Tests whether the specified configuration is detached.
113      *
114      * @param c the configuration to test
115      * @return a flag whether the root node of this configuration is detached
116      */
117     private static boolean isDetached(final HierarchicalConfiguration<ImmutableNode> c) {
118         final SubnodeConfiguration subnodeConfig = assertInstanceOf(SubnodeConfiguration.class, c);
119         final InMemoryNodeModel nodeModel = subnodeConfig.getRootNodeModel();
120         return nodeModel.isTrackedNodeDetached(subnodeConfig.getRootSelector());
121     }
122 
123     /** The test synchronizer. */
124     private SynchronizerTestImpl sync;
125 
126     /** The test file to be read. */
127     private File testFile;
128 
129     /** The test configuration. */
130     private BaseHierarchicalConfiguration config;
131 
132     @BeforeEach
133     public void setUp() throws Exception {
134         final XMLConfiguration c = new XMLConfiguration();
135         testFile = ConfigurationAssert.getTestFile("test.xml");
136         new FileHandler(c).load(testFile);
137         sync = new SynchronizerTestImpl();
138         c.setSynchronizer(sync);
139         config = c;
140     }
141 
142     /**
143      * Tests whether addNodes() is correctly synchronized.
144      */
145     @Test
146     void testAddNodesSynchronized() {
147         final ImmutableNode node = NodeStructureHelper.createNode("newNode", "true");
148         config.addNodes("test.addNodes", Collections.singleton(node));
149         sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE);
150     }
151 
152     /**
153      * Tests whether childConfigurationsAt() is correctly synchronized.
154      */
155     @Test
156     void testChildConfigurationsAtSynchronized() {
157         final List<HierarchicalConfiguration<ImmutableNode>> subs = config.childConfigurationsAt("clear");
158         assertFalse(subs.isEmpty());
159         sync.verify(Methods.BEGIN_READ, Methods.END_READ);
160     }
161 
162     /**
163      * Tests whether clearTree() is correctly synchronized.
164      */
165     @Test
166     void testClearTreeSynchronized() {
167         config.clearTree("clear");
168         sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE);
169     }
170 
171     /**
172      * Tests whether a clone() operation also copies the data used to manage SubnodeConfiguration objects.
173      */
174     @Test
175     void testCloneCopySubnodeData() {
176         final BaseHierarchicalConfiguration conf2 = new BaseHierarchicalConfiguration(config);
177 
178         final HierarchicalConfiguration<ImmutableNode> sub = conf2.configurationAt("element2.subelement", true);
179         @SuppressWarnings("unchecked") // clone retains the type
180         final HierarchicalConfiguration<ImmutableNode> copy = (HierarchicalConfiguration<ImmutableNode>) conf2.clone();
181         final HierarchicalConfiguration<ImmutableNode> sub2 = copy.configurationAt("element2.subelement", true);
182         // This must not cause a validate operation on sub1, but on sub2
183         copy.clearTree("element2");
184         assertTrue(isDetached(sub2));
185         assertFalse(isDetached(sub));
186     }
187 
188     /**
189      * Tests whether clone() is correctly synchronized.
190      */
191     @Test
192     void testCloneSynchronized() {
193         final BaseHierarchicalConfiguration clone = (BaseHierarchicalConfiguration) config.clone();
194         sync.verify(Methods.BEGIN_READ, Methods.END_READ);
195         assertNotSame(config.getSynchronizer(), clone.getSynchronizer());
196     }
197 
198     /**
199      * Tests whether synchronization is performed when constructing a SubnodeConfiguration.
200      */
201     @Test
202     void testConfigurationAtSynchronized() {
203         final HierarchicalConfiguration<ImmutableNode> sub = config.configurationAt("element2");
204         assertEquals("I'm complex!", sub.getString("subelement.subsubelement"));
205         sync.verify(Methods.BEGIN_READ, Methods.END_READ, Methods.BEGIN_READ, Methods.END_READ);
206     }
207 
208     /**
209      * Tests whether synchronization is performed when constructing multiple SubnodeConfiguration objects.
210      */
211     @Test
212     void testConfigurationsAtSynchronized() {
213         final List<HierarchicalConfiguration<ImmutableNode>> subs = config.configurationsAt("list.item");
214         assertFalse(subs.isEmpty());
215         sync.verify(Methods.BEGIN_READ, Methods.END_READ);
216     }
217 
218     /**
219      * Tests whether synchronization is performed when copying a configuration.
220      */
221     @Test
222     void testCopyConstructorSynchronized() {
223         final BaseHierarchicalConfiguration copy = new BaseHierarchicalConfiguration(config);
224         sync.verify(Methods.BEGIN_READ, Methods.END_READ);
225         assertNotSame(sync, copy.getSynchronizer());
226     }
227 
228     /**
229      * Tests whether getMaxIndex() is correctly synchronized.
230      */
231     @Test
232     void testGetMaxIndexSynchronized() {
233         assertTrue(config.getMaxIndex("list.item") > 0);
234         sync.verify(Methods.BEGIN_READ, Methods.END_READ);
235     }
236 
237     /**
238      * Tests whether getRootElementName() is correctly synchronized.
239      */
240     @Test
241     void testGetRootElementNameSynchronized() {
242         assertEquals("testconfig", config.getRootElementName());
243         sync.verify(Methods.BEGIN_READ, Methods.END_READ);
244     }
245 
246     /**
247      * Tests that access to an initialized configuration's sub configurations is possible without a special synchronizer.
248      */
249     @Test
250     void testReadOnlyAccessToSubConfigurations() throws ConfigurationException {
251         final FileBasedConfigurationBuilder<XMLConfiguration> builder = new FileBasedConfigurationBuilder<>(XMLConfiguration.class);
252         builder.configure(new Parameters().fileBased().setFile(testFile));
253         config = builder.getConfiguration();
254 
255         final CountDownLatch startLatch = new CountDownLatch(1);
256         final Collection<SubNodeAccessThread> threads = new ArrayList<>();
257         for (int i = 0; i < 4; i++) {
258             final SubNodeAccessThread t = new SubNodeAccessThread(config, startLatch, "element2", "subelement.subsubelement");
259             t.start();
260             threads.add(t);
261         }
262         for (int i = 0; i < 4; i++) {
263             final SubNodeAccessThread t = new SubNodeAccessThread(config, startLatch, "element2.subelement", "subsubelement");
264             t.start();
265             threads.add(t);
266         }
267 
268         startLatch.countDown();
269         for (final SubNodeAccessThread t : threads) {
270             t.verify();
271         }
272     }
273 
274     /**
275      * Tests whether updates on nodes are communicated to all SubnodeConfigurations of a configuration.
276      */
277     @Test
278     void testSubnodeUpdate() {
279         config.addProperty("element2.test", Boolean.TRUE);
280         final HierarchicalConfiguration<ImmutableNode> sub = config.configurationAt("element2", true);
281         final HierarchicalConfiguration<ImmutableNode> subsub = sub.configurationAt("subelement", true);
282         config.clearTree("element2.subelement");
283         assertFalse(isDetached(sub));
284         assertTrue(isDetached(subsub));
285     }
286 
287     /**
288      * Tests whether updates caused by a SubnodeConfiguration are communicated to all other SubnodeConfigurations.
289      */
290     @Test
291     void testSubnodeUpdateBySubnode() {
292         final HierarchicalConfiguration<ImmutableNode> sub = config.configurationAt("element2", true);
293         final HierarchicalConfiguration<ImmutableNode> subsub = sub.configurationAt("subelement", true);
294         final HierarchicalConfiguration<ImmutableNode> sub2 = config.configurationAt("element2.subelement", true);
295         sub.clearTree("subelement");
296         assertTrue(isDetached(sub2));
297         assertTrue(isDetached(subsub));
298     }
299 
300     /**
301      * Tests whether subset() is correctly synchronized.
302      */
303     @Test
304     void testSubsetSynchronized() {
305         final Configuration subset = config.subset("test");
306         sync.verify(Methods.BEGIN_READ, Methods.END_READ);
307         assertSame(sync, subset.getSynchronizer());
308     }
309 }