org.apache.geode.distributed.internal.ClusterConfigurationService.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.geode.distributed.internal.ClusterConfigurationService.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.geode.distributed.internal;

import static org.apache.geode.distributed.ConfigurationProperties.CLUSTER_CONFIGURATION_DIR;
import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_MANAGER;
import static org.apache.geode.distributed.ConfigurationProperties.SECURITY_POST_PROCESSOR;

import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.io.filefilter.DirectoryFileFilter;
import org.apache.geode.CancelException;
import org.apache.geode.cache.AttributesFactory;
import org.apache.geode.cache.Cache;
import org.apache.geode.cache.DataPolicy;
import org.apache.geode.cache.DiskStore;
import org.apache.geode.cache.Region;
import org.apache.geode.cache.Scope;
import org.apache.geode.cache.execute.ResultCollector;
import org.apache.geode.distributed.DistributedLockService;
import org.apache.geode.distributed.DistributedMember;
import org.apache.geode.distributed.DistributedSystem;
import org.apache.geode.distributed.internal.locks.DLockService;
import org.apache.geode.internal.cache.GemFireCacheImpl;
import org.apache.geode.internal.cache.InternalRegionArguments;
import org.apache.geode.internal.cache.persistence.PersistentMemberID;
import org.apache.geode.internal.cache.persistence.PersistentMemberManager;
import org.apache.geode.internal.cache.persistence.PersistentMemberPattern;
import org.apache.geode.internal.cache.xmlcache.CacheXmlGenerator;
import org.apache.geode.internal.lang.StringUtils;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.management.internal.cli.CliUtil;
import org.apache.geode.management.internal.configuration.callbacks.ConfigurationChangeListener;
import org.apache.geode.management.internal.configuration.domain.Configuration;
import org.apache.geode.management.internal.configuration.domain.SharedConfigurationStatus;
import org.apache.geode.management.internal.configuration.domain.XmlEntity;
import org.apache.geode.management.internal.configuration.functions.UploadJarFunction;
import org.apache.geode.management.internal.configuration.messages.ConfigurationRequest;
import org.apache.geode.management.internal.configuration.messages.ConfigurationResponse;
import org.apache.geode.management.internal.configuration.messages.SharedConfigurationStatusResponse;
import org.apache.geode.management.internal.configuration.utils.XmlUtils;
import org.apache.logging.log4j.Logger;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileFilter;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Properties;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Collectors;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactoryConfigurationError;

@SuppressWarnings({ "deprecation", "unchecked" })
public class ClusterConfigurationService {

    private static final Logger logger = LogService.getLogger();

    /**
     * Name of the directory where the shared configuration artifacts are stored
     */
    public static final String CLUSTER_CONFIG_ARTIFACTS_DIR_NAME = "cluster_config";
    private static final String CLUSTER_CONFIG_DISK_STORE_NAME = "cluster_config";
    public static final String CLUSTER_CONFIG_DISK_DIR_PREFIX = "ConfigDiskDir_";

    public static final String CLUSTER_CONFIG = "cluster";

    /**
     * Name of the lock service used for shared configuration
     */
    private static final String SHARED_CONFIG_LOCK_SERVICE_NAME = "__CLUSTER_CONFIG_LS";

    /**
     * Name of the lock for locking the shared configuration
     */
    public static final String SHARED_CONFIG_LOCK_NAME = "__CLUSTER_CONFIG_LOCK";

    /**
     * Name of the region which is used to store the configuration information
     */
    private static final String CONFIG_REGION_NAME = "_ConfigurationRegion";

    private final String configDirPath;
    private final String configDiskDirPath;

    private final Set<PersistentMemberPattern> newerSharedConfigurationLocatorInfo = new HashSet<>();
    private final AtomicReference<SharedConfigurationStatus> status = new AtomicReference<>();

    private GemFireCacheImpl cache;
    private final DistributedLockService sharedConfigLockingService;

