org.apache.ace.configurator.ConfiguratorTest.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.ace.configurator.ConfiguratorTest.java

Source

/*
 * 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.ace.configurator;

import static org.apache.ace.test.utils.TestUtils.UNIT;
import static org.testng.Assert.assertEquals;
import static org.testng.Assert.assertNotNull;
import static org.testng.Assert.assertTrue;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Dictionary;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

import org.apache.ace.test.utils.FileUtils;
import org.apache.ace.test.utils.TestUtils;
import org.osgi.framework.BundleContext;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.log.LogService;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

public class ConfiguratorTest {
    private Configurator m_configurator;
    private File m_configDir;
    private MockConfigAdmin m_configAdmin;

    private volatile CountDownLatch m_deleteLatch;
    private volatile CountDownLatch m_updateLatch;

    @Test(groups = { UNIT })
    public void testAddConfiguration() throws Exception {
        String pid = "test-add";

        Properties initialConfiguration = createProperties();
        saveConfiguration(pid, initialConfiguration);

        Dictionary<String, ?> configuration = getAndWaitForConfigurationUpdate(pid);
        assertNotNull(configuration, "No configuration received from configurator");
        assertEquals(createProperties(), configuration, "Configuration content is unexpected");
    }

    @Test(groups = { UNIT })
    public void testAddFactoryConfiguration() throws Exception {
        String pid = "test-add";
        String factoryPID = "testFactory";

        Properties props = createProperties();
        saveConfiguration(pid, "testFactory", props);

        Dictionary<String, ?> configuration = getAndWaitForConfigurationUpdate(factoryPID);
        assertNotNull(configuration, "No configuration received from configurator");
        assertEquals(configuration.remove("factory.instance.pid"), "testFactory_test-add",
                "Incorrect factory instance pid was added to the configuration");
        assertEquals(createProperties(), configuration, "Configuration content is unexpected");
    }

    // update a configuration, only adding a key (this is allowed in all cases)
    @Test(groups = { UNIT })
    public void testChangeConfigurationUsingNewKey() throws Exception {
        String pid = "test-change";

        Properties initialConfiguration = createProperties();
        saveConfiguration(pid, initialConfiguration);

        Dictionary<String, ?> configuration = getAndWaitForConfigurationUpdate(pid);
        assertNotNull(configuration, "No configuration received from configurator");
        assertEquals(initialConfiguration, configuration);

        initialConfiguration.put("anotherKey", "anotherValue");
        saveConfiguration("test-change", initialConfiguration);

        // now the configuration should be updated
        configuration = getAndWaitForConfigurationUpdate(pid);
        assertNotNull(configuration, "No configuration received from configurator");
        assertEquals(initialConfiguration, configuration);
    }

    // update a configuration, changing an already existing key, not using reconfiguration
    @Test(groups = { UNIT })
    public void testChangeConfigurationUsingSameKeyNoReconfigure() throws Exception {
        String pid = "test-change";

        Properties configurationValues = createProperties();
        Properties initialConfigurationValues = new Properties();
        initialConfigurationValues.putAll(configurationValues);
        saveConfiguration(pid, configurationValues);

        Dictionary<String, ?> configuration = getAndWaitForConfigurationUpdate(pid);
        assertNotNull(configuration, "No configuration received from configurator");
        assertEquals(configurationValues, configuration);

        configurationValues.put("test", "value42");
        saveConfiguration("test-change", configurationValues);

        // The update should have been ignored, and the old values should still be present.
        configuration = getAndWaitForConfigurationUpdate(pid);
        assertNotNull(configuration, "No configuration received from configurator");
        assertEquals(initialConfigurationValues, configuration);
    }

    // update a configuration, changing an already existing key, using reconfiguration
    @Test(groups = { UNIT })
    public void testChangeConfigurationUsingSameKeyWithReconfigure() throws Exception {
        String pid = "test-change";

        setUp(true); // Instruct the configurator to reconfigure
        Properties configurationValues = createProperties();
        saveConfiguration(pid, configurationValues);

        Dictionary<String, ?> configuration = getAndWaitForConfigurationUpdate(pid);
        assertNotNull(configuration, "No configuration received from configurator");
        assertEquals(configurationValues, configuration);

        configurationValues.put("test", "value42");
        saveConfiguration(pid, configurationValues);

        // now the configuration should be updated
        configuration = getAndWaitForConfigurationUpdate(pid);
        assertNotNull(configuration, "No configuration received from configurator");
        assertEquals(configurationValues, configuration);
    }

    @Test(groups = { UNIT })
    public void testPropertySubstitution() throws Exception {
        String pid = "test-subst";

        Properties initial = new Properties();
        initial.put("key1", "leading ${foo.${bar}} middle ${baz} trailing");
        initial.put("bar", "a");
        initial.put("foo.a", "text");
        initial.put("baz", "word");
        // ACE-401: use some weird log4j conversion pattern in our config file, should not confuse the Configurator's
        // substitution algorithm...
        initial.put("key2", "%d{ISO8601} | %-5.5p | %C | %X{bundle.name} | %m%n");
        // unknown and partially unknown variables shouldn't get substituted...
        initial.put("key3", "${qux} ${quu.${bar}} ${baz.${bar}}");
        saveConfiguration(pid, initial);

        Dictionary<String, ?> config = getAndWaitForConfigurationUpdate(pid);
        assertNotNull(config, "No configuration received from configurator");
        assertEquals(config.get("key1"), "leading text middle word trailing", "Substitution failed!");
        assertEquals(config.get("key2"), "%d{ISO8601} | %-5.5p | %C | %X{bundle.name} | %m%n",
                "Substitution failed!");
        assertEquals(config.get("key3"), "${qux} ${quu.${bar}} ${baz.${bar}}", "Substitution failed!");
    }

    @Test(groups = { UNIT })
    public void testPropertySubstitutionFromContext() throws Exception {
        String pid = "test-subst";

        Properties initialConfiguration = createProperties();
        initialConfiguration.put("subst", "${contextProp}");
        saveConfiguration(pid, initialConfiguration);

        Dictionary<String, ?> configuration = getAndWaitForConfigurationUpdate(pid);
        assertNotNull(configuration, "No configuration received from configurator");
        assertEquals(configuration.get("subst"), "contextVal", "Substitution failed");
    }

    // remove a configuration
    @Test(groups = { UNIT })
    public void testRemoveConfiguration() throws Exception {
        String pid = "test-remove";

        Properties initialConfiguration = createProperties();
        saveConfiguration(pid, initialConfiguration);

        Dictionary<String, ?> configuration = getAndWaitForConfigurationUpdate(pid);
        assertNotNull(configuration, "No configuration received from configurator");
        assertEquals(createProperties(), configuration);

        // ok, the configuration is done.
        // now try to remove it.
        removeConfiguration(pid);

        // after some processing time, we should get a message that the configuration is now removed.
        waitForConfigurationDelete(pid);
    }

    // remove a configuration
    @Test(groups = { UNIT })
    public void testRemoveFactoryConfiguration() throws Exception {
        String pid = "test-remove";
        String factoryPID = "testFactory";

        Properties props = createProperties();
        saveConfiguration(pid, factoryPID, props);
        getAndWaitForConfigurationUpdate(factoryPID);

        removeConfiguration(pid, factoryPID);

        // after some processing time, we should get a message that the configuration is now removed.
        waitForConfigurationDelete(factoryPID);
    }

    @BeforeMethod(alwaysRun = true)
    protected void setUp() throws Exception {
        setUp(false);
    }

    /**
     * Sets up the environment for testing.
     * 
     * @param reconfig
     *            Indicates whether or not the configurator should use reconfiguration.
     */
    protected void setUp(boolean reconfig) throws Exception {
        m_configAdmin = new MockConfigAdmin() {
            @Override
            void configDeleted(MockConfiguration config) {
                m_deleteLatch.countDown();
            }

            @Override
            void configUpdated(MockConfiguration config) {
                m_updateLatch.countDown();
            }
        };

        m_configDir = FileUtils.createTempFile(null);
        m_configDir.mkdir();
        m_configurator = new Configurator(m_configDir, 200, reconfig);

        TestUtils.configureObject(m_configurator, ConfigurationAdmin.class, m_configAdmin);
        TestUtils.configureObject(m_configurator, LogService.class);
        TestUtils.configureObject(m_configurator, BundleContext.class,
                TestUtils.createMockObjectAdapter(BundleContext.class, new Object() {
                    @SuppressWarnings("unused")
                    public String getProperty(String key) {
                        if ("contextProp".equals(key)) {
                            return "contextVal";
                        }
                        return null;
                    }
                }));
        m_configurator.start();
    }

    @AfterMethod(alwaysRun = true)
    protected void tearDown() throws Exception {
        m_configurator.stop();
        FileUtils.removeDirectoryWithContent(m_configDir);

        m_deleteLatch = null;
        m_updateLatch = null;
    }

    // set some standard properties for testing
    private Properties createProperties() {
        Properties props = new Properties();
        props.put("test", "value1");
        props.put("test2", "value2");
        return props;
    }

    /**
     * Get the configuration and if it not available yet wait for it. If there is still no configuration after the wait
     * time, null is returned.
     */
    private Dictionary<String, ?> getAndWaitForConfigurationUpdate(String pid) throws Exception {
        assertTrue(m_updateLatch.await(2, TimeUnit.SECONDS));

        return m_configAdmin.getConfiguration(pid).getProperties();
    }

    // remove a created configuration file
    private void removeConfiguration(String servicePid) {
        removeConfiguration(servicePid, null);
    }

    private void removeConfiguration(String servicePid, String factoryPid) {
        if (factoryPid != null) {
            new File(m_configDir, factoryPid + File.separator + servicePid + ".cfg").delete();
        } else {
            new File(m_configDir, servicePid + ".cfg").delete();
        }

        m_deleteLatch = new CountDownLatch(1);
    }

    /**
     * Renames a given source file to a new destination file, using Commons-IO.
     * <p>
     * This avoids the problem mentioned in ACE-155.
     * </p>
     * 
     * @param source
     *            the file to rename;
     * @param dest
     *            the file to rename to.
     */
    private void renameFile(File source, File dest) {
        try {
            org.apache.commons.io.FileUtils.moveFile(source, dest);
        } catch (IOException e) {
            throw new RuntimeException("Failed to rename file!", e);
        }
    }

    /**
     * save the properties into a configuration file the configurator can read. The file is first created and then moved
     * to make sure the configuration doesn't read an empty file
     */
    private void saveConfiguration(String servicePid, Properties configuration) {
        saveConfiguration(servicePid, null, configuration);
    }

    /**
     * save the properties into a configuration file stored in a directory reflecting the factory pid
     */
    private void saveConfiguration(String servicePid, String factoryPid, Properties configuration) {
        OutputStream fileOutputStream = null;
        File outFile = null;
        try {
            outFile = FileUtils.createTempFile(null);
            fileOutputStream = new FileOutputStream(outFile);
            configuration.store(fileOutputStream, null);
        } catch (IOException ioe) {
            // the test will fail, ignore this.
        } finally {
            if (fileOutputStream != null) {
                try {
                    fileOutputStream.close();
                } catch (IOException e) {
                    // nothing we can do
                }
            }
        }
        if (outFile != null) {
            if (factoryPid == null) {
                File dest = new File(m_configDir, servicePid + ".cfg");
                if (dest.exists()) {
                    dest.delete();
                }
                renameFile(outFile, dest);
            } else {
                File file = new File(m_configDir, factoryPid);
                file.mkdirs();
                File dest = new File(file, servicePid + ".cfg");
                if (dest.exists()) {
                    dest.delete();
                }
                renameFile(outFile, dest);
            }
        }

        m_updateLatch = new CountDownLatch(1);
    }

    /**
     * Get the configuration and if it not available yet wait for it. If there is still no configuration after the wait
     * time, null is returned.
     */
    private void waitForConfigurationDelete(String pid) throws Exception {
        assertTrue(m_deleteLatch.await(2, TimeUnit.SECONDS));
    }
}