/*
* 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.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLConnection;
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";
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) {
File initFile = null;
String windir = System.getenv("WINDIR");
if (StringUtils.isNotBlank(windir)) {
File tempDir = new File(new File(windir), "TEMP");
initFile = new File(tempDir, INITIAL_STARTUP_CONFIG_FILENAME);
}
if (initFile == null || !initFile.exists()) {
String tempStr = System.getProperty("java.io.tmpdir");
initFile = new File(new File(tempStr),
INITIAL_STARTUP_CONFIG_FILENAME);
if (!initFile.exists()) {
return false;
}
}
FileInputStream in = null;
String url = "";
boolean delete = false;
try {
in = new FileInputStream(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 = FileUtils.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);
return false;
} finally {
if (in != null) {
try {
in.close();
} catch (IOException e) {
}
}
if (delete && !initFile.delete()) {
LOG.fine("Unable to deleted file " + initFile);
}
}
}
/**
* #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 + " settings (overwrite? " + overWrite
+ ") from server: " + 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
{
String finalURL = Util.removeLastSlashFromURI(server);
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;
}
}