    public ClusterConfigurationService(Cache cache) throws IOException {
        this.cache = (GemFireCacheImpl) cache;
        Properties properties = cache.getDistributedSystem().getProperties();
        // resolve the cluster config dir
        String clusterConfigRootDir = properties.getProperty(CLUSTER_CONFIGURATION_DIR);

        if (StringUtils.isBlank(clusterConfigRootDir)) {
            clusterConfigRootDir = System.getProperty("user.dir");
        } else {
            File diskDir = new File(clusterConfigRootDir);
            if (!diskDir.exists() && !diskDir.mkdirs()) {
                throw new IOException("Cannot create directory : " + clusterConfigRootDir);
            }
            clusterConfigRootDir = diskDir.getCanonicalPath();
        }

        // resolve the file paths
        String configDiskDirName = CLUSTER_CONFIG_DISK_DIR_PREFIX + cache.getDistributedSystem().getName();

        configDirPath = FilenameUtils.concat(clusterConfigRootDir, CLUSTER_CONFIG_ARTIFACTS_DIR_NAME);
        configDiskDirPath = FilenameUtils.concat(clusterConfigRootDir, configDiskDirName);
        sharedConfigLockingService = getSharedConfigLockService(cache.getDistributedSystem());
        status.set(SharedConfigurationStatus.NOT_STARTED);
    }

    /**
     * Gets or creates (if not created) shared configuration lock service
     */
    private DistributedLockService getSharedConfigLockService(DistributedSystem ds) {
        DistributedLockService sharedConfigDls = DLockService.getServiceNamed(SHARED_CONFIG_LOCK_SERVICE_NAME);
        try {
            if (sharedConfigDls == null) {
                sharedConfigDls = DLockService.create(SHARED_CONFIG_LOCK_SERVICE_NAME,
                        (InternalDistributedSystem) ds, true, true);
            }
        } catch (IllegalArgumentException e) {
            return DLockService.getServiceNamed(SHARED_CONFIG_LOCK_SERVICE_NAME);
        }
        return sharedConfigDls;
    }

    /**
     * Adds/replaces the xml entity in the shared configuration we don't need to trigger the change
     * listener for this modification, so it's ok to operate on the original configuration object
     */
    public void addXmlEntity(XmlEntity xmlEntity, String[] groups) {
        lockSharedConfiguration();
        try {
            Region<String, Configuration> configRegion = getConfigurationRegion();
            if (groups == null || groups.length == 0) {
                groups = new String[] { ClusterConfigurationService.CLUSTER_CONFIG };
            }
            for (String group : groups) {
                Configuration configuration = (Configuration) configRegion.get(group);
                if (configuration == null) {
                    configuration = new Configuration(group);
                }
                String xmlContent = configuration.getCacheXmlContent();
                if (xmlContent == null || xmlContent.isEmpty()) {
                    StringWriter sw = new StringWriter();
                    PrintWriter pw = new PrintWriter(sw);
                    CacheXmlGenerator.generateDefault(pw);
                    xmlContent = sw.toString();
                }
                try {
                    final Document doc = XmlUtils.createAndUpgradeDocumentFromXml(xmlContent);
                    XmlUtils.addNewNode(doc, xmlEntity);
                    configuration.setCacheXmlContent(XmlUtils.prettyXml(doc));
                    configRegion.put(group, configuration);
                } catch (Exception e) {
                    logger.error("error updating cluster configuration for group " + group, e);
                }
            }
        } finally {
            unlockSharedConfiguration();
        }
    }

    /**
     * Deletes the xml entity from the shared configuration.
     */
    public void deleteXmlEntity(final XmlEntity xmlEntity, String[] groups) {
        lockSharedConfiguration();
        try {
            Region<String, Configuration> configRegion = getConfigurationRegion();
            // No group is specified, so delete in every single group if it exists.
            if (groups == null) {
                Set<String> groupSet = configRegion.keySet();
                groups = groupSet.toArray(new String[groupSet.size()]);
            }
            for (String group : groups) {
                Configuration configuration = (Configuration) configRegion.get(group);
                if (configuration != null) {
                    String xmlContent = configuration.getCacheXmlContent();
                    try {
                        if (xmlContent != null && !xmlContent.isEmpty()) {
                            Document doc = XmlUtils.createAndUpgradeDocumentFromXml(xmlContent);
                            XmlUtils.deleteNode(doc, xmlEntity);
                            configuration.setCacheXmlContent(XmlUtils.prettyXml(doc));
                            configRegion.put(group, configuration);
                        }
                    } catch (Exception e) {
                        logger.error("error updating cluster configuration for group " + group, e);
                    }
                }
            }
        } finally {
            unlockSharedConfiguration();
        }
    }

