de.dal33t.powerfolder.util.ConfigurationLoader.java Source code

Java tutorial

Introduction

Here is the source code for de.dal33t.powerfolder.util.ConfigurationLoader.java

Source

/*
 * Copyright 2004 - 2009 Christian Sprajc. All rights reserved.
 *
 * This file is part of PowerFolder.
 *
 * PowerFolder is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation.
 *
 * PowerFolder 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.
 *
 * You should have received a copy of the GNU General Public License
 * along with PowerFolder. If not, see <http://www.gnu.org/licenses/>.
 *
 * $Id: CleanupTranslationFiles.java 4282 2008-06-16 03:25:09Z tot $
 */
package de.dal33t.powerfolder.util;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Properties;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.prefs.Preferences;

import org.apache.commons.cli.CommandLine;

import de.dal33t.powerfolder.ConfigurationEntry;
import de.dal33t.powerfolder.Controller;
import de.dal33t.powerfolder.disk.FolderSettings;
import de.dal33t.powerfolder.message.ConfigurationLoadRequest;

/**
 * Helper class around configuration
 *
 * @author Christian Sprajc
 * @version $Revision$
 */
public class ConfigurationLoader {
    private static final String INITIAL_STARTUP_CONFIG_FILENAME = "initial_startup.config";
    public static final String DEFAULT_CONFIG_FILENAME = "Default.config";
    public static final String RADIUS_CONFIG_FILENAME = "RADIUS.config";
    private static final String DEFAULT_PROPERTIES_URI = "/client_deployment/" + DEFAULT_CONFIG_FILENAME;
    private static final String PREFERENCES_PREFIX = "pref.";
    private static final int URL_CONNECT_TIMEOUT_SECONDS = 10;

    private static Logger LOG = Logger.getLogger(ConfigurationLoader.class.getName());

    private ConfigurationLoader() {
    }

    public static boolean overwriteConfigEntries(Properties p) {
        boolean overWrite = Boolean.valueOf(ConfigurationEntry.CONFIG_OVERWRITE_VALUES.getDefaultValue());
        String owStr = p.getProperty(ConfigurationEntry.CONFIG_OVERWRITE_VALUES.getConfigKey());
        try {
            overWrite = Boolean.parseBoolean(owStr);
        } catch (Exception e) {
            LOG.warning("Unable to parse pre-config overwrite value. Problem value: " + owStr + ". Now using: "
                    + overWrite + ". " + e);
        }
        return overWrite;
    }

    private static boolean dropFolderSettings(Properties p) {
        boolean drop = Boolean.valueOf(ConfigurationEntry.CONFIG_DROP_FOLDER_SETTINGS.getDefaultValue());
        String owStr = p.getProperty(ConfigurationEntry.CONFIG_DROP_FOLDER_SETTINGS.getConfigKey());
        try {
            drop = Boolean.parseBoolean(owStr);
        } catch (Exception e) {
            LOG.warning("Unable to parse pre-config drop folders settings value. Problem value: " + owStr
                    + ". Now using: " + drop + ". " + e);
        }
        return drop;
    }

