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