    // we don't need to trigger the change listener for this modification, so it's ok to
    // operate on the original configuration object
    public void modifyXmlAndProperties(Properties properties, XmlEntity xmlEntity, String[] groups) {
        lockSharedConfiguration();
        try {
            if (groups == null) {
                groups = new String[] { ClusterConfigurationService.CLUSTER_CONFIG };
            }
            Region<String, Configuration> configRegion = getConfigurationRegion();
            for (String group : groups) {
                Configuration configuration = configRegion.get(group);
                if (configuration == null) {
                    configuration = new Configuration(group);
                }

                if (xmlEntity != null) {
                    String xmlContent = configuration.getCacheXmlContent();
                    if (xmlContent == null || xmlContent.isEmpty()) {
                        StringWriter sw = new StringWriter();
                        PrintWriter pw = new PrintWriter(sw);
                        CacheXmlGenerator.generateDefault(pw);
                        xmlContent = sw.toString();
                    }
                    try {
                        Document doc = XmlUtils.createAndUpgradeDocumentFromXml(xmlContent);
                        // Modify the cache attributes
                        XmlUtils.modifyRootAttributes(doc, xmlEntity);
                        // Change the xml content of the configuration and put it the config region
                        configuration.setCacheXmlContent(XmlUtils.prettyXml(doc));
                    } catch (Exception e) {
                        logger.error("error updating cluster configuration for group " + group, e);
                    }
                }

                if (properties != null) {
                    configuration.getGemfireProperties().putAll(properties);
                }
                configRegion.put(group, configuration);
            }
        } finally {
            unlockSharedConfiguration();
        }
    }

    /**
     * Add jar information into the shared configuration and save the jars in the file system used
     * when deploying jars
     * 
     * @return true on success
     */
    public boolean addJarsToThisLocator(String[] jarNames, byte[][] jarBytes, String[] groups) {
        boolean success = true;
        lockSharedConfiguration();
        try {
            if (groups == null) {
                groups = new String[] { ClusterConfigurationService.CLUSTER_CONFIG };
            }
            Region<String, Configuration> configRegion = getConfigurationRegion();
            for (String group : groups) {
                Configuration configuration = configRegion.get(group);

                if (configuration == null) {
                    configuration = new Configuration(group);
                    createConfigDirIfNecessary(group);
                }

                String groupDir = FilenameUtils.concat(configDirPath, group);
                for (int i = 0; i < jarNames.length; i++) {
                    String filePath = FilenameUtils.concat(groupDir, jarNames[i]);
                    File jarFile = new File(filePath);
                    try {
                        FileUtils.writeByteArrayToFile(jarFile, jarBytes[i]);
                    } catch (IOException e) {
                        logger.info(e);
                    }
                }

                // update the record after writing the jars to the file system, since the listener
                // will need the jars on file to upload to other locators. Need to update the jars
                // using a new copy of the Configuration so that the change listener will pick up the jar
                // name changes.
                Configuration configurationCopy = new Configuration(configuration);
                configurationCopy.addJarNames(jarNames);
                configRegion.put(group, configurationCopy);
            }
        } catch (Exception e) {
            success = false;
            logger.info(e.getMessage(), e);
        } finally {
            unlockSharedConfiguration();
        }
        return success;
    }

    /**
     * Removes the jar files from the shared configuration. used when undeploy jars
     *
     * @param jarNames Names of the jar files.
     * @param groups Names of the groups which had the jar file deployed.
     * @return true on success.
     */
    public boolean removeJars(final String[] jarNames, String[] groups) {
        boolean success = true;
        lockSharedConfiguration();
        try {
            Region<String, Configuration> configRegion = getConfigurationRegion();
            if (groups == null) {
                groups = configRegion.keySet().stream().toArray(String[]::new);
            }
            for (String group : groups) {
                Configuration configuration = configRegion.get(group);
                if (configuration == null) {
                    break;
                }
                Configuration configurationCopy = new Configuration(configuration);
                configurationCopy.removeJarNames(jarNames);
                configRegion.put(group, configurationCopy);
            }
        } catch (Exception e) {
            logger.info("Exception occurred while deleting the jar files", e);
            success = false;
        } finally {
            unlockSharedConfiguration();
        }
        return success;
    }

    /**
     * read the jar bytes in the file system
     */
    // used when creating cluster config response
    // and used when uploading the jars to another locator
    public byte[] getJarBytesFromThisLocator(String group, String jarName) throws Exception {
        Configuration configuration = getConfiguration(group);

        File jar = getPathToJarOnThisLocator(group, jarName).toFile();

        if (configuration == null || !configuration.getJarNames().contains(jarName) || !jar.exists()) {
            return null;
        }

        return FileUtils.readFileToByteArray(jar);
    }

    // used in the cluster config change listener when jarnames are changed in the internal region
    public void downloadJarFromOtherLocators(String groupName, String jarName) throws Exception {
        logger.info("Getting Jar files from other locators");
        DM dm = cache.getDistributionManager();
        DistributedMember me = cache.getMyId();
        Set<DistributedMember> locators = new HashSet<>(dm.getAllHostedLocatorsWithSharedConfiguration().keySet());
        locators.remove(me);

        createConfigDirIfNecessary(groupName);

        byte[] jarBytes = locators.stream()
                .map((DistributedMember locator) -> downloadJarFromLocator(locator, groupName, jarName))
                .filter(Objects::nonNull).findFirst().orElseThrow(() -> new IllegalStateException(
                        "No locators have a deployed jar named " + jarName + " in " + groupName));

        File jarToWrite = getPathToJarOnThisLocator(groupName, jarName).toFile();
        FileUtils.writeByteArrayToFile(jarToWrite, jarBytes);
    }

    // used when creating cluster config response
    public Map<String, byte[]> getAllJarsFromThisLocator(Set<String> groups) throws Exception {
        Map<String, byte[]> jarNamesToJarBytes = new HashMap<>();

        for (String group : groups) {
            Configuration groupConfig = getConfiguration(group);
            if (groupConfig == null) {
                break;
            }

            Set<String> jars = groupConfig.getJarNames();
            for (String jar : jars) {
                byte[] jarBytes = getJarBytesFromThisLocator(group, jar);
                jarNamesToJarBytes.put(jar, jarBytes);
            }
        }

        return jarNamesToJarBytes;
    }

    /**
     * Creates the shared configuration service
     * 
     * @param loadSharedConfigFromDir when set to true, loads the configuration from the share_config
     *        directory
     */
    public void initSharedConfiguration(boolean loadSharedConfigFromDir) throws Exception {
        status.set(SharedConfigurationStatus.STARTED);
        Region<String, Configuration> configRegion = this.getConfigurationRegion();
        lockSharedConfiguration();
        try {
            if (loadSharedConfigFromDir) {
                logger.info("Reading cluster configuration from '{}' directory",
                        ClusterConfigurationService.CLUSTER_CONFIG_ARTIFACTS_DIR_NAME);
                loadSharedConfigurationFromDisk();
            } else {
                persistSecuritySettings(configRegion);
                // for those groups that have jar files, need to download the jars from other locators
                // if it doesn't exist yet
                Set<String> groups = configRegion.keySet();
                for (String group : groups) {
                    Configuration config = configRegion.get(group);
                    for (String jar : config.getJarNames()) {
                        if (!(getPathToJarOnThisLocator(group, jar).toFile()).exists()) {
                            downloadJarFromOtherLocators(group, jar);
                        }
                    }
                }
            }
        } finally {
            unlockSharedConfiguration();
        }

        status.set(SharedConfigurationStatus.RUNNING);
    }

    private void persistSecuritySettings(final Region<String, Configuration> configRegion) {
        Properties securityProps = cache.getDistributedSystem().getSecurityProperties();

        Configuration clusterPropertiesConfig = configRegion.get(ClusterConfigurationService.CLUSTER_CONFIG);
        if (clusterPropertiesConfig == null) {
            clusterPropertiesConfig = new Configuration(ClusterConfigurationService.CLUSTER_CONFIG);
            configRegion.put(ClusterConfigurationService.CLUSTER_CONFIG, clusterPropertiesConfig);
        }
        // put security-manager and security-post-processor in the cluster config
        Properties clusterProperties = clusterPropertiesConfig.getGemfireProperties();

        if (securityProps.containsKey(SECURITY_MANAGER)) {
            clusterProperties.setProperty(SECURITY_MANAGER, securityProps.getProperty(SECURITY_MANAGER));
        }
        if (securityProps.containsKey(SECURITY_POST_PROCESSOR)) {
            clusterProperties.setProperty(SECURITY_POST_PROCESSOR,
                    securityProps.getProperty(SECURITY_POST_PROCESSOR));
        }
    }

    /**
     * Creates a ConfigurationResponse based on the configRequest, configuration response contains the
     * requested shared configuration This method locks the ClusterConfigurationService
     */
    public ConfigurationResponse createConfigurationReponse(final ConfigurationRequest configRequest)
            throws Exception {

        ConfigurationResponse configResponse = new ConfigurationResponse();

        for (int i = 0; i < configRequest.getNumAttempts(); i++) {
            boolean isLocked = sharedConfigLockingService.lock(SHARED_CONFIG_LOCK_NAME, 5000, 5000);
            try {
                if (isLocked) {
                    Set<String> groups = configRequest.getGroups();
                    groups.add(ClusterConfigurationService.CLUSTER_CONFIG);
                    logger.info("Building up configuration response with following configurations: {}", groups);

                    for (String group : groups) {
                        Configuration configuration = getConfiguration(group);
                        configResponse.addConfiguration(configuration);
                    }

                    Map<String, byte[]> jarNamesToJarBytes = getAllJarsFromThisLocator(groups);
                    String[] jarNames = jarNamesToJarBytes.keySet().stream().toArray(String[]::new);
                    byte[][] jarBytes = jarNamesToJarBytes.values().toArray(new byte[jarNames.length][]);

                    configResponse.addJarsToBeDeployed(jarNames, jarBytes);
                    configResponse.setFailedToGetSharedConfig(false);
                    return configResponse;
                }
            } finally {
                sharedConfigLockingService.unlock(SHARED_CONFIG_LOCK_NAME);
            }

        }
        configResponse.setFailedToGetSharedConfig(true);
        return configResponse;
    }

    /**
     * Create a response containing the status of the Shared configuration and information about other
     * locators containing newer shared configuration data (if at all)
     * 
     * @return {@link SharedConfigurationStatusResponse} containing the
     *         {@link SharedConfigurationStatus}
     */
    public SharedConfigurationStatusResponse createStatusResponse() {
        SharedConfigurationStatusResponse response = new SharedConfigurationStatusResponse();
        response.setStatus(getStatus());
        response.addWaitingLocatorInfo(newerSharedConfigurationLocatorInfo);
        return response;
    }

    /**
     * For tests only. TODO: clean this up and remove from production code
     * <p/>
     * Throws {@code AssertionError} wrapping any exception thrown by operation.
     */
    public void destroySharedConfiguration() {
        try {
            Region<String, Configuration> configRegion = getConfigurationRegion();
            if (configRegion != null) {
                configRegion.destroyRegion();
            }
            DiskStore configDiskStore = this.cache.findDiskStore(CLUSTER_CONFIG_ARTIFACTS_DIR_NAME);
            if (configDiskStore != null) {
                configDiskStore.destroy();
                File file = new File(configDiskDirPath);
                FileUtils.deleteDirectory(file);
            }
            FileUtils.deleteDirectory(new File(configDirPath));
        } catch (Exception exception) {
            throw new AssertionError(exception);
        }
    }

    public Path getPathToJarOnThisLocator(String groupName, String jarName) {
        return new File(configDirPath).toPath().resolve(groupName).resolve(jarName);
    }

    public Configuration getConfiguration(String groupName) {
        Configuration configuration = getConfigurationRegion().get(groupName);
        return configuration;
    }

    public Map<String, Configuration> getEntireConfiguration() throws Exception {
        Set<String> keys = getConfigurationRegion().keySet();
        return getConfigurationRegion().getAll(keys);
    }

    /**
     * Returns the path of Shared configuration directory
     * 
     * @return {@link String} path of the shared configuration directory
     */
    public String getSharedConfigurationDirPath() {
        return configDirPath;
    }

    /**
     * Gets the current status of the ClusterConfigurationService If the status is started , it
     * determines if the shared configuration is waiting for new configuration on other locators
     * 
     * @return {@link SharedConfigurationStatus}
     */
    public SharedConfigurationStatus getStatus() {
        SharedConfigurationStatus scStatus = this.status.get();
        if (scStatus == SharedConfigurationStatus.STARTED) {
            PersistentMemberManager pmm = cache.getPersistentMemberManager();
            Map<String, Set<PersistentMemberID>> waitingRegions = pmm.getWaitingRegions();
            if (!waitingRegions.isEmpty()) {
                this.status.compareAndSet(SharedConfigurationStatus.STARTED, SharedConfigurationStatus.WAITING);
                Set<PersistentMemberID> persMemIds = waitingRegions.get(Region.SEPARATOR_CHAR + CONFIG_REGION_NAME);
                for (PersistentMemberID persMemId : persMemIds) {
                    newerSharedConfigurationLocatorInfo.add(new PersistentMemberPattern(persMemId));
                }
            }
        }
        return this.status.get();
    }

    /**
     * Loads the internal region with the configuration in the configDirPath
     */
    public void loadSharedConfigurationFromDisk() throws Exception {
        lockSharedConfiguration();
        File[] groupNames = new File(configDirPath).listFiles((FileFilter) DirectoryFileFilter.INSTANCE);
        Map<String, Configuration> sharedConfiguration = new HashMap<String, Configuration>();

        try {
            for (File groupName : groupNames) {
                Configuration configuration = readConfiguration(groupName);
                sharedConfiguration.put(groupName.getName(), configuration);
            }
            Region clusterRegion = getConfigurationRegion();
            clusterRegion.clear();
            clusterRegion.putAll(sharedConfiguration);

            // Overwrite the security settings using the locator's properties, ignoring whatever
            // in the import
            persistSecuritySettings(clusterRegion);

        } finally {
            unlockSharedConfiguration();
        }
    }

    public void renameExistingSharedConfigDirectory() {
        File configDirFile = new File(configDirPath);
        if (configDirFile.exists()) {
            String configDirFileName2 = CLUSTER_CONFIG_ARTIFACTS_DIR_NAME
                    + new SimpleDateFormat("yyyyMMddhhmm").format(new Date()) + "." + System.nanoTime();
            File configDirFile2 = new File(configDirFile.getParent(), configDirFileName2);
            try {
                FileUtils.moveDirectory(configDirFile, configDirFile2);
            } catch (IOException e) {
                logger.info(e);
            }
        }
    }

    // Write the content of xml and properties into the file system for exporting purpose
    public void writeConfigToFile(final Configuration configuration) throws Exception {
        File configDir = createConfigDirIfNecessary(configuration.getConfigName());

        File propsFile = new File(configDir, configuration.getPropertiesFileName());
        BufferedWriter bw = new BufferedWriter(new FileWriter(propsFile));
        configuration.getGemfireProperties().store(bw, null);
        bw.close();

        File xmlFile = new File(configDir, configuration.getCacheXmlFileName());
        FileUtils.writeStringToFile(xmlFile, configuration.getCacheXmlContent(), "UTF-8");
    }

    private boolean lockSharedConfiguration() {
        return sharedConfigLockingService.lock(SHARED_CONFIG_LOCK_NAME, -1, -1);
    }

    private void unlockSharedConfiguration() {
        sharedConfigLockingService.unlock(SHARED_CONFIG_LOCK_NAME);
    }

    private byte[] downloadJarFromLocator(DistributedMember locator, String groupName, String jarName) {
        ResultCollector<byte[], List<byte[]>> rc = (ResultCollector<byte[], List<byte[]>>) CliUtil
                .executeFunction(new UploadJarFunction(), new Object[] { groupName, jarName }, locator);

        List<byte[]> result = rc.getResult();

        // we should only get one byte[] back in the list
        return result.stream().filter(Objects::nonNull).findFirst().orElse(null);
    }

    /**
     * Gets the region containing the shared configuration data. The region is created , if it does
     * not exist already. Note : this could block if this locator contains stale persistent
     * configuration data.
     * 
     * @return {@link Region} ConfigurationRegion, this should never be null
     */
    private Region<String, Configuration> getConfigurationRegion() {
        Region<String, Configuration> configRegion = cache.getRegion(CONFIG_REGION_NAME);

        try {
            if (configRegion == null) {
                File diskDir = new File(configDiskDirPath);

                if (!diskDir.exists()) {
                    if (!diskDir.mkdirs()) {
                        throw new IOException("Cannot create directory at " + configDiskDirPath);
                    }
                }

                File[] diskDirs = { diskDir };
                cache.createDiskStoreFactory().setDiskDirs(diskDirs).setAutoCompact(true).setMaxOplogSize(10)
                        .create(CLUSTER_CONFIG_DISK_STORE_NAME);

                AttributesFactory<String, Configuration> regionAttrsFactory = new AttributesFactory<String, Configuration>();
                regionAttrsFactory.setDataPolicy(DataPolicy.PERSISTENT_REPLICATE);
                regionAttrsFactory.setCacheListener(new ConfigurationChangeListener(this));
                regionAttrsFactory.setDiskStoreName(CLUSTER_CONFIG_DISK_STORE_NAME);
                regionAttrsFactory.setScope(Scope.DISTRIBUTED_ACK);
                InternalRegionArguments internalArgs = new InternalRegionArguments();
                internalArgs.setIsUsedForMetaRegion(true);
                internalArgs.setMetaRegionWithTransactions(false);

                configRegion = cache.createVMRegion(CONFIG_REGION_NAME, regionAttrsFactory.create(), internalArgs);
            }

        } catch (CancelException e) {
            if (configRegion == null) {
                this.status.set(SharedConfigurationStatus.STOPPED);
            }
            throw e; // CONFIG: don't rethrow as Exception, keep it a subclass of CancelException

        } catch (Exception e) {
            if (configRegion == null) {
                this.status.set(SharedConfigurationStatus.STOPPED);
            }
            throw new RuntimeException("Error occurred while initializing cluster configuration", e);
        }

        return configRegion;
    }

    /**
     * Reads the configuration information from the shared configuration directory and returns a
     * {@link Configuration} object
     * 
     * @return {@link Configuration}
     */
    private Configuration readConfiguration(File groupConfigDir) throws SAXException, ParserConfigurationException,
            TransformerFactoryConfigurationError, TransformerException, IOException {
        Configuration configuration = new Configuration(groupConfigDir.getName());
        File cacheXmlFull = new File(groupConfigDir, configuration.getCacheXmlFileName());
        File propertiesFull = new File(groupConfigDir, configuration.getPropertiesFileName());

        configuration.setCacheXmlFile(cacheXmlFull);
        configuration.setPropertiesFile(propertiesFull);

        Set<String> jarFileNames = Arrays.stream(groupConfigDir.list())
                .filter((String filename) -> filename.endsWith(".jar")).collect(Collectors.toSet());
        configuration.addJarNames(jarFileNames);
        return configuration;
    }

    /**
     * Creates a directory for this configuration if it doesn't already exist.
     */
    private File createConfigDirIfNecessary(final String configName) throws Exception {
        File clusterConfigDir = new File(getSharedConfigurationDirPath());
        if (!clusterConfigDir.exists()) {
            if (!clusterConfigDir.mkdirs()) {
                throw new IOException("Cannot create directory : " + getSharedConfigurationDirPath());
            }
        }
        Path configDirPath = clusterConfigDir.toPath().resolve(configName);

        File configDir = configDirPath.toFile();
        if (!configDir.exists()) {
            if (!configDir.mkdir()) {
                throw new IOException("Cannot create directory : " + configDirPath);
            }
        }

        return configDir;
    }

    // check if it's ok from populate the properties from one member to another
    public static boolean isMisConfigured(Properties fromProps, Properties toProps, String key) {
        String fromPropValue = fromProps.getProperty(key);
        String toPropValue = toProps.getProperty(key);

        // if this to prop is not specified, this is always OK.
        if (org.apache.commons.lang.StringUtils.isBlank(toPropValue))
            return false;

        // to props is not blank, but from props is blank, NOT OK.
        if (org.apache.commons.lang.StringUtils.isBlank(fromPropValue))
            return true;

        // at this point check for eqality
        return !fromPropValue.equals(toPropValue);
    }
}