    /**
     * Processes/Handles a configuration (re-) load request.
     *
     * @param controller
     * @param clr
     */
    public static void processMessage(final Controller controller, ConfigurationLoadRequest clr) {
        Reject.ifNull(controller, "Controller");
        Reject.ifNull(clr, "Message");
        try {
            LOG.info("Processing message: " + clr);
            if (StringUtils.isBlank(clr.getConfigURL())) {
                // Single Key=value option
                if (clr.isKeyValue()) {
                    boolean hasValue = controller.getConfig().containsKey(clr.getKey());
                    if (clr.isReplaceExisting() == null || clr.isReplaceExisting()
                            || (!hasValue && !clr.isReplaceExisting())) {
                        if (clr.getValue() == null) {
                            controller.getConfig().remove(clr.getKey());
                        } else {
                            controller.getConfig().put(clr.getKey(), clr.getValue());
                        }
                        // Seems to be valid, store.
                        controller.saveConfig();
                        LOG.log(Level.INFO, "Update configuration " + clr.getKey() + "=" + clr.getValue());
                    }
                }
            } else {
                Properties preConfig = ConfigurationLoader.loadPreConfiguration(clr.getConfigURL());
                if (preConfig != null) {
                    boolean overwrite;
                    if (clr.isReplaceExisting() != null) {
                        overwrite = clr.isReplaceExisting();
                    } else {
                        overwrite = overwriteConfigEntries(preConfig);
                    }
                    if (dropFolderSettings(preConfig)) {
                        Set<String> entryIds = FolderSettings.loadEntryIds(controller.getConfig());
                        for (String entryId : entryIds) {
                            FolderSettings.removeEntries(controller.getConfig(), entryId);
                        }
                    }
                    int i = ConfigurationLoader.merge(preConfig, controller.getConfig(),
                            controller.getPreferences(), overwrite);
                    LOG.log(Level.FINE, "Loaded/Merged " + i + " config/prefs entries from: " + clr.getConfigURL());
                    ConfigurationEntry.CONFIG_URL.setValue(controller, clr.getConfigURL());
                    // Seems to be valid, store.
                    controller.saveConfig();
                } else {
                    LOG.log(Level.WARNING, "Unable to load config from " + clr.getConfigURL());
                }
            }
            if (clr.isRestartRequired() && controller.isStarted()) {
                if (controller.getUptime() < 10000L) {
                    controller.schedule(new Runnable() {
                        public void run() {
                            controller.shutdownAndRequestRestart();
                        }
                    }, 10000L);
                } else {
                    controller.shutdownAndRequestRestart();
                }
            }
        } catch (IOException e) {
            LOG.log(Level.SEVERE, "Unable to reload configuration: " + clr + ". " + e, e);
        }
    }

    /**
     * #2467: Set server URL via command line option in installer
     *
     * @param controller
     * @return
     */
    public static boolean loadAndMergeFromInstaller(Controller controller) {
        Path initFile = null;
        String windir = System.getenv("WINDIR");
        if (StringUtils.isNotBlank(windir)) {
            Path tempDir = Paths.get(windir).resolve("TEMP");
            initFile = tempDir.resolve(INITIAL_STARTUP_CONFIG_FILENAME);
        }
        if (initFile == null || Files.notExists(initFile)) {
            String tempStr = System.getProperty("java.io.tmpdir");
            initFile = Paths.get(tempStr).resolve(INITIAL_STARTUP_CONFIG_FILENAME);
            if (Files.notExists(initFile)) {
                return false;
            }
        }
        String url = "";
        boolean delete = false;
        try (InputStream in = Files.newInputStream(initFile)) {
            Properties props = new Properties();
            props.load(in);

            url = props.getProperty(ConfigurationEntry.CONFIG_URL.getConfigKey());
            if (StringUtils.isBlank(url)) {
                String fn = props.getProperty(ConfigurationEntry.INSTALLER_FILENAME.getConfigKey());
                if (StringUtils.isNotBlank(fn)) {
                    url = PathUtils.decodeURLFromFilename(fn);
                }
            }
            if (StringUtils.isBlank(url)) {
                return false;
            }
            Properties preConfig = loadPreConfiguration(url);
            if (preConfig == null) {
                return false;
            }
            int i = merge(preConfig, controller);
            LOG.info("Startup " + i + " with server " + url);
            if (i > 0) {
                ConfigurationEntry.CONFIG_URL.setValue(controller, url);
                controller.saveConfig();
            }
            delete = true;
            return true;
        } catch (Exception e) {
            LOG.warning("Unable to read configuration " + initFile + " / " + url + ". " + e);
        } finally {
            if (delete) {
                try {
                    Files.delete(initFile);
                } catch (IOException ioe) {
                    LOG.fine("Unable to deleted file " + initFile + ". " + ioe);
                }
            }
        }

        return false;
    }

    /**
     * #2179 Loads the the config URL from the command line interface and merges
     * it with the controllers config.
     *
     * @param controller
     * @return if a config was successfully loaded
     */
    public static boolean loadAndMergeCLI(Controller controller) {
        CommandLine cli = controller.getCommandLine();
        if (cli == null) {
            return false;
        }
        String configName = cli.getOptionValue("c");
        if (StringUtils.isNotBlank(configName)
                && (configName.startsWith("http:") || configName.startsWith("https:"))) {
            return loadAndMergeURL(controller, configName);
        }
        return false;
    }

    /**
     * #2179 Loads the the config from the server and merges it with the
     * controllers config.
     *
     * @param controller
     * @return if a config was successfully loaded
     */
    public static boolean loadAndMergeConfigURL(Controller controller) {
        return loadAndMergeURL(controller, ConfigurationEntry.CONFIG_URL.getValue(controller));
    }

    /**
     * #2179 Loads the the config from the URL and merges it with the
     * controllers config.
     *
     * @param controller
     * @return if a config was successfully loaded
     */
    public static boolean loadAndMergeURL(Controller controller, String configURL) {
        Reject.ifNull(controller, "Controller is null");
        if (StringUtils.isBlank(configURL)) {
            return false;
        }
        String un = controller.getCLIUsername();
        char[] pw = Util.toCharArray(controller.getCLIPassword());
        try {
            Properties serverConfig = loadPreConfiguration(configURL, un, pw);
            boolean overWrite = overwriteConfigEntries(serverConfig);
            if (dropFolderSettings(serverConfig)) {
                Set<String> entryIds = FolderSettings.loadEntryIds(controller.getConfig());
                for (String entryId : entryIds) {
                    FolderSettings.removeEntries(controller.getConfig(), entryId);
                }
            }
            int i = merge(serverConfig, controller.getConfig(), controller.getPreferences(), overWrite);

            LOG.info("Loaded " + i + " profile settings (overwrite? " + overWrite + ") from: " + configURL);

            if (i > 0) {
                controller.saveConfig();
            }
            return true;
        } catch (Exception e) {
            LOG.warning("Unable to load config from server: " + configURL + ". " + e);
            return false;
        }
    }

    /**
     * Loads a pre-configuration from a server. Automatically adds HTTP:// and
     * url suffix.
     *
     * @param server
     * @return the loaded config.
     * @throws IOException
     */
    public static Properties loadPreConfiguration(String server) throws IOException {
        return loadPreConfiguration(server, null, null);
    }

    /**
     * Loads a pre-configuration from a server. Automatically adds HTTP:// and
     * url suffix.
     *
     * @param server
     * @return the loaded config.
     * @throws IOException
     */
    public static Properties loadPreConfiguration(String server, String un, char[] pw) throws IOException {
        Reject.ifBlank(server, "Server URL is blank");
        String finalURL = Util.removeLastSlashFromURI(server.replace("download_client", ""));
        if (!finalURL.startsWith("http")) {
            finalURL = "http://" + finalURL;
        }
        if (!finalURL.endsWith(".config") && !finalURL.contains(DEFAULT_PROPERTIES_URI)) {
            finalURL += DEFAULT_PROPERTIES_URI;
        }
        return loadPreConfiguration(new URL(finalURL.replace(" ", "%20")), un, pw);
    }

    /**
     * Loads a pre-configuration from the URL
     *
     * @param from
     *            the URL to load from
     * @return the loaded properties WITHOUT those in config.
     * @throws IOException
     */
    private static Properties loadPreConfiguration(URL from, String un, char[] pw) throws IOException {
        Reject.ifNull(from, "URL is null");
        URLConnection con = from.openConnection();
        if (StringUtils.isNotBlank(un)) {
            String s = un + ":" + Util.toString(pw);
            LoginUtil.clear(pw);
            String base64 = "Basic " + Base64.encodeBytes(s.getBytes("UTF-8"));
            con.setRequestProperty("Authorization", base64);
        }
        con.setConnectTimeout(1000 * URL_CONNECT_TIMEOUT_SECONDS);
        con.setReadTimeout(1000 * URL_CONNECT_TIMEOUT_SECONDS);
        con.connect();
        InputStream in = con.getInputStream();
        try {
            return loadPreConfiguration(in);
        } finally {
            try {
                in.close();
            } catch (Exception e) {
            }
        }
    }

    /**
     * Loads the pre configuration from file from the classpath.
     *
     * @param filename
     *            the filename to load
     * @return the loaded properties
     * @throws IOException
     */
    public static Properties loadPreConfigFromClasspath(String filename) throws IOException {
        InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename);
        if (in == null) {
            throw new FileNotFoundException("File '" + filename + "' not found in classpath");
        }
        try {
            return loadPreConfiguration(in);
        } finally {
            try {
                in.close();
            } catch (Exception e) {
            }
        }
    }

    public static int merge(Properties preConfig, Controller controller) {
        boolean overWrite = overwriteConfigEntries(preConfig);
        if (dropFolderSettings(preConfig)) {
            Set<String> entryIds = FolderSettings.loadEntryIds(controller.getConfig());
            for (String entryId : entryIds) {
                FolderSettings.removeEntries(controller.getConfig(), entryId);
            }
        }
        return merge(preConfig, controller.getConfig(), controller.getPreferences(), overWrite);
    }

    /**
     * Convenient method to combine
     * {@link #mergeConfigs(Properties, Properties, boolean)} and
     * {@link #mergePreferences(Properties, Preferences, boolean)}
     *
     * @param preConfig
     *            the pre config
     * @param targetConfig
     *            the config file to set the pre-configuration values into.
     * @param targetPreferences
     *            the preferences to set the pre-configuration values into.
     * @param replaceExisting
     *            if existing key/value pairs will be overwritten by pairs of
     *            pre config.
     * @return the sum of merged entries.
     */
    public static int merge(Properties preConfig, Properties targetConfig, Preferences targetPreferences,
            boolean replaceExisting) {
        return mergeConfigs(preConfig, targetConfig, replaceExisting)
                + mergePreferences(preConfig, targetPreferences, replaceExisting);
    }

    /**
     * Merges the give pre configuration properties into the target config
     * properties. It can be choosen if existing keys in the target properties
     * should be replaced or not.
     *
     * @param preConfig
     *            the pre config
     * @param targetConfig
     *            the config file to set the pre-configuration values into.
     * @param replaceExisting
     *            if existing key/value pairs will be overwritten by pairs of
     *            pre config.
     * @return the number of merged entries.
     */
    private static int mergeConfigs(Properties preConfig, Properties targetConfig, boolean replaceExisting) {
        Reject.ifNull(preConfig, "PreConfig is null");
        Reject.ifNull(targetConfig, "TargetConfig is null");
        int n = 0;
        for (Object obj : preConfig.keySet()) {
            String key = (String) obj;
            String value = preConfig.getProperty(key);
            if (!targetConfig.containsKey(key) || replaceExisting) {
                Object oldValue = targetConfig.setProperty(key, value);
                if (!key.startsWith(PREFERENCES_PREFIX) && !value.equals(oldValue)) {
                    n++;
                }
                LOG.finer("Preconfigured " + key + "=" + value);
            }
        }
        if (n > 0) {
            LOG.fine(n + " default configurations set");
        } else {
            LOG.finer("No additional default configurations set");
        }
        return n;
    }

    /**
     * PUBLIC because of tests. DO NOT USE. Use
     * {@link #merge(Properties, Properties, Preferences, boolean)} instead.
     * <p>
     * Merges the give pre configuration properties into the target preferences.
     * It can be choosen if existing keys in the target preferences should be
     * replaced or not. Will only set those values from preConfig where the key
     * begins with "pref." and cut it off. "pref.xxx=true" will be set to
     * "xxx=true" in preferences.
     *
     * @param preConfig
     *            the pre config
     * @param targetPreferences
     *            the preferences to set the pre-configuration values into.
     * @param replaceExisting
     *            if existing key/value pairs will be overwritten by pairs of
     *            pre config.
     * @return the number of merged entries.
     */
    public static int mergePreferences(Properties preConfig, Preferences targetPreferences,
            boolean replaceExisting) {
        Reject.ifNull(preConfig, "PreConfig is null");
        Reject.ifNull(targetPreferences, "TargetPreferences is null");
        int n = 0;
        for (Object obj : preConfig.keySet()) {
            String key = (String) obj;
            String value = preConfig.getProperty(key);
            if (!key.startsWith(PREFERENCES_PREFIX)) {
                continue;
            } else {
                key = key.substring(PREFERENCES_PREFIX.length(), key.length());
            }
            boolean entryMissing = "-XXWEIRED-DEFAULT-VALUE"
                    .equals(targetPreferences.get(key, "-XXWEIRED-DEFAULT-VALUE"));
            if (entryMissing || replaceExisting) {
                targetPreferences.put(key, value);
                n++;
                LOG.finer("Preconfigured " + key + "=" + value);
            }
        }
        if (n > 0) {
            LOG.fine(n + " default preferences set");
        } else {
            LOG.finer("No additional default preferences set");
        }
        return n;
    }

    /**
     * Loads a configuration file from the given input stream.
     *
     * @param in
     *            the input stream to read the pre-config from
     * @return the loaded properties.
     * @throws IOException
     */
    private static Properties loadPreConfiguration(InputStream in) throws IOException {
        Reject.ifNull(in, "Unable to load Preconfiguration. Input stream is null");
        Properties preConfig = new Properties();
        preConfig.load(in);
        return preConfig;
    }
}