com.flexive.tests.embedded.ConfigurationTest.java Source code

Java tutorial

Introduction

Here is the source code for com.flexive.tests.embedded.ConfigurationTest.java

Source

/***************************************************************
 *  This file is part of the [fleXive](R) framework.
 *
 *  Copyright (c) 1999-2014
 *  UCS - unique computing solutions gmbh (http://www.ucs.at)
 *  All rights reserved
 *
 *  The [fleXive](R) project is free software; you can redistribute
 *  it and/or modify it under the terms of the GNU Lesser General Public
 *  License version 2.1 or higher as published by the Free Software Foundation.
 *
 *  The GNU Lesser General Public License can be found at
 *  http://www.gnu.org/licenses/lgpl.html.
 *  A copy is found in the textfile LGPL.txt and important notices to the
 *  license from the author are found in LICENSE.txt distributed with
 *  these libraries.
 *
 *  This library is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  For further information about UCS - unique computing solutions gmbh,
 *  please see the company website: http://www.ucs.at
 *
 *  For further information about [fleXive](R), please see the
 *  project website: http://www.flexive.org
 *
 *
 *  This copyright notice MUST APPEAR in all copies of the file!
 ***************************************************************/
package com.flexive.tests.embedded;

import com.flexive.shared.CacheAdmin;
import com.flexive.shared.EJBLookup;
import com.flexive.shared.FxContext;
import com.flexive.shared.configuration.*;
import com.flexive.shared.configuration.parameters.ParameterFactory;
import com.flexive.shared.content.FxPK;
import com.flexive.shared.exceptions.*;
import com.flexive.shared.interfaces.*;
import com.flexive.shared.security.Account;
import com.flexive.shared.security.Mandator;
import com.flexive.shared.security.UserTicket;
import com.flexive.shared.value.FxLargeNumber;
import com.flexive.tests.shared.TestParameters;
import org.apache.commons.lang.ArrayUtils;
import org.apache.commons.lang.StringUtils;
import org.testng.annotations.*;

import java.io.Serializable;
import java.util.*;

import static com.flexive.shared.EJBLookup.*;
import static com.flexive.tests.embedded.FxTestUtils.login;
import static com.flexive.tests.embedded.FxTestUtils.logout;
import static org.testng.Assert.*;

/**
 * Configuration test suite
 *
 * @author Daniel Lichtenberger (daniel.lichtenberger@flexive.com), UCS - unique computing solutions gmbh (http://www.ucs.at)
 */
@Test(groups = { "ejb", "configuration" })
public class ConfigurationTest {

    private TestUser user;
    private GlobalConfigurationEngine globalConfiguration;
    private DivisionConfigurationEngine divisionConfiguration;
    private UserConfigurationEngine userConfiguration;
    private ApplicationConfigurationEngine applicationConfiguration;
    private NodeConfigurationEngine nodeConfiguration;
    private MandatorConfigurationEngine mandatorConfiguration;
    private ConfigurationEngine configuration;

    public ConfigurationTest() {
    }

    public ConfigurationTest(TestUser user) {
        this.user = user;
    }

    @Factory
    public Object[] createTestInstances() throws FxApplicationException {
        List<Object> result = new ArrayList<Object>();
        for (TestUser user : TestUsers.getConfiguredTestUsers()) {
            result.add(new ConfigurationTest(user));
        }
        if (result.size() > 1) {
            // TODO add configuration management role
            //result.add(new ConfigurationTest(TestUsers.createUser("CONFIGROLE", Role.GlobalSupervisor)));
        }
        return result.toArray();
    }

    @BeforeClass
    public void beforeClass() throws Exception {
        globalConfiguration = EJBLookup.getGlobalConfigurationEngine();
        divisionConfiguration = EJBLookup.getDivisionConfigurationEngine();
        userConfiguration = getUserConfigurationEngine();
        configuration = EJBLookup.getConfigurationEngine();
        applicationConfiguration = getApplicationConfigurationEngine();
        nodeConfiguration = getNodeConfigurationEngine();
        mandatorConfiguration = getMandatorConfigurationEngine();
    }

    @BeforeMethod
    public void beforeTestMethod() throws FxLoginFailedException, FxAccountInUseException {
        //System.out.println("Login: " + user.getUserName());
        login(user);
    }

    @AfterMethod
    public void afterTestMethod() throws Exception {
        //System.out.println("Logout: " + user.getUserName());
        logout();
    }

    /**
     * Generic test class
     *
     * @param <T>   type parameter
     */
    public static class GenericConfigurationTest<T extends Serializable> {
        private GenericConfigurationEngine configuration;
        private Parameter<T> parameter;
        private ParameterDataEditBean<T> data;
        private T value;

        /**
         * Create a new test class for the given parameter.
         *
         * @param configuration the configuration instance to be tested
         * @param parameter     the parameter to be used for unit tests
         * @param value         the value to be stored in the given parameter
         */
        public GenericConfigurationTest(GenericConfigurationEngine configuration, Parameter<T> parameter, T value) {
            this.configuration = configuration;
            this.parameter = parameter;
            this.data = (ParameterDataEditBean<T>) parameter.getData();
            this.value = value;
        }

        /**
         * Run all tests for this instance
         *
         * @throws Exception if an exception is thrown
         */
        public void runTests() throws Exception {
            ParameterPath oldPath = data.getPath();
            try {
                for (SystemParameterPaths path : SystemParameterPaths.getTestPaths()) {
                    if (!path.getScope().hasScope(ParameterScope.GLOBAL)) {
                        continue;
                    }
                    // cycle through all available scopes
                    data.setPath(path);
                    testDefaultValue();
                    testPut();
                    testDelete();
                    testUpdate();
                    testGetAll();
                }
            } finally {
                // restore values
                data.setPath(oldPath);
            }
        }

        /**
         * Check if the default parameter value is returned properly.
         *
         * @throws Exception if an error occurred
         */
        private void testDefaultValue() throws Exception {
            try {
                configuration.remove(parameter);
                if (!mayUpdateConfig())
                    fail("Should not be permitted to delete parameter " + parameter + " in " + configuration);
                assertTrue(parameter.getDefaultValue().equals(configuration.get(parameter)),
                        "Default value incorrect.");
            } catch (FxNoAccessException e) {
                if (mayUpdateConfig())
                    fail("Failed to delete parameter although privileges exist.");
            }
        }

        /**
         * Test storing a parameter in the configuration
         *
         * @throws Exception if an error occurred
         */
        private void testPut() throws Exception {
            try {
                configuration.put(parameter, value);
                if (!mayUpdateConfig())
                    fail("Should not be permitted to set parameter " + parameter + " in " + configuration);

                T dbValue = configuration.get(parameter);
                assertTrue(!((dbValue == null && value != null)
                        || (dbValue != null && value == null && parameter.getDefaultValue() == null)));
                assertTrue(dbValue == null || dbValue.equals(value != null ? value : parameter.getDefaultValue()),
                        "Parameter value not stored correctly or incorrect implementation of \"Object#equals\"");
            } catch (FxNoAccessException e) {
                if (mayUpdateConfig())
                    fail("Failed to put parameter although privileges exist for parameter " + parameter + " in "
                            + configuration);

            } finally {
                safeDelete();
            }
        }

        /**
         * Test deleting a parameter
         *
         * @throws Exception if an error occurred
         */
        private void testDelete() throws Exception {
            try {
                configuration.put(parameter, value);
                if (!mayUpdateConfig())
                    fail("Should not be permitted to set parameter " + parameter + " in " + configuration);

                configuration.get(parameter);
                configuration.remove(parameter);
                if (!mayUpdateConfig())
                    fail("Should not be permitted to delete parameter " + parameter + " in " + configuration);

                try {
                    configuration.get(parameter, parameter.getKey(), true);
                    fail("Parameter deleted but still retrievable with get(): " + parameter);
                } catch (FxNotFoundException e) {
                    // succeed
                }
            } catch (FxNoAccessException e) {
                if (mayUpdateConfig())
                    fail("Failed to update/delete parameter although privileges exist " + "for parameter "
                            + parameter + " in " + configuration);
            } finally {
                safeDelete();
            }
        }

        /**
         * Test updating a parameter
         *
         * @throws Exception if an error occurred
         */
        private void testUpdate() throws Exception {
            try {
                assertTrue(!parameter.getDefaultValue().equals(value),
                        "Default value and value must not be the same!");
                configuration.put(parameter, parameter.getDefaultValue());
                assertTrue(parameter.getDefaultValue().equals(configuration.get(parameter)),
                        "Failed to load value.");
                configuration.put(parameter, value);
                if (!mayUpdateConfig())
                    fail("Should not be permitted to set parameter " + parameter + " in " + configuration);
                assertTrue((configuration.get(parameter) == null && value == null
                        && parameter.getDefaultValue() == null)
                        || (configuration.get(parameter) != null && value == null
                                && parameter.getDefaultValue() != null)
                        || configuration.get(parameter).equals(value), "Failed to load updated value.");
            } catch (FxNoAccessException e) {
                if (mayUpdateConfig())
                    fail("Failed to update parameter although privileges exist " + "for parameter " + parameter
                            + " in " + configuration);
            } finally {
                safeDelete();
            }
        }

        /**
         * Test retrieving a group of parameters stored in a path.
         *
         * @throws Exception if an error occurred
         */
        private void testGetAll() throws Exception {
            try {
                configuration.put(parameter, "key1", parameter.getDefaultValue());
                configuration.put(parameter, "key2", value);
                configuration.put(parameter, "key3", value);
                configuration.put(parameter, "key4", value);
                if (!mayUpdateConfig())
                    fail("Should not be permitted to set parameter " + parameter + " in " + configuration);

                if (!(configuration instanceof GlobalConfigurationEngine)) {
                    final Map<ParameterData, Serializable> allParameters = configuration.getAll();
                    assertTrue(allParameters.size() > 0);
                }

                Map<String, T> params = configuration.getAll(parameter);
                assertTrue(4 == params.entrySet().size(), "Should have retrieved four parameters.");
                int ctr = 1;
                final String[] keyValues = { "key1", "key2", "key3", "key4" };
                for (String key : keyValues) {
                    if (params.get(key) != null) {
                        assertTrue(params.get(key).equals(ctr == 1 ? parameter.getDefaultValue() : value),
                                "Invalid parameter value");
                    }
                    ctr++;
                }

                final Collection<String> keys = configuration.getKeys(parameter);
                assertTrue(4 == keys.size(), "Should have retrieved for parameters.");
                for (String key : keyValues) {
                    assertTrue(keys.contains(key), "Key " + key + " not found in result returned by getKeys.");
                }
            } catch (FxNoAccessException e) {
                if (mayUpdateConfig()) {
                    fail("Failed to update/delete parameter although privileges exist " + "for parameter "
                            + parameter + " in " + configuration);
                }
            } finally {
                safeDelete();
            }
        }

        /**
         * Security-aware "cleanup" for parameters assuming that one can
         * only delete parameters if one can set them...
         *
         * @throws FxApplicationException on errors
         */
        private void safeDelete() throws FxApplicationException {
            try {
                configuration.removeAll(parameter);
            } catch (FxNoAccessException e) {
                if (mayUpdateConfig()) {
                    fail("Failed to delete parameter although privileges exist.");
                }
            }
        }

        /**
         * Returns true if the current user may update this test's config
         *
         * @return true if the current user may update this test's config
         * @throws FxLookupException if a lookup error occurred
         */
        private boolean mayUpdateConfig() throws FxLookupException {
            final UserTicket ticket = FxContext.getUserTicket();
            final ParameterScope scope = parameter.getScope();
            GenericConfigurationEngine checkConfiguration = configuration;
            if (configuration instanceof ConfigurationEngine) {
                // get "primary" config for current parameter for security checks
                if (scope == ParameterScope.GLOBAL) {
                    checkConfiguration = EJBLookup.getGlobalConfigurationEngine();
                } else if (scope == ParameterScope.DIVISION || scope == ParameterScope.DIVISION_ONLY) {
                    checkConfiguration = EJBLookup.getDivisionConfigurationEngine();
                } else if (scope == ParameterScope.USER || scope == ParameterScope.USER_ONLY) {
                    checkConfiguration = getUserConfigurationEngine();
                } else if (scope == ParameterScope.APPLICATION || scope == ParameterScope.APPLICATION_ONLY) {
                    checkConfiguration = getApplicationConfigurationEngine();
                }
            }
            return (checkConfiguration instanceof GlobalConfigurationEngine
                    && FxContext.get().isGlobalAuthenticated())
                    || (checkConfiguration instanceof DivisionConfigurationEngine && ticket.isGlobalSupervisor())
                    || (checkConfiguration instanceof ApplicationConfigurationEngine && ticket.isGlobalSupervisor())
                    || (checkConfiguration instanceof NodeConfigurationEngine && ticket.isGlobalSupervisor())
                    || (checkConfiguration instanceof UserConfigurationEngine && !ticket.isGuest())
                    || (checkConfiguration instanceof MandatorConfigurationEngine && ticket.isMandatorSupervisor());
        }

    }

    /**
     * Test the global configuration
     *
     * @throws Exception if an error occurred
     */
    @Test
    public void globalConfiguration() throws Exception {
        try {
            FxContext.get().setGlobalAuthenticated(false);
            globalConfiguration.put(TestParameters.CACTUS_TEST, "test");
            fail("Global configuration modifiable without global admin login!");
        } catch (FxNoAccessException e) {
            // pass
        }
        final GlobalConfigurationEngine config = EJBLookup.getGlobalConfigurationEngine();
        // check if privileges are respected for all config actions
        testGenericConfiguration(config);
        try {
            FxContext.get().setGlobalAuthenticated(true);
            testGenericConfiguration(config);
        } finally {
            FxContext.get().setGlobalAuthenticated(false);
        }
        // test division infos
        int[] divisionIds = config.getDivisionIds();
        int ctr = 0;
        for (DivisionData data : config.getDivisions()) {
            assertTrue(ArrayUtils.contains(divisionIds, data.getId()),
                    "Division ID not returned by getDivisionIds().");
            assertTrue(StringUtils.isNotBlank(data.getDataSource()), "Division data source not returned");
            assertTrue(StringUtils.isNotBlank(data.getDomainRegEx()), "Domain regexp missing");
            if (data.isAvailable()) {
                assertTrue(StringUtils.isNotBlank(data.getDbVersion()), "DB version missing");
                assertTrue(!data.getDbVendor().equals("Unknown"), "DB vendor missing");
            }
            config.clearDivisionCache();
            assertTrue(config.getDivisionData(data.getId()).equals(data));
            ctr++;
        }
        assertTrue(ctr == divisionIds.length, "getDivisions() and getDivisionIds() array length don't match");

        // test division table update
        final DivisionData[] orig = config.getDivisions();
        FxContext.get().setGlobalAuthenticated(true);
        try {
            final DivisionData newDivision = new DivisionData(1, false, "test", "xxx", "Unknown", "1.2", "Unknown");
            config.saveDivisions(Arrays.asList(newDivision));
            assertTrue(config.getDivisions().length == 1, "Division table not updated");
            assertTrue(config.getDivisions()[0].equals(newDivision), "New division not written properly");
        } finally {
            config.saveDivisions(Arrays.asList(orig));
            assertTrue(Arrays.equals(config.getDivisions(), orig));
            FxContext.get().setGlobalAuthenticated(false);
        }
    }

    /**
     * Test the per-division configuration
     *
     * @throws Exception if an error occurred
     */
    @Test
    public void divisionConfiguration() throws Exception {
        testGenericConfiguration(divisionConfiguration);
    }

    /**
     * Test the per-application configuration
     *
     * @throws Exception if an error occurred
     */
    @Test
    public void applicationConfiguration() throws Exception {
        testGenericConfiguration(applicationConfiguration);
    }

    @Test
    public void mandatorConfiguration() throws Exception {
        testGenericConfiguration(mandatorConfiguration);
    }

    /**
     * Test the per-user configuration
     *
     * @throws Exception if an error occurred
     */
    @Test
    public void userConfiguration() throws Exception {
        testGenericConfiguration(userConfiguration);
    }

    /**
     * Test the node configuration
     *
     * @throws Exception if an error occurred
     */
    @Test
    public void nodeConfiguration() throws Exception {
        testGenericConfiguration(nodeConfiguration);
    }

    /**
     * Test the FxConfiguration wrapper
     *
     * @throws Exception if an error occurred
     */
    @Test
    public void fxConfiguration() throws Exception {
        try {
            FxContext.get().setGlobalAuthenticated(false);
            configuration.put(TestParameters.CACTUS_TEST, "test");
            fail("User allowed to set global config parameters without auth.");
        } catch (FxNoAccessException e) {
            // pass
        }
        // test fallbacks
        try {
            FxContext.get().setGlobalAuthenticated(true);
            testFallbacks(TestParameters.CACTUS_TEST, "test");
            testFallbacks(TestParameters.CACTUS_TEST, null);
        } finally {
            FxContext.get().setGlobalAuthenticated(false);
        }
        // run generic config tests
        try {
            FxContext.get().setGlobalAuthenticated(true);
            testGenericConfiguration(configuration);
        } finally {
            FxContext.get().setGlobalAuthenticated(false);
        }
    }

    @Test
    public void putInSourceTest() throws FxApplicationException {
        final Parameter<Boolean> param = ParameterFactory.newInstance(Boolean.class,
                SystemParameterPaths.TEST_DIVISION_ONLY, "testKey", true);
        try {
            configuration.putInSource(param, false); // put in source without an existing database entry
            assertAccess(param);
            assertTrue(!configuration.get(param));
            configuration.putInSource(param, true); // overwrite existing value
            assertTrue(configuration.get(param));
        } catch (FxNoAccessException e) {
            assertNoAccess(param);
        } finally {
            try {
                EJBLookup.getConfigurationEngine().remove(param);
            } catch (FxApplicationException e) {
                // ignore
            }
        }
    }

    @Test
    public void putInForeignApplication() throws FxApplicationException {
        foreignDomainTest(getApplicationConfigurationEngine(), "otherapp");
    }

    @Test
    public void putInForeignUser() throws FxApplicationException {
        // find a user that is not our own
        final List<Account> accounts = EJBLookup.getAccountEngine().loadAll();
        long userId = -1;
        for (Account account : accounts) {
            if (account.getId() != FxContext.get().getTicket().getUserId()) {
                userId = account.getId();
                break;
            }
        }
        assertTrue(userId != -1);

        foreignDomainTest(getUserConfigurationEngine(), userId);
    }

    @Test
    public void putInForeignMandator() throws FxApplicationException {
        foreignDomainTest(mandatorConfiguration,
                CacheAdmin.getEnvironment().getMandator(Mandator.MANDATOR_FLEXIVE).getName());
    }

    @Test
    public void putInForeignNode() throws FxApplicationException {
        foreignDomainTest(getNodeConfigurationEngine(), "mynode");
    }

    @Test
    public void testLegacyFxValueParameters() {
        // test deserialization of FxValue instances with different class layouts
        final String xml = "      <value class=\"com.flexive.shared.value.FxLargeNumber\">\n"
                + "        <multiLanguage>false</multiLanguage>\n"
                + "        <defaultLanguage>0</defaultLanguage>\n"
                + "        <selectedLanguage>0</selectedLanguage>\n"
                + "        <maxInputLength>-1</maxInputLength>\n" + "        <XPath/>\n"
                + "        <singleValue class=\"long\">5</singleValue>\n"
                + "        <singleValueEmpty>false</singleValueEmpty>\n" + "        <readOnly>false</readOnly>\n"
                + "      </value>\n";

        final Parameter<FxLargeNumber> param = ParameterFactory.newInstance(FxLargeNumber.class, "/",
                ParameterScope.USER, null);

        final FxLargeNumber value = param.getValue(xml);

        assertEquals(value.getBestTranslation(), (Long) 5L);
    }

    private <T extends Serializable> void foreignDomainTest(CustomDomainConfigurationEngine<T> dce,
            T otherDomainValue) throws FxApplicationException {
        final Parameter<Integer> param = TestParameters.CACTUS_TEST_INT;
        final boolean supervisor = FxContext.getUserTicket().isGlobalSupervisor();
        try {
            assertEquals(dce.getDomains(param).size(), 0, "Configuration parameter in use: " + param);
            if (dce instanceof UserConfigurationEngine) {
                assertTrue(supervisor,
                        "User configuration engine must not allow domain listings except for global supervisors.");
            }
        } catch (FxNoAccessException e) {
            assertTrue(
                    (dce instanceof UserConfigurationEngine || dce instanceof MandatorConfigurationEngine)
                            && !supervisor,
                    "Only user or mandator configuration engine should prohibit domain listings.");
        }

        // the following block is expected to fail unless the user is a global supervisor
        final boolean expectFailure = !supervisor;
        boolean createdOwn = false;
        try {
            dce.put(param, 21);
            createdOwn = true;
            assertEquals(dce.getDomains(param).size(), 1);

            dce.put(otherDomainValue, param, param.getKey(), 22);
            final List<T> domains = dce.getDomains(param);
            assertEquals(domains.size(), 2, "There should be two entries: " + param);
            assertTrue(domains.contains(otherDomainValue), "Custom domain not found in " + domains);

            assertTrue(dce.getDomains().contains(otherDomainValue),
                    "Custom domain not found in " + dce.getDomains());

            assertEquals(dce.get(param).intValue(), 21);
            assertEquals(dce.get(otherDomainValue, param, param.getKey(), false).intValue(), 22);

            assertTrue(dce.getAll(otherDomainValue).containsValue(22), "Parameter from other domain not returned");

            for (T domain : domains) {
                dce.remove(domain, param, param.getKey());
            }
            assertEquals(dce.getDomains(param).size(), 0, "Configuration parameter not removed: " + param);
        } catch (FxNoAccessException e) {
            if (createdOwn) {
                assertTrue(dce instanceof UserConfigurationEngine,
                        "Only the user configuration should always allow creation of own instances.");
                dce.remove(param);
            }
            if (!expectFailure) {
                fail("Expected failure, but user was allowed to update foreign configuration IDs.");
            }
        }
    }

    /**
     * Generic configuration test. Tests all known types of parameters
     * for the given configuration.
     *
     * @param configuration The configuration to be tested
     * @throws Exception if an error occurs
     */
    private void testGenericConfiguration(GenericConfigurationEngine configuration) throws Exception {
        // test string parameters
        new GenericConfigurationTest<String>(configuration, TestParameters.CACTUS_TEST, "test").runTests();
        new GenericConfigurationTest<String>(configuration, TestParameters.CACTUS_TEST, "").runTests();
        new GenericConfigurationTest<String>(configuration, TestParameters.CACTUS_TEST, null).runTests();
        // test int parameters
        final Parameter<Integer> param = TestParameters.CACTUS_TEST_INT;
        new GenericConfigurationTest<Integer>(configuration, param, 255329).runTests();
        new GenericConfigurationTest<Integer>(configuration, param, -1032412).runTests();
        try {
            new GenericConfigurationTest<Integer>(configuration, param, null).runTests();
            fail("Should not be able to put null values in int parameters.");
        } catch (Exception e) {
            // pass
        }
        // test long parameters
        new GenericConfigurationTest<Long>(configuration, TestParameters.CACTUS_TEST_LONG, 255329L).runTests();
        new GenericConfigurationTest<Long>(configuration, TestParameters.CACTUS_TEST_LONG, -1032412L).runTests();
        try {
            new GenericConfigurationTest<Long>(configuration, TestParameters.CACTUS_TEST_LONG, null).runTests();
            fail("Should not be able to put null values in long parameters.");
        } catch (Exception e) {
            // pass
        }
        // test boolean parameters
        ParameterDataEditBean<Boolean> data = (ParameterDataEditBean<Boolean>) TestParameters.CACTUS_TEST_BOOL
                .getData();
        data.setDefaultValue(false);
        new GenericConfigurationTest<Boolean>(configuration, TestParameters.CACTUS_TEST_BOOL, true).runTests();
        data.setDefaultValue(true);
        new GenericConfigurationTest<Boolean>(configuration, TestParameters.CACTUS_TEST_BOOL, false).runTests();
        try {
            new GenericConfigurationTest<Boolean>(configuration, TestParameters.CACTUS_TEST_BOOL, null).runTests();
            fail("Should not be able to put null values in boolean parameters.");
        } catch (Exception e) {
            // pass
        }
        // test object parameters with a simple POJO
        new GenericConfigurationTest<FxPK>(configuration, TestParameters.TEST_OBJ, new FxPK(5, 1)).runTests();
        try {
            new GenericConfigurationTest<FxPK>(configuration, TestParameters.TEST_OBJ, null).runTests();
            fail("Should not be able to put null values in object parameters.");
        } catch (Exception e) {
            // pass
        }
        // test foreign ids
        if (configuration instanceof CustomDomainConfigurationEngine) {
            final CustomDomainConfigurationEngine domainConfig = (CustomDomainConfigurationEngine) configuration;
            boolean remove = false;
            try {
                assertEquals(domainConfig.getDomains(param).size(), 0, "Old values exist for: " + param);
                try {
                    domainConfig.put(param, 21);
                    assertAccess(param, domainConfig);
                    remove = true;
                    assertEquals(domainConfig.getDomains(param).size(), 1, "There should be an ID entry: " + param);
                } catch (FxNoAccessException e) {
                    assertNoAccess(param, domainConfig);
                } finally {
                    if (remove) {
                        configuration.remove(param);
                    }
                }
            } catch (FxNoAccessException e) {
                // cannot list IDs
            }
        }
    }

    /**
     * Test all kinds of fallbacks of FxConfiguration
     *
     * @param <T>       parameter value type to be tested
     * @param parameter parameter to be tested
     * @param value     the value to be tested
     * @throws Exception if an error occurred
     */
    private <T extends Serializable> void testFallbacks(Parameter<T> parameter, T value) throws Exception {
        cleanup(parameter);
        for (ParameterPath path : SystemParameterPaths.getTestPaths()) {
            ParameterPath origPath = updatePath(parameter, path);
            try {
                configuration.put(parameter, value);
                assertAccess(parameter);
                T dbValue = configuration.get(parameter);
                checkFallbacks(parameter, dbValue);
            } catch (FxNoAccessException e) {
                assertNoAccess(parameter);
            } finally {
                cleanup(parameter);
                updatePath(parameter, origPath);
            }
        }
    }

    /**
     * Purges the given test parameter from all known configurations.
     *
     * @param parameter the parameter to be deleted
     */
    private void cleanup(Parameter<? extends Serializable> parameter) {
        tryRemove(parameter, globalConfiguration, ParameterScope.GLOBAL);
        tryRemove(parameter, divisionConfiguration, ParameterScope.DIVISION);
        tryRemove(parameter, userConfiguration, ParameterScope.USER);
        tryRemove(parameter, nodeConfiguration, ParameterScope.NODE);
    }

    private void tryRemove(Parameter<? extends Serializable> parameter, GenericConfigurationEngine configuration,
            ParameterScope scope) {
        try {
            configuration.remove(parameter);
            assertAccess(parameter, scope);
        } catch (FxNoAccessException e) {
            assertNoAccess(parameter, scope);
        } catch (Exception e) {
            System.out.println("Failed to delete parameter from " + scope.name() + " config: " + e.getMessage());
        }
    }

    /**
     * Helper method to manually check the fallback value
     *
     * @param parameter the parameter to be checked
     * @param expected  expected parameter value
     * @param <T>       value type
     * @throws FxApplicationException on errors
     */
    private <T extends Serializable> void checkFallbacks(Parameter<T> parameter, T expected)
            throws FxApplicationException {
        ArrayList<ParameterScope> fallbacks = new ArrayList<ParameterScope>();
        fallbacks.add(0, parameter.getScope());
        fallbacks.addAll(parameter.getScope().getFallbacks());
        for (ParameterScope scope : fallbacks) {
            try {
                T value;
                final GenericConfigurationEngine config;
                if (scope == ParameterScope.GLOBAL) {
                    config = globalConfiguration;
                } else if (scope == ParameterScope.DIVISION || scope == ParameterScope.DIVISION_ONLY) {
                    config = divisionConfiguration;
                } else if (scope == ParameterScope.USER || scope == ParameterScope.USER_ONLY) {
                    config = userConfiguration;
                } else if (scope == ParameterScope.APPLICATION || scope == ParameterScope.APPLICATION_ONLY) {
                    config = applicationConfiguration;
                } else if (scope == ParameterScope.NODE || scope == ParameterScope.NODE_ONLY) {
                    config = nodeConfiguration;
                } else {
                    fail("Unexpected parameter scope: " + scope);
                    throw new UnsupportedOperationException();
                }
                value = config.get(parameter, parameter.getData().getKey(), true);
                // parameter exists in config, check value and return
                assertTrue((expected == null && value == null) || expected.equals(value),
                        "Unexpected parameter value");
                return;
            } catch (FxNotFoundException e) {
                // continue with next configuration
            }
        }
        // parameter does not exist in db - check default value
        if (parameter.getDefaultValue() != null) {
            assertTrue(parameter.getDefaultValue().equals(expected),
                    "Configuration should have returned the default value.");
        } else {
            fail("Parameter does not found: " + parameter);
        }
    }

    /**
     * Update the path of test paraemeters.
     *
     * @param parameter the parameter to be updated
     * @param path      the path to be set
     * @return the previously set path
     */
    private ParameterPath updatePath(Parameter<?> parameter, ParameterPath path) {
        ParameterPath oldPath = parameter.getPath();
        ((ParameterDataEditBean<?>) parameter.getData()).setPath(path);
        return oldPath;
    }

    /**
     * Assert that the current user may not access (update) the given parameter.
     *
     * @param parameter the parameter to be checked
     * @param scope scope of the parameter
     */
    private void assertNoAccess(Parameter<?> parameter, ParameterScope scope) {
        UserTicket ticket = FxContext.getUserTicket();
        if (scope == ParameterScope.USER || scope == ParameterScope.USER_ONLY) {
            fail("User parameters should always be writable for the user: " + parameter);
        }
        if ((scope == ParameterScope.DIVISION || scope == ParameterScope.DIVISION_ONLY)
                && ticket.isGlobalSupervisor()) {
            fail("User is global supervisor, but cannot update division parameter: " + parameter);
        }
        if (scope == ParameterScope.GLOBAL && FxContext.get().isGlobalAuthenticated()) {
            fail("User is authenticated for global config, but may not update parameter: " + parameter);
        }
    }

    private void assertNoAccess(Parameter<?> parameter) {
        assertNoAccess(parameter, parameter.getScope());
    }

    /**
     * Assert that the current user may access the given parameter.
     *
     * @param parameter the parameter to be checked
     * @param scope scope of the parameter
     */
    private void assertAccess(Parameter<?> parameter, ParameterScope scope) {
        UserTicket ticket = FxContext.getUserTicket();
        if ((scope == ParameterScope.DIVISION || scope == ParameterScope.DIVISION_ONLY)
                && !ticket.isGlobalSupervisor()) {
            fail("User is NOT global supervisor, but can update division parameter: " + parameter);
        }
        if (scope == ParameterScope.GLOBAL && !FxContext.get().isGlobalAuthenticated()) {
            fail("User is NOT authenticated for global config, but may update global parameter: " + parameter);
        }
    }

    private void assertAccess(Parameter<?> parameter) {
        assertAccess(parameter, parameter.getScope());
    }

    private void assertAccess(Parameter<?> parameter, GenericConfigurationEngine configuration) {
        assertAccess(parameter, scopeForAccessCheck(parameter, configuration));
    }

    private void assertNoAccess(Parameter<?> parameter, GenericConfigurationEngine configuration) {
        assertNoAccess(parameter, scopeForAccessCheck(parameter, configuration));
    }

    private ParameterScope scopeForAccessCheck(Parameter<?> parameter, GenericConfigurationEngine configuration) {
        final ParameterScope scope;
        if (configuration instanceof ConfigurationEngine) {
            scope = parameter.getScope();
        } else if (configuration instanceof ApplicationConfigurationEngine
                || configuration instanceof NodeConfigurationEngine
                || configuration instanceof DivisionConfigurationEngine) {
            scope = ParameterScope.DIVISION; // same rules as for division config
        } else if (configuration instanceof UserConfigurationEngine) {
            scope = ParameterScope.USER;
        } else if (configuration instanceof GlobalConfigurationEngine) {
            scope = ParameterScope.GLOBAL;
        } else if (configuration instanceof MandatorConfigurationEngine) {
            scope = ParameterScope.MANDATOR;
        } else {
            throw new IllegalArgumentException("Unknown configuration engine: " + configuration);
        }
        return scope;
    }
}