blob: 770acc090d58729cce06a563ca3be9c8d179d803 [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.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.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.Before;
import org.junit.Test;
/**
* A test class for {@code BaseHierarchicalConfiguration} which checks whether
* the Synchronizer is called correctly by the methods specific for hierarchical
* configurations.
*
* @version $Id$
*/
public class TestBaseHierarchicalConfigurationSynchronization
{
/** The test synchronizer. */
private SynchronizerTestImpl sync;
/** The test file to be read. */
private File testFile;
/** The test configuration. */
private BaseHierarchicalConfiguration config;
@Before
public void setUp() throws Exception
{
XMLConfiguration c = new XMLConfiguration();
testFile = ConfigurationAssert.getTestFile("test.xml");
new FileHandler(c).load(testFile);
sync = new SynchronizerTestImpl();
c.setSynchronizer(sync);
config = c;
}
/**
* Tests whether getMaxIndex() is correctly synchronized.
*/
@Test
public void testGetMaxIndexSynchronized()
{
assertTrue("Wrong max index", config.getMaxIndex("list.item") > 0);
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
}
/**
* Tests whether getRootElementName() is correctly synchronized.
*/
@Test
public void testGetRootElementNameSynchronized()
{
assertEquals("Wrong root element name", "testconfig",
config.getRootElementName());
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
}
/**
* Tests whether clone() is correctly synchronized.
*/
@Test
public void testCloneSynchronized()
{
BaseHierarchicalConfiguration clone =
(BaseHierarchicalConfiguration) config.clone();
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
assertNotSame("Synchronizer was not cloned", config.getSynchronizer(),
clone.getSynchronizer());
}
/**
* Tests whether addNodes() is correctly synchronized.
*/
@Test
public void testAddNodesSynchronized()
{
ImmutableNode node = NodeStructureHelper.createNode("newNode", "true");
config.addNodes("test.addNodes", Collections.singleton(node));
sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE);
}
/**
* Tests whether clearTree() is correctly synchronized.
*/
@Test
public void testClearTreeSynchronized()
{
config.clearTree("clear");
sync.verify(Methods.BEGIN_WRITE, Methods.END_WRITE);
}
/**
* Tests whether synchronization is performed when copying a configuration.
*/
@Test
public void testCopyConstructorSynchronized()
{
BaseHierarchicalConfiguration copy =
new BaseHierarchicalConfiguration(config);
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
assertNotSame("Synchronizer was copied", sync, copy.getSynchronizer());
}
/**
* Tests whether synchronization is performed when constructing a
* SubnodeConfiguration.
*/
@Test
public void testConfigurationAtSynchronized()
{
HierarchicalConfiguration<ImmutableNode> sub = config.configurationAt("element2");
assertEquals("Wrong property", "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()
{
List<HierarchicalConfiguration<ImmutableNode>> subs =
config.configurationsAt("list.item");
assertFalse("No subnode configurations", subs.isEmpty());
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
}
/**
* Tests whether childConfigurationsAt() is correctly synchronized.
*/
@Test
public void testChildConfigurationsAtSynchronized()
{
List<HierarchicalConfiguration<ImmutableNode>> subs =
config.childConfigurationsAt("clear");
assertFalse("No subnode configurations", subs.isEmpty());
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
}
/**
* 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(HierarchicalConfiguration<ImmutableNode> c)
{
assertTrue("Not a sub configuration", c instanceof SubnodeConfiguration);
return ((InMemoryNodeModel) c.getNodeModel())
.isTrackedNodeDetached(((SubnodeConfiguration) c)
.getRootSelector());
}
/**
* Tests whether updates on nodes are communicated to all
* SubnodeConfigurations of a configuration.
*/
@Test
public void testSubnodeUpdate()
{
config.addProperty("element2.test", Boolean.TRUE);
HierarchicalConfiguration<ImmutableNode> sub =
config.configurationAt("element2", true);
HierarchicalConfiguration<ImmutableNode> subsub =
sub.configurationAt("subelement", true);
config.clearTree("element2.subelement");
assertFalse("Sub1 detached", isDetached(sub));
assertTrue("Sub2 still attached", isDetached(subsub));
}
/**
* Tests whether updates caused by a SubnodeConfiguration are communicated
* to all other SubnodeConfigurations.
*/
@Test
public void testSubnodeUpdateBySubnode()
{
HierarchicalConfiguration<ImmutableNode> sub =
config.configurationAt("element2", true);
HierarchicalConfiguration<ImmutableNode> subsub =
sub.configurationAt("subelement", true);
HierarchicalConfiguration<ImmutableNode> sub2 =
config.configurationAt("element2.subelement", true);
sub.clearTree("subelement");
assertTrue("Sub2 still attached", isDetached(sub2));
assertTrue("Subsub still attached", isDetached(subsub));
}
/**
* Tests whether a clone() operation also copies the data used to manage
* SubnodeConfiguration objects.
*/
@Test
public void testCloneCopySubnodeData()
{
BaseHierarchicalConfiguration conf2 =
new BaseHierarchicalConfiguration(config);
HierarchicalConfiguration<ImmutableNode> sub =
conf2.configurationAt("element2.subelement", true);
@SuppressWarnings("unchecked") // clone retains the type
HierarchicalConfiguration<ImmutableNode> copy =
(HierarchicalConfiguration<ImmutableNode>) conf2.clone();
HierarchicalConfiguration<ImmutableNode> sub2 =
copy.configurationAt("element2.subelement", true);
// This must not cause a validate operation on sub1, but on sub2
copy.clearTree("element2");
assertTrue("Sub2 not detached", isDetached(sub2));
assertFalse("Sub 1 was detached", isDetached(sub));
}
/**
* Tests whether subset() is correctly synchronized.
*/
@Test
public void testSubsetSynchronized()
{
Configuration subset = config.subset("test");
sync.verify(Methods.BEGIN_READ, Methods.END_READ);
assertSame("Wrong Synchronizer", sync, subset.getSynchronizer());
}
/**
* Tests that access to an initialized configuration's sub configurations is
* possible without a special synchronizer.
*/
@Test
public void testReadOnlyAccessToSubConfigurations()
throws ConfigurationException
{
FileBasedConfigurationBuilder<XMLConfiguration> builder =
new FileBasedConfigurationBuilder<XMLConfiguration>(
XMLConfiguration.class);
builder.configure(new Parameters().fileBased().setFile(testFile));
config = builder.getConfiguration();
CountDownLatch startLatch = new CountDownLatch(1);
Collection<SubNodeAccessThread> threads =
new ArrayList<SubNodeAccessThread>();
for (int i = 0; i < 4; i++)
{
SubNodeAccessThread t =
new SubNodeAccessThread(config, startLatch, "element2",
"subelement.subsubelement");
t.start();
threads.add(t);
}
for (int i = 0; i < 4; i++)
{
SubNodeAccessThread t =
new SubNodeAccessThread(config, startLatch,
"element2.subelement", "subsubelement");
t.start();
threads.add(t);
}
startLatch.countDown();
for (SubNodeAccessThread t : threads)
{
t.verify();
}
}
/**
* A thread class for testing concurrent access to SubNode configurations.
*/
private static 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(HierarchicalConfiguration<ImmutableNode> c,
CountDownLatch startLatch, String keySubConfig,
String keyProperty)
{
config = c;
latch = startLatch;
keySub = keySubConfig;
keyProp = keyProperty;
}
@Override
public void run()
{
try
{
latch.await();
HierarchicalConfiguration<ImmutableNode> subConfig =
config.configurationAt(keySub, true);
value = subConfig.getString(keyProp);
}
catch (InterruptedException iex)
{
// ignore
}
}
/**
* Verifies that the correct value was read.
*/
public void verify()
{
try
{
join();
}
catch (InterruptedException e)
{
fail("Waiting was interrupted: " + e);
}
assertEquals("Wrong value", "I'm complex!", value);
}
}
}