blob: 7355dea676bc19e6c17d3152025eb301e0b1a16d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.commons.configuration2;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNotSame;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import org.apache.commons.configuration2.SynchronizerTestImpl.Methods;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.builder.fluent.Parameters;
import org.apache.commons.configuration2.ex.ConfigurationException;
import org.apache.commons.configuration2.io.FileHandler;
import org.apache.commons.configuration2.tree.ImmutableNode;
import org.apache.commons.configuration2.tree.InMemoryNodeModel;
import org.apache.commons.configuration2.tree.NodeStructureHelper;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
/**
* A test class for {@code BaseHierarchicalConfiguration} which checks whether the Synchronizer is called correctly by
* the methods specific for hierarchical configurations.
*/
public class TestBaseHierarchicalConfigurationSynchronization {
/**
* A thread class for testing concurrent access to SubNode configurations.
*/
private static final class SubNodeAccessThread extends Thread {
/** The test configuration. */
private final HierarchicalConfiguration<ImmutableNode> config;
/** The latch for synchronizing thread start. */
private final CountDownLatch latch;
/** The key for the sub configuration. */
private final String keySub;
/** The key for the property. */
private final String keyProp;
/** The value read by this thread. */
private String value;
/**
* Creates a new instance of {@code SubNodeAccessThread}
*
* @param c the test configuration
* @param startLatch the start latch
* @param keySubConfig the key for the sub configuration
* @param keyProperty the key for the property
*/
public SubNodeAccessThread(final HierarchicalConfiguration<ImmutableNode> c, final CountDownLatch startLatch, final String keySubConfig,
final String keyProperty) {
config = c;
latch = startLatch;
keySub = keySubConfig;
keyProp = keyProperty;
}
@Override
public void run() {
try {
latch.await();
final HierarchicalConfiguration<ImmutableNode> subConfig = config.configurationAt(keySub, true);
value = subConfig.getString(keyProp);
} catch (final InterruptedException iex) {
// ignore
}
}
/**
* Verifies that the correct value was read.
*/
public void verify() {
try {
join();
} catch (final InterruptedException e) {
fail("Waiting was interrupted: " + e);
}
assertEquals("I'm complex!", value);
}
}
/**
* Tests whether the specified configuration is detached.
*
* @param c the configuration to test
* @return a flag whether the root node of this configuration is detached
*/
private static boolean isDetached(final HierarchicalConfiguration<ImmutableNode> c) {
final SubnodeConfiguration subnodeConfig = assertInstanceOf(SubnodeConfiguration.class, c);
final InMemoryNodeModel nodeModel = subnodeConfig.getRootNodeModel();
return nodeModel.isTrackedNodeDetached(subnodeConfig.getRootSelector());
}
/** The test synchronizer. */
private SynchronizerTestImpl sync;
/** The test file to be read. */
private File testFile;
/** The test configuration. */
private BaseHierarchicalConfiguration config;
@BeforeEach
public void setUp() throws Exception {
final XMLConfiguration c = new XMLConfiguration();
testFile = ConfigurationAssert.getTestFile("test.xml");
new FileHandler(c).load(testFile);
sync = new SynchronizerTestImpl();
c.setSynchronizer(sync);
config = c;
}
/**
* Tests whether addNodes() is correctly synchronized.
*/
@Test
public void testAddNodesSynchronized() {
final ImmutableNode node = NodeStructureHelper.createNode("newNode", "true");
config.addNodes("test.addNodes", Collections.singleton(node));
sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE);
}
/**
* Tests whether childConfigurationsAt() is correctly synchronized.
*/
@Test
public void testChildConfigurationsAtSynchronized() {
final List<HierarchicalConfiguration<ImmutableNode>> subs = config.childConfigurationsAt("clear");
assertFalse(subs.isEmpty());
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
}
/**
* Tests whether clearTree() is correctly synchronized.
*/
@Test
public void testClearTreeSynchronized() {
config.clearTree("clear");
sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE);
}
/**
* Tests whether a clone() operation also copies the data used to manage SubnodeConfiguration objects.
*/
@Test
public void testCloneCopySubnodeData() {
final BaseHierarchicalConfiguration conf2 = new BaseHierarchicalConfiguration(config);
final HierarchicalConfiguration<ImmutableNode> sub = conf2.configurationAt("element2.subelement", true);
@SuppressWarnings("unchecked") // clone retains the type
final HierarchicalConfiguration<ImmutableNode> copy = (HierarchicalConfiguration<ImmutableNode>) conf2.clone();
final HierarchicalConfiguration<ImmutableNode> sub2 = copy.configurationAt("element2.subelement", true);
// This must not cause a validate operation on sub1, but on sub2
copy.clearTree("element2");
assertTrue(isDetached(sub2));
assertFalse(isDetached(sub));
}
/**
* Tests whether clone() is correctly synchronized.
*/
@Test
public void testCloneSynchronized() {
final BaseHierarchicalConfiguration clone = (BaseHierarchicalConfiguration) config.clone();
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
assertNotSame(config.getSynchronizer(), clone.getSynchronizer());
}
/**
* Tests whether synchronization is performed when constructing a SubnodeConfiguration.
*/
@Test
public void testConfigurationAtSynchronized() {
final HierarchicalConfiguration<ImmutableNode> sub = config.configurationAt("element2");
assertEquals("I'm complex!", sub.getString("subelement.subsubelement"));
sync.verify(Methods.BEGIN_READ, Methods.END_READ, Methods.BEGIN_READ, Methods.END_READ);
}
/**
* Tests whether synchronization is performed when constructing multiple SubnodeConfiguration objects.
*/
@Test
public void testConfigurationsAtSynchronized() {
final List<HierarchicalConfiguration<ImmutableNode>> subs = config.configurationsAt("list.item");
assertFalse(subs.isEmpty());
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
}
/**
* Tests whether synchronization is performed when copying a configuration.
*/
@Test
public void testCopyConstructorSynchronized() {
final BaseHierarchicalConfiguration copy = new BaseHierarchicalConfiguration(config);
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
assertNotSame(sync, copy.getSynchronizer());
}
/**
* Tests whether getMaxIndex() is correctly synchronized.
*/
@Test
public void testGetMaxIndexSynchronized() {
assertTrue(config.getMaxIndex("list.item") > 0);
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
}
/**
* Tests whether getRootElementName() is correctly synchronized.
*/
@Test
public void testGetRootElementNameSynchronized() {
assertEquals("testconfig", config.getRootElementName());
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
}
/**
* Tests that access to an initialized configuration's sub configurations is possible without a special synchronizer.
*/
@Test
public void testReadOnlyAccessToSubConfigurations() throws ConfigurationException {
final FileBasedConfigurationBuilder<XMLConfiguration> builder = new FileBasedConfigurationBuilder<>(XMLConfiguration.class);
builder.configure(new Parameters().fileBased().setFile(testFile));
config = builder.getConfiguration();
final CountDownLatch startLatch = new CountDownLatch(1);
final Collection<SubNodeAccessThread> threads = new ArrayList<>();
for (int i = 0; i < 4; i++) {
final SubNodeAccessThread t = new SubNodeAccessThread(config, startLatch, "element2", "subelement.subsubelement");
t.start();
threads.add(t);
}
for (int i = 0; i < 4; i++) {
final SubNodeAccessThread t = new SubNodeAccessThread(config, startLatch, "element2.subelement", "subsubelement");
t.start();
threads.add(t);
}
startLatch.countDown();
for (final SubNodeAccessThread t : threads) {
t.verify();
}
}
/**
* Tests whether updates on nodes are communicated to all SubnodeConfigurations of a configuration.
*/
@Test
public void testSubnodeUpdate() {
config.addProperty("element2.test", Boolean.TRUE);
final HierarchicalConfiguration<ImmutableNode> sub = config.configurationAt("element2", true);
final HierarchicalConfiguration<ImmutableNode> subsub = sub.configurationAt("subelement", true);
config.clearTree("element2.subelement");
assertFalse(isDetached(sub));
assertTrue(isDetached(subsub));
}
/**
* Tests whether updates caused by a SubnodeConfiguration are communicated to all other SubnodeConfigurations.
*/
@Test
public void testSubnodeUpdateBySubnode() {
final HierarchicalConfiguration<ImmutableNode> sub = config.configurationAt("element2", true);
final HierarchicalConfiguration<ImmutableNode> subsub = sub.configurationAt("subelement", true);
final HierarchicalConfiguration<ImmutableNode> sub2 = config.configurationAt("element2.subelement", true);
sub.clearTree("subelement");
assertTrue(isDetached(sub2));
assertTrue(isDetached(subsub));
}
/**
* Tests whether subset() is correctly synchronized.
*/
@Test
public void testSubsetSynchronized() {
final Configuration subset = config.subset("test");
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
assertSame(sync, subset.getSynchronizer());
}
}