org.eclipse.smarthome.config.dispatch.test.ConfigDispatcherOSGiTest.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.smarthome.config.dispatch.test.ConfigDispatcherOSGiTest.java

Source

/**
 * Copyright (c) 2014,2018 Contributors to the Eclipse Foundation
 *
 * See the NOTICE file(s) distributed with this work for additional
 * information regarding copyright ownership.
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 */
package org.eclipse.smarthome.config.dispatch.test;

import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.assertThat;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.List;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.eclipse.smarthome.config.core.ConfigConstants;
import org.eclipse.smarthome.config.dispatch.internal.ConfigDispatcher;
import org.eclipse.smarthome.test.java.JavaOSGiTest;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;

public class ConfigDispatcherOSGiTest extends JavaOSGiTest {

    @Rule
    public TemporaryFolder tmpBaseFolder = new TemporaryFolder();

    private ConfigurationAdmin configAdmin;

    private ConfigDispatcher cd;

    private static final String CONFIGURATION_BASE_DIR = "configurations";
    private static final String SEP = File.separator;

    private static String defaultConfigFile;

    private Configuration configuration;
    private static String configBaseDirectory;

    @BeforeClass
    public static void setUpClass() {
        // Store the default values in order to restore them after all the tests are finished.
        defaultConfigFile = System.getProperty(ConfigDispatcher.SERVICECFG_PROG_ARGUMENT);
    }

    @Before
    public void setUp() throws Exception {
        configBaseDirectory = tmpBaseFolder.getRoot().getAbsolutePath();
        FileUtils.copyDirectory(new File(CONFIGURATION_BASE_DIR), new File(configBaseDirectory));

        configAdmin = getService(ConfigurationAdmin.class);
        assertThat(configAdmin, is(notNullValue()));

        cd = new ConfigDispatcher();
        cd.setConfigurationAdmin(configAdmin);
    }

    @After
    public void tearDown() throws Exception {
        // Clear the configuration with the current pid from the persistent store.
        if (configuration != null) {
            configuration.delete();
        }
    }

    @AfterClass
    public static void tearDownClass() {
        // Set the system properties to their initial values.
        setSystemProperty(ConfigDispatcher.SERVICECFG_PROG_ARGUMENT, defaultConfigFile);
    }

    @Test
    public void allConfigurationFilesWithLocalPIDsAreProcessedAndConfigurationIsUpdated() {
        String configDirectory = configBaseDirectory + SEP + "local_pid_conf";
        String servicesDirectory = "local_pid_services";

        String defaultConfigFileName = configDirectory + SEP + "local.pid.default.file.cfg";

        initialize(defaultConfigFileName);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        // Assert that a file with local pid from the root configuration directory is processed.
        verifyValueOfConfigurationProperty("local.default.pid", "default.property", "default.value");

        // Assert that a file with local pid from the services directory is processed.
        verifyValueOfConfigurationProperty("local.service.first.pid", "service.property", "service.value");

        // Assert that more than one file with local pid from the services directory is processed.
        verifyValueOfConfigurationProperty("local.service.second.pid", "service.property", "service.value");
    }

    private String getAbsoluteConfigDirectory(String configDirectory, String servicesDirectory) {
        return configDirectory + SEP + servicesDirectory;
    }

    @Test
    public void whenTheConfigurationFileNameContainsDotAndNoPIDIsDefinedTheFileNameBecomesPID() {
        String configDirectory = configBaseDirectory + SEP + "no_pid_conf";
        String servicesDirectory = "no_pid_services";
        // In this test case we need configuration files, which names contain dots.
        String defaultConfigFilePath = configDirectory + SEP + "no.pid.default.file.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /* Assert that the configuration is updated with the name of the file in the root directory as a pid. */
        verifyValueOfConfigurationProperty("no.pid.default.file", "default.property", "default.value");

        /* Assert that the configuration is updated with the name of the file in the services directory as a pid. */
        verifyValueOfConfigurationProperty("no.pid.service.file", "service.property", "service.value");
    }

    @Test
    public void IfNoPIDIsDefinedInConfigurationFileWithNo_dotNameTheDefaultNamespacePlusTheFileNameBecomesPID() {
        String configDirectory = configBaseDirectory + SEP + "no_pid_no_dot_conf";
        String servicesDirectory = "no_pid_no_dot_services";
        // In this test case we need configuration files, which names don't contain dots.
        String defaultConfigFilePath = configDirectory + SEP + "default.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /*
         * Assert that the configuration is updated with the default
         * namespace the no-dot name of the file in the root directory as pid.
         */
        verifyValueOfConfigurationProperty(ConfigDispatcher.SERVICE_PID_NAMESPACE + ".default",
                "no.dot.default.property", "no.dot.default.value");

        /*
         * Assert that the configuration is updated with the default namespace
         * the no-dot name of the file in the services directory as pid.
         */
        verifyValueOfConfigurationProperty(ConfigDispatcher.SERVICE_PID_NAMESPACE + ".service",
                "no.dot.service.property", "no.dot.service.value");
    }

    @Test
    public void whenLocalPIDDoesNotContainDotTheDefaultNamespacePlusThatPIDIsTheOverallPID() {
        String configDirectory = configBaseDirectory + SEP + "local_pid_no_dot_conf";
        String servicesDirectory = "local_pid_no_dot_services";
        String defaultConfigFilePath = configDirectory + SEP + "local.pid.no.dot.default.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /*
         * Assert that the configuration is updated with the default namespace
         * the no-dot pid from the file in the the root directory.
         */
        verifyValueOfConfigurationProperty(ConfigDispatcher.SERVICE_PID_NAMESPACE + ".default", "default.property",
                "default.value");

        /*
         * Assert that the configuration is updated with the default namespace
         * the no-dot pid from the file in the the services directory.
         */
        verifyValueOfConfigurationProperty(ConfigDispatcher.SERVICE_PID_NAMESPACE + ".service", "service.property",
                "service.value");
    }

    @Test
    public void whenLocalPIDIsAnEmptyStringTheDefaultNamespacePlusDotIsTheOverallPID() {
        // This test case is a corner case for the above test.
        String configDirectory = configBaseDirectory + SEP + "local_pid_empty_conf";
        String servicesDirectory = "local_pid_empty_services";
        String defaultConfigFilePath = configDirectory + SEP + "local.pid.empty.default.file.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        verifyValueOfConfigurationProperty(ConfigDispatcher.SERVICE_PID_NAMESPACE + ".", "default.property",
                "default.value");

        verifyValueOfConfigurationProperty(ConfigDispatcher.SERVICE_PID_NAMESPACE + ".", "service.property",
                "service.value");
    }

    @Test
    public void valueIsStillValidIfItIsLeftEmpty() {
        String configDirectory = configBaseDirectory + SEP + "local_pid_no_value_conf";
        String servicesDirectory = "local_pid_no_value_services";
        String defaultConfigFilePath = configDirectory + SEP + "local.pid.no.value.default.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /*
         * Assert that the configuration is updated with
         * an empty value for a file in the root directory.
         */
        verifyValueOfConfigurationProperty("default.pid", "default.property", "");

        /*
         * Assert that the configuration is updated with
         * an empty value for a file in the services directory.
         */
        verifyValueOfConfigurationProperty("service.pid", "service.property", "");
    }

    @Test
    public void ifPropertyIsLeftEmptyAConfigurationWithTheGivenPIDWillNotExist() {
        String configDirectory = configBaseDirectory + SEP + "local_pid_no_property_conf";
        String servicesDirectory = "local_pid_no_property_services";
        String defaultConfigFilePath = configDirectory + SEP + "local.pid.no.property.default.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /*
         * Assert that the configuration is not updated with an
         * empty-string property from the file in the root directory.
         */
        verifyNoPropertiesForConfiguration("no.property.default.pid");

        /*
         * Assert that the configuration is not updated with an
         * empty-string property from the file in the services directory.
         */
        verifyNoPropertiesForConfiguration("no.property.service.pid");
    }

    @Test
    public void propertiesForLocalPIDCanBeOverridden() {
        String configDirectory = configBaseDirectory + SEP + "local_pid_conflict_conf";
        String servicesDirectory = "local_pid_conflict_services";
        String defaultConfigFilePath = configDirectory + SEP + "local.pid.conflict.default.file.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /*
         * Assert that the second processed property from the
         * file in the root directory overrides the first one.
         */
        verifyValueOfConfigurationProperty("same.default.local.pid", "default.property", "default.value2");

        /*
         * Assert that the second processed property from the file
         * in the services directory overrides the first one.
         */
        verifyValueOfConfigurationProperty("same.service.local.pid", "service.property", "service.value2");
    }

    @Test
    public void commentLinesAreNotProcessed() {
        String configDirectory = configBaseDirectory + SEP + "comments_conf";
        String servicesDirectory = "comments_services";
        String defaultConfigFilePath = configDirectory + SEP + "comments.default.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /*
         * Assert that the configuration is not updated with a
         * pid from a comment from a file in the root directory.
         */
        verifyNoPropertiesForConfiguration("comment.default.pid");

        /*
         * Assert that the configuration is not updated with a
         * pid from a comment from a file in the services directory.
         */
        verifyNoPropertiesForConfiguration("comment.service.pid");
    }

    @Test
    public void txtFilesAreNotProcessed() {
        String configDirectory = configBaseDirectory + SEP + "txt_conf";
        String servicesDirectory = "txt_services";
        String defaultConfigFilePath = configDirectory + SEP + "txt.default.txt";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /*
         * Assert that the configuration is not updated with
         * pid from a txt file in the root directory.
         */
        verifyNoPropertiesForConfiguration("txt.default.pid");

        /*
         * Assert that the configuration is not updated with
         * pid from a txt file in the services directory.
         */
        verifyNoPropertiesForConfiguration("txt.service.pid");
    }

    @Test
    public void allConfigurationFilesWithGlobalPIDsAreProcessedAndConfigurationIsUpdated() {
        String configDirectory = configBaseDirectory + SEP + "global_pid_conf";
        String servicesDirectory = "global_pid_services";
        String defaultConfigFilePath = configDirectory + SEP + "global.pid.default.file.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        // Assert that a file with global pid from the root configuration directory is processed.
        verifyValueOfConfigurationProperty("global.default.pid", "default.property", "default.value");

        // Assert that a file with global pid from the services directory is processed.
        verifyValueOfConfigurationProperty("global.service.first.pid", "service.property", "service.value");

        // Assert that more than one file with global pid from the services directory is processed.
        verifyValueOfConfigurationProperty("global.service.second.pid", "service.property", "service.value");
    }

    @Test
    public void ifThePropertyValuePairIsPrefixedWithLocalPIDInTheSameFileTheGlobalPIDIsIgnored() {
        String configDirectory = configBaseDirectory + SEP + "ignored_global_pid_conf";
        String servicesDirectory = "ignored_global_pid_services";
        String defaultConfigFilePath = configDirectory + SEP + "ignored.global.default.pid.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        // Assert that the configuration is not updated with the global pid in the root directory.
        verifyNoPropertiesForConfiguration("ignored.global.default.pid");

        // Assert that the configuration is updated with the local pid in the root directory.
        verifyValueOfConfigurationProperty("not.ignored.local.default.pid", "local.default.property",
                "local.default.value");

        // Assert that the configuration is not updated with the global pid in the services directory.
        verifyNoPropertiesForConfiguration("ignored.global.service.pid");

        // Assert that the configuration is updated with the local pid in the services directory.
        verifyValueOfConfigurationProperty("not.ignored.local.service.pid", "local.service.property",
                "local.service.value");
    }

    @Test
    public void ifThePropertyIsNotPrefixedWithLocalPIDTheLastPIDBecomesPIDForThatProperty() {
        String configDirectory = configBaseDirectory + SEP + "last_pid_conf";
        String servicesDirectory = "last_pid_services";
        String defaultConfigFilePath = configDirectory + SEP + "first.global.default.pid.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /*
         * Assert that a property=value pair is associated with the global pid,
         * defined in the above line in a file in the root directory
         * rather than with the local pid, defined in the below line.
         */
        verifyValueOfConfigurationProperty("first.global.default.pid", "global.default.property1",
                "global.default.value1");
        verifyNotExistingConfigurationProperty("last.local.default.pid", "global.default.property1");

        /*
         * Assert that a property=value pair is associated with the last defined local pid,
         * rather than with the global pid, defined in the first line of a file in the root directory.
         */
        verifyValueOfConfigurationProperty("last.local.default.pid", "global.default.property2",
                "global.default.value2");
        verifyNotExistingConfigurationProperty("first.global.default.pid", "global.default.property2");

        /*
         * Assert that a property=value pair is associated with the global pid,
         * defined in the above line in a file in the services directory
         * rather than with the local pid, defined in the below line.
         */
        verifyValueOfConfigurationProperty("first.global.service.pid", "global.service.property1",
                "global.service.value1");
        verifyNotExistingConfigurationProperty("last.local.default.pid", "global.service.property1");

        /*
         * Assert that a property=value pair is associated with the last defined local pid,
         * rather than with the global pid, defined in the first line of a file in the services directory.
         */
        verifyValueOfConfigurationProperty("last.local.service.pid", "global.service.property2",
                "global.service.value2");
        verifyNotExistingConfigurationProperty("first.global.service.pid", "global.service.property2");
    }

    @Test
    public void ifThereIsNoPropertyValuePairForGlobalPIDAConfigurationWithTheGivenPIDWillNotExist() {
        String configDirectory = configBaseDirectory + SEP + "global_pid_no_pair_conf";
        String servicesDirectory = "global_pid_no_pair_services";
        String defaultConfigFilePath = configDirectory + SEP + "global.pid.no.pair.default.file.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /*
         * Assert that pid with no property=value pair
         * is not processed in a file in the root directory.
         */
        verifyNoPropertiesForConfiguration("no.pair.default.pid");

        /*
         * Assert that pid with no property=value pair
         * is not processed in a file in the services directory.
         */
        verifyNoPropertiesForConfiguration("no.pair.service.pid");
    }

    @Test
    public void whenGlobalPIDIsEmptyStringItRemainsAnEmptyString() {
        String configDirectory = configBaseDirectory + SEP + "global_pid_empty_conf";
        String servicesDirectory = "global_pid_empty_services";

        initialize(null);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        // Assert that an empty-string pid from a file in the services directory is processed.
        verifyValueOfConfigurationProperty("", "global.service.property", "global.service.value");
    }

    @Test
    public void PropertyValuePairsForALocalPIDInDifferentFilesAreStillAssociatedWithThatPID() {
        String configDirectory = configBaseDirectory + SEP + "local_pid_different_files_no_conflict_conf";
        String servicesDirectory = "local_pid_different_files_no_conflict_services";
        String defaultConfigFilePath = configDirectory + SEP + "local.pid.default.file.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /*
         * Assert that the configuration is updated with all the property=value
         * pairs for the same local pid from all the processed files.
         */
        verifyValueOfConfigurationProperty("local.pid", "first.property", "first.value");
        verifyValueOfConfigurationProperty("local.pid", "second.property", "second.value");
        verifyValueOfConfigurationProperty("local.pid", "third.property", "third.value");
        verifyValueOfConfigurationProperty("local.pid", "fourth.property", "fourth.value");
    }

    @Test
    public void propertiesForTheSameLocalPIDInDifferentFilesMustBeOverriddenInTheOrderTheyWereLastModified()
            throws Exception {
        String configDirectory = configBaseDirectory + SEP + "local_pid_different_files_conflict_conf";
        String servicesDirectory = "local_pid_different_files_conflict_services";
        String defaultConfigFilePath = configDirectory + SEP + "local.pid.default.file.cfg";
        String lastModifiedFileName = "last.modified.service.file.cfg";

        /*
         * Every file from servicesDirectory contains this property, but with different value.
         * The value for this property in the last processed file must override the previous
         * values for the same property in the configuration.
         */
        String conflictProperty = "property";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        // Modify this file, so that we are sure it is the last modified file in servicesDirectory.
        File fileToModify = new File(configDirectory + SEP + servicesDirectory + SEP + lastModifiedFileName);
        FileUtils.touch(fileToModify);
        cd.processConfigFile(fileToModify);

        String value = getLastModifiedValueForPoperty(
                getAbsoluteConfigDirectory(configDirectory, servicesDirectory), conflictProperty);

        /*
         * Assert that the property for the same local pid in the last modified file
         * has overridden the other properties for that pid from previously modified files.
         */
        verifyValueOfConfigurationProperty("local.conflict.pid", conflictProperty, value);
    }

    @Test
    public void whenPropertyValuePairsForAGlobalPIDAreInDifferentFilesPropertiesWillNotBeMerged()
            throws IOException {
        String configDirectory = configBaseDirectory + SEP + "global_pid_different_files_no_merge_conf";
        String servicesDirectory = "global_pid_different_files_no_merge_services";

        initialize(null);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        // Modify this file, so that we are sure it is the last modified file
        File lastModified = new File(
                configDirectory + SEP + servicesDirectory + SEP + "global.pid.service.c.file.cfg");
        FileUtils.touch(lastModified);
        cd.processConfigFile(lastModified);

        /*
         * Assert that the configuration is updated only with the property=value
         * pairs which are parsed last:
         */
        verifyNotExistingConfigurationProperty("different.files.global.pid", "first.property");
        verifyNotExistingConfigurationProperty("different.files.global.pid", "second.property");
        verifyValueOfConfigurationProperty("different.files.global.pid", "third.property", "third.value");
    }

    @Test
    public void whenLocalPIDIsDefinedForGlobalPIDFile_AbortParsing() {
        String configDirectory = configBaseDirectory + SEP + "global_pid_with_local_pid_line_error";
        String servicesDirectory = "global_pid_with_local_pid_line_services_error";

        initialize(null);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /*
         * Assert that the configuration is updated only with the property=value
         * pairs which are parsed last:
         */
        verifyNotExistingConfiguration("global.service.pid");
    }

    @Test
    public void whenContextExistsInExlusivePIDCreateMultipleServices() throws IOException {
        String configDirectory = configBaseDirectory + SEP + "multiple_service_contexts";
        String servicesDirectory = "multiple_contexts";

        initialize(null);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        verifyValueOfConfigurationPropertyWithContext("service.pid#ctx1", "property1", "value1");
        verifyValueOfConfigurationPropertyWithContext("service.pid#ctx2", "property1", "value2");
    }

    @Test
    public void whenContextExistsInExlusivePIDCreateMultipleServicesUpdateWithDuplicate() throws IOException {
        String configDirectory = configBaseDirectory + SEP + "multiple_service_contexts_duplicates";
        String servicesDirectory = "multiple_contexts";

        initialize(null);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        File srcDupFile = new File(configDirectory + SEP + "duplicate" + SEP + "service-ctx1duplicate.cfg");

        cd.processConfigFile(srcDupFile);

        // only ctx1 is overwritten by service-ctx1duplicate.cfg
        verifyValueOfConfigurationPropertyWithContext("service.pid#ctx1", "property1", "valueDup");
        verifyValueOfConfigurationPropertyWithContext("service.pid#ctx2", "property1", "value2");
    }

    @Test
    public void whenContextExistsInExlusivePIDCreateMultipleServicesAndDeleteOneOfThem() throws IOException {
        String configDirectory = configBaseDirectory + SEP + "multiple_service_contexts";
        String servicesDirectory = "multiple_contexts";

        initialize(null);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        verifyValueOfConfigurationPropertyWithContext("service.pid#ctx1", "property1", "value1");
        verifyValueOfConfigurationPropertyWithContext("service.pid#ctx2", "property1", "value2");

        File serviceConfigFile = new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory),
                "service-ctx1.cfg");

        cd.fileRemoved(serviceConfigFile.getAbsolutePath());

        Configuration c1 = getConfigurationWithContext("service.pid#ctx1");
        assertThat(c1, is(nullValue()));

        Configuration c2 = getConfigurationWithContext("service.pid#ctx2");
        assertThat(c2.getProperties().get("property1"), is("value2"));
    }

    @Test
    public void whenExclusivePIDFileIsDeleted_DeleteTheConfiguration() throws IOException {
        String configDirectory = configBaseDirectory + SEP + "exclusive_pid_file_removed_during_runtime";
        String servicesDirectory = "exclusive_pid_file_removed_during_runtime_services";

        initialize(null);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        verifyValueOfConfigurationProperty("global.service.pid", "property1", "value1");

        String pid = "service.pid.cfg";
        File serviceConfigFile = new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory), pid);

        cd.fileRemoved(serviceConfigFile.getAbsolutePath());

        waitForAssert(() -> {
            try {
                configuration = configAdmin.getConfiguration(pid);
            } catch (IOException e) {
                throw new IllegalArgumentException(
                        "IOException occured while retrieving configuration for pid " + pid, e);
            }
            assertThat(configuration.getProperties(), is(nullValue()));
        });
    }

    @Test
    public void whenExclusiveConfigIsTruncated_OverrideReducedConfig() throws IOException {
        String configDirectory = configBaseDirectory + SEP + "exclusive_pid_overrides_configuration_on_update";
        String servicesDirectory = "exclusive_pid_overrides_configuration_on_update_services";

        File serviceConfigFile = new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory),
                "service.pid.cfg");

        initialize(null);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /*
         * Assert that the configuration is updated with all properties:
         */
        verifyValueOfConfigurationProperty("global.service.pid", "property1", "value1");
        verifyValueOfConfigurationProperty("global.service.pid", "property2", "value2");

        truncateLastLine(serviceConfigFile);

        cd.processConfigFile(serviceConfigFile);

        /*
         * Assert that the configuration is updated without the truncated property/value pair:
         */
        verifyValueOfConfigurationProperty("global.service.pid", "property1", "value1");
        verifyNotExistingConfigurationProperty("global.service.pid", "property2");
    }

    @Test
    public void whenExclusiveConfigFileIsDeleted_shouldRemoveConfigFromConfigAdmin() throws IOException {
        String configDirectory = configBaseDirectory + SEP
                + "exclusive_pid_configuration_removed_after_file_delete";
        String servicesDirectory = "exclusive_pid_configuration_removed_after_file_delete_services";

        File serviceConfigFile = new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory),
                "service.pid.cfg");

        initialize(null);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /*
         * Assert that the configuration is updated with all properties:
         */
        String pid = "global.service.pid";
        verifyValueOfConfigurationProperty(pid, "property1", "value1");
        verifyValueOfConfigurationProperty(pid, "property2", "value2");

        // remember the file content and delete the file:
        cd.fileRemoved(serviceConfigFile.getAbsolutePath());

        /*
         * Assert that the configuration was deleted from configAdmin
         */
        waitForAssert(() -> {
            try {
                configuration = configAdmin.getConfiguration(pid);
            } catch (IOException e) {
                throw new IllegalArgumentException(
                        "IOException occured while retrieving configuration for pid " + pid, e);
            }
            assertThat(configuration.getProperties(), is(nullValue()));
        });
    }

    @Test
    public void propertiesForTheSameGlobalPIDInDifferentFilesMustBeOverriddenInTheOrderTheyWereLastModified()
            throws Exception {
        String configDirectory = configBaseDirectory + SEP + "global_pid_different_files_conflict_conf";
        String servicesDirectory = "global_pid_different_files_conflict_services";
        String defaultConfigFilePath = configDirectory + SEP + "global.pid.default.file.cfg";
        String lastModifiedFileName = "global.pid.last.modified.service.file.cfg";

        /*
         * Every file from servicesDirectory contains this property, but with different value.
         * The value for this property in the last processed file will override the previous
         * values for the same property in the configuration.
         */
        String conflictProperty = "property";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        // Modify this file, so that we are sure it is the last modified file in servicesDirectory.
        File fileToModify = new File(configDirectory + SEP + servicesDirectory + SEP + lastModifiedFileName);
        FileUtils.touch(fileToModify);
        cd.processConfigFile(fileToModify);

        String value = getLastModifiedValueForPoperty(
                getAbsoluteConfigDirectory(configDirectory, servicesDirectory), conflictProperty);

        /*
         * Assert that the property for the same global pid in the last modified file
         * has overridden the other properties for that pid from previously modified files.
         */
        verifyValueOfConfigurationProperty("different.files.global.pid", conflictProperty, value);
    }

    @Test
    public void whenExclusivePIDisDefinedInlineFromDifferentFile_skipTheLine() {
        String configDirectory = configBaseDirectory + SEP + "global_and_local_pid_different_files_conf";
        String servicesDirectory = "global_and_local_pid_no_conflict_services";
        String defaultConfigFilePath = configDirectory + SEP + "global.and.local.pid.default.file.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        // Assert that the configuration is updated with all the properties for a pid from all the processed files.
        verifyValueOfConfigurationProperty("no.conflict.global.and.local.pid", "exclusive.property",
                "global.value");
        verifyNotExistingConfigurationProperty("no.conflict.global.and.local.pid", "inline.property");
    }

    @Test
    public void localPIDIsOverriddenByGlobalPIDInDifferentFileIfTheFileWithTheGlobalOneIsModifiedLast()
            throws Exception {
        String configDirectory = configBaseDirectory + SEP + "global_and_local_pid_different_files_conf";
        String servicesDirectory = "global_and_local_pid_overridden_local_services";
        String defaultConfigFilePath = configDirectory + SEP + "global.and.local.pid.default.file.cfg";
        String lastModifiedFileName = "a.overriding.global.pid.service.file.cfg";

        /*
         * Both files(with local and global pid) contain this property, but with different value.
         * The value for this property in the last processed file must override the previous
         * values for the same property in the configuration.
         */
        String conflictProperty = "global.and.local.property";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        // Modify this file, so that we are sure it is the last modified file in servicesDirectory.
        File fileToModify = new File(configDirectory + SEP + servicesDirectory + SEP + lastModifiedFileName);
        FileUtils.touch(fileToModify);
        cd.processConfigFile(fileToModify);

        String value = getLastModifiedValueForPoperty(
                getAbsoluteConfigDirectory(configDirectory, servicesDirectory), conflictProperty);

        /*
         * Assert that the global pid from the last modified file
         * has overridden the local pid from previously processed file.
         */
        verifyValueOfConfigurationProperty("overridden.local.pid", conflictProperty, value);
    }

    @Test
    public void globalPIDIsOverriddenByLocalPIDInDifferentFileIfTheFileWithTheLocalOneIsModifiedLast()
            throws Exception {
        String configDirectory = configBaseDirectory + SEP + "global_and_local_pid_different_files_conf";
        String servicesDirectory = "global_and_local_pid_overridden_global_services";
        String defaultConfigFilePath = configDirectory + SEP + "global.and.local.pid.default.file.cfg";
        String lastModifiedFileName = "a.overriding.local.pid.service.file.cfg";

        /*
         * Both files(with local and global pid) contain this property, but with different value.
         * The value for this property in the last processed file must override the previous
         * values for the same property in the configuration.
         */
        String conflictProperty = "global.and.local.property";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        // Modify this file, so that we are sure it is the last modified file in servicesDirectory.
        File fileToModify = new File(configDirectory + SEP + servicesDirectory + SEP + lastModifiedFileName);
        FileUtils.touch(fileToModify);
        cd.processConfigFile(fileToModify);

        String value = getLastModifiedValueForPoperty(
                getAbsoluteConfigDirectory(configDirectory, servicesDirectory), conflictProperty);

        /*
         * Assert that the local pid from the last modified file
         * has overridden the global pid from previously processed file.
         */
        verifyValueOfConfigurationProperty("overridden.global.pid", conflictProperty, value);
    }

    @Test
    public void propertiesForPIDInFilesInServicesDirectoryMustOverrideDefaultProperties() {
        String configDirectory = configBaseDirectory + SEP + "default_vs_services_files_conf";
        String servicesDirectory = "default_vs_services_files_services";
        String defaultConfigFilePath = configDirectory + SEP + "default.file.cfg";

        initialize(defaultConfigFilePath);

        cd.processConfigFile(new File(getAbsoluteConfigDirectory(configDirectory, servicesDirectory)));

        /*
         * Assert that a file from the services directory is processed
         * after a file from the root directory, therefore the configuration
         * is updated with properties from the file in the services directory.
         */
        verifyValueOfConfigurationProperty("default.and.service.global.pid", "property1", "value1.2");
        verifyValueOfConfigurationProperty("default.and.service.local.pid", "property2", "value2.2");
    }

    private void initialize(String defaultConfigFile) {
        setSystemProperty(ConfigDispatcher.SERVICECFG_PROG_ARGUMENT, defaultConfigFile);

        cd.activate(bundleContext);
    }

    private Configuration getConfigurationWithContext(String pidWithContext) {
        String pid = null;
        String configContext = null;
        if (pidWithContext.contains(ConfigConstants.SERVICE_CONTEXT_MARKER)) {
            pid = pidWithContext.split(ConfigConstants.SERVICE_CONTEXT_MARKER)[0];
            configContext = pidWithContext.split(ConfigConstants.SERVICE_CONTEXT_MARKER)[1];
        } else {
            throw new IllegalArgumentException("PID does not have a context");
        }
        Configuration[] configs = null;
        try {
            configs = configAdmin.listConfigurations("(&(service.factoryPid=" + pid + ")("
                    + ConfigConstants.SERVICE_CONTEXT + "=" + configContext + "))");
        } catch (IOException e) {
            throw new IllegalArgumentException(
                    "IOException occured while retrieving configuration for pid " + pidWithContext, e);
        } catch (InvalidSyntaxException e) {
            throw new IllegalArgumentException(
                    "InvalidSyntaxException occured while retrieving configuration for pid " + pidWithContext, e);
        }

        if (configs == null) {
            return null;
        }

        assertThat(configs.length, is(1));

        return configs[0];
    }

    private void verifyValueOfConfigurationPropertyWithContext(String pidWithContext, String property,
            String value) {
        waitForAssert(() -> {
            Configuration configuration = getConfigurationWithContext(pidWithContext);

            assertThat(configuration, is(notNullValue()));
            assertThat(configuration.getProperties(), is(notNullValue()));
            assertThat(configuration.getProperties().get(property), is(equalTo(value)));
        });
    }

    private void verifyValueOfConfigurationProperty(String pid, String property, String value) {
        waitForAssert(() -> {
            try {
                configuration = configAdmin.getConfiguration(pid);
            } catch (IOException e) {
                throw new IllegalArgumentException(
                        "IOException occured while retrieving configuration for pid " + pid, e);
            }
            assertThat(configuration, is(notNullValue()));
            assertThat(configuration.getProperties(), is(notNullValue()));
            assertThat(configuration.getProperties().get(property), is(equalTo(value)));
        });
    }

    private void verifyNotExistingConfiguration(String pid) {
        /*
         * If a property is not present in the configuration's properties,
         * configuration.getProperties().get(property) should return null.
         *
         * Sending events, related to modification of file, is a OS specific action.
         * So when we check if a configuration is updated, we use separate waitForAssert-s
         * in order to be sure that the events are processed before the assertion.
         */
        waitForAssert(() -> {
            try {
                configuration = configAdmin.getConfiguration(pid);
            } catch (IOException e) {
                throw new IllegalArgumentException(
                        "IOException occured while retrieving configuration for pid " + pid, e);
            }
            assertThat(configuration, is(notNullValue()));
            assertThat(configuration.getProperties(), is(nullValue()));
        });
    }

    private void verifyNotExistingConfigurationProperty(String pid, String property) {
        /*
         * If a property is not present in the configuration's properties,
         * configuration.getProperties().get(property) should return null.
         *
         * Sending events, related to modification of file, is a OS specific action.
         * So when we check if a configuration is updated, we use separate waitForAssert-s
         * in order to be sure that the events are processed before the assertion.
         */
        waitForAssert(() -> {
            try {
                configuration = configAdmin.getConfiguration(pid);
            } catch (IOException e) {
                throw new IllegalArgumentException(
                        "IOException occured while retrieving configuration for pid " + pid, e);
            }
            assertThat(configuration, is(notNullValue()));
            assertThat(configuration.getProperties(), is(notNullValue()));
            assertThat(configuration.getProperties().get(property), is(nullValue()));
        });
    }

    private void verifyNoPropertiesForConfiguration(String pid) {
        // We have to wait for all the files to be processed and the configuration to be updated.
        waitForAssert(() -> {
            try {
                configuration = configAdmin.getConfiguration(pid);
            } catch (IOException e) {
                throw new IllegalArgumentException(
                        "IOException occured while retrieving configuration for pid " + pid, e);
            }
            assertThat(configuration.getProperties(), is(nullValue()));
        });
    }

    private void truncateLastLine(File file) throws IOException {
        List<String> lines = IOUtils.readLines(new FileInputStream(file));
        IOUtils.writeLines(lines.subList(0, lines.size() - 1), "\n", new FileOutputStream(file));
    }

    private String getLastModifiedValueForPoperty(String path, String property) {
        // This method will return null, if there are no files in the directory.
        String value = null;

        File file = getLastModifiedFileFromDir(path);
        if (file == null) {
            return null;
        }
        try (FileInputStream fileInputStream = new FileInputStream(file)) {
            List<String> lines = IOUtils.readLines(fileInputStream);
            for (String line : lines) {
                if (line.contains(property + "=")) {
                    value = StringUtils.substringAfter(line, property + "=");
                }
            }
        } catch (IOException e) {
            throw new IllegalArgumentException(
                    "An exception " + e + " was thrown, while reading from file " + file.getPath(), e);
        }

        return value;
    }

    /*
     * Mainly used the code, provided in
     * http://stackoverflow.com/questions/2064694/how-do-i-find-the-last-modified-file-in-a-directory-in-java
     */
    private File getLastModifiedFileFromDir(String dirPath) {
        File dir = new File(dirPath);
        File[] files = dir.listFiles();
        if (files == null || files.length == 0) {
            return null;
        }

        File lastModifiedFile = files[0];
        for (int i = 1; i < files.length; i++) {
            if (lastModifiedFile.lastModified() < files[i].lastModified()) {
                lastModifiedFile = files[i];
            }
        }
        return lastModifiedFile;
    }

    private static void setSystemProperty(String systemProperty, String initialValue) {
        if (initialValue != null) {
            System.setProperty(systemProperty, initialValue);
        } else {
            /* A system property cannot be set to null, it has to be cleared */
            System.clearProperty(systemProperty);
        }
    }
}