James Moger
2014-03-21 53a83f67aef203379e43a9ed89ccfcb16c16200e
Implement setting removal for configuration settings
5 files modified
824 ■■■■ changed files
src/main/java/com/gitblit/FileSettings.java 17 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/IStoredSettings.java 768 ●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/WebXmlSettings.java 30 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/models/ServerSettings.java 4 ●●●● patch | view | raw | blame | history
src/test/java/com/gitblit/tests/mock/MemorySettings.java 5 ●●●●● patch | view | raw | blame | history
src/main/java/com/gitblit/FileSettings.java
@@ -103,6 +103,23 @@
        return properties;
    }
    @Override
    public boolean saveSettings() {
        String content = FileUtils.readContent(propertiesFile, "\n");
        for (String key : removals) {
            String regex = "(?m)^(" + regExEscape(key) + "\\s*+=\\s*+)"
                    + "(?:[^\r\n\\\\]++|\\\\(?:\r?\n|\r|.))*+$";
            content = content.replaceAll(regex, "");
        }
        removals.clear();
        FileUtils.writeContent(propertiesFile, content);
        // manually set the forceReload flag because not all JVMs support real
        // millisecond resolution of lastModified. (issue-55)
        forceReload = true;
        return true;
    }
    /**
     * Updates the specified settings in the settings file.
     */
src/main/java/com/gitblit/IStoredSettings.java
@@ -1,374 +1,396 @@
/*
 * Copyright 2011 gitblit.com.
 *
 * Licensed 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 com.gitblit;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.utils.StringUtils;
/**
 * Base class for stored settings implementations.
 *
 * @author James Moger
 *
 */
public abstract class IStoredSettings {
    protected final Logger logger;
    protected final Properties overrides = new Properties();
    public IStoredSettings(Class<? extends IStoredSettings> clazz) {
        logger = LoggerFactory.getLogger(clazz);
    }
    protected abstract Properties read();
    private Properties getSettings() {
        Properties props = read();
        props.putAll(overrides);
        return props;
    }
    /**
     * Returns the list of keys whose name starts with the specified prefix. If
     * the prefix is null or empty, all key names are returned.
     *
     * @param startingWith
     * @return list of keys
     */
    public List<String> getAllKeys(String startingWith) {
        List<String> keys = new ArrayList<String>();
        Properties props = getSettings();
        if (StringUtils.isEmpty(startingWith)) {
            keys.addAll(props.stringPropertyNames());
        } else {
            startingWith = startingWith.toLowerCase();
            for (Object o : props.keySet()) {
                String key = o.toString();
                if (key.toLowerCase().startsWith(startingWith)) {
                    keys.add(key);
                }
            }
        }
        return keys;
    }
    /**
     * Returns the boolean value for the specified key. If the key does not
     * exist or the value for the key can not be interpreted as a boolean, the
     * defaultValue is returned.
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
     */
    public boolean getBoolean(String name, boolean defaultValue) {
        Properties props = getSettings();
        if (props.containsKey(name)) {
            String value = props.getProperty(name);
            if (!StringUtils.isEmpty(value)) {
                return Boolean.parseBoolean(value.trim());
            }
        }
        return defaultValue;
    }
    /**
     * Returns the integer value for the specified key. If the key does not
     * exist or the value for the key can not be interpreted as an integer, the
     * defaultValue is returned.
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
     */
    public int getInteger(String name, int defaultValue) {
        Properties props = getSettings();
        if (props.containsKey(name)) {
            try {
                String value = props.getProperty(name);
                if (!StringUtils.isEmpty(value)) {
                    return Integer.parseInt(value.trim());
                }
            } catch (NumberFormatException e) {
                logger.warn("Failed to parse integer for " + name + " using default of "
                        + defaultValue);
            }
        }
        return defaultValue;
    }
    /**
     * Returns the long value for the specified key. If the key does not
     * exist or the value for the key can not be interpreted as an long, the
     * defaultValue is returned.
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
     */
    public long getLong(String name, long defaultValue) {
        Properties props = getSettings();
        if (props.containsKey(name)) {
            try {
                String value = props.getProperty(name);
                if (!StringUtils.isEmpty(value)) {
                    return Long.parseLong(value.trim());
                }
            } catch (NumberFormatException e) {
                logger.warn("Failed to parse long for " + name + " using default of "
                        + defaultValue);
            }
        }
        return defaultValue;
    }
    /**
     * Returns an int filesize from a string value such as 50m or 50mb
     * @param name
     * @param defaultValue
     * @return an int filesize or defaultValue if the key does not exist or can
     *         not be parsed
     */
    public int getFilesize(String name, int defaultValue) {
        String val = getString(name, null);
        if (StringUtils.isEmpty(val)) {
            return defaultValue;
        }
        return com.gitblit.utils.FileUtils.convertSizeToInt(val, defaultValue);
    }
    /**
     * Returns an long filesize from a string value such as 50m or 50mb
     * @param n
     * @param defaultValue
     * @return a long filesize or defaultValue if the key does not exist or can
     *         not be parsed
     */
    public long getFilesize(String key, long defaultValue) {
        String val = getString(key, null);
        if (StringUtils.isEmpty(val)) {
            return defaultValue;
        }
        return com.gitblit.utils.FileUtils.convertSizeToLong(val, defaultValue);
    }
    /**
     * Returns the char value for the specified key. If the key does not exist
     * or the value for the key can not be interpreted as a char, the
     * defaultValue is returned.
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
     */
    public char getChar(String name, char defaultValue) {
        Properties props = getSettings();
        if (props.containsKey(name)) {
            String value = props.getProperty(name);
            if (!StringUtils.isEmpty(value)) {
                return value.trim().charAt(0);
            }
        }
        return defaultValue;
    }
    /**
     * Returns the string value for the specified key. If the key does not exist
     * or the value for the key can not be interpreted as a string, the
     * defaultValue is returned.
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
     */
    public String getString(String name, String defaultValue) {
        Properties props = getSettings();
        if (props.containsKey(name)) {
            String value = props.getProperty(name);
            if (value != null) {
                return value.trim();
            }
        }
        return defaultValue;
    }
    /**
     * Returns the string value for the specified key.  If the key does not
     * exist an exception is thrown.
     *
     * @param key
     * @return key value
     */
    public String getRequiredString(String name) {
        Properties props = getSettings();
        if (props.containsKey(name)) {
            String value = props.getProperty(name);
            if (value != null) {
                return value.trim();
            }
        }
        throw new RuntimeException("Property (" + name + ") does not exist");
    }
    /**
     * Returns a list of space-separated strings from the specified key.
     *
     * @param name
     * @return list of strings
     */
    public List<String> getStrings(String name) {
        return getStrings(name, " ");
    }
    /**
     * Returns a list of strings from the specified key using the specified
     * string separator.
     *
     * @param name
     * @param separator
     * @return list of strings
     */
    public List<String> getStrings(String name, String separator) {
        List<String> strings = new ArrayList<String>();
        Properties props = getSettings();
        if (props.containsKey(name)) {
            String value = props.getProperty(name);
            strings = StringUtils.getStringsFromValue(value, separator);
        }
        return strings;
    }
    /**
     * Returns a list of space-separated integers from the specified key.
     *
     * @param name
     * @return list of strings
     */
    public List<Integer> getIntegers(String name) {
        return getIntegers(name, " ");
    }
    /**
     * Returns a list of integers from the specified key using the specified
     * string separator.
     *
     * @param name
     * @param separator
     * @return list of integers
     */
    public List<Integer> getIntegers(String name, String separator) {
        List<Integer> ints = new ArrayList<Integer>();
        Properties props = getSettings();
        if (props.containsKey(name)) {
            String value = props.getProperty(name);
            List<String> strings = StringUtils.getStringsFromValue(value, separator);
            for (String str : strings) {
                try {
                    int i = Integer.parseInt(str);
                    ints.add(i);
                } catch (NumberFormatException e) {
                }
            }
        }
        return ints;
    }
    /**
     * Returns a map of strings from the specified key.
     *
     * @param name
     * @return map of string, string
     */
    public Map<String, String> getMap(String name) {
        Map<String, String> map = new LinkedHashMap<String, String>();
        for (String string : getStrings(name)) {
            String[] kvp = string.split("=", 2);
            String key = kvp[0];
            String value = kvp[1];
            map.put(key,  value);
        }
        return map;
    }
    /**
     * Override the specified key with the specified value.
     *
     * @param key
     * @param value
     */
    public void overrideSetting(String key, String value) {
        overrides.put(key, value);
    }
    /**
     * Override the specified key with the specified value.
     *
     * @param key
     * @param value
     */
    public void overrideSetting(String key, int value) {
        overrides.put(key, "" + value);
    }
    /**
     * Override the specified key with the specified value.
     *
     * @param key
     * @param value
     */
    public void overrideSetting(String key, boolean value) {
        overrides.put(key, "" + value);
    }
    /**
     * Tests for the existence of a setting.
     *
     * @param key
     * @return true if the setting exists
     */
    public boolean hasSettings(String key) {
        return getString(key, null) != null;
    }
    /**
     * Updates the values for the specified keys and persists the entire
     * configuration file.
     *
     * @param map
     *            of key, value pairs
     * @return true if successful
     */
    public abstract boolean saveSettings(Map<String, String> updatedSettings);
    /**
     * Merge all settings from the settings parameter into this instance.
     *
     * @param settings
     */
    public void merge(IStoredSettings settings) {
        getSettings().putAll(settings.getSettings());
        overrides.putAll(settings.overrides);
    }
/*
 * Copyright 2011 gitblit.com.
 *
 * Licensed 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 com.gitblit;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeSet;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.gitblit.utils.StringUtils;
/**
 * Base class for stored settings implementations.
 *
 * @author James Moger
 *
 */
public abstract class IStoredSettings {
    protected final Logger logger;
    protected final Properties overrides = new Properties();
    protected final Set<String> removals = new TreeSet<String>();
    public IStoredSettings(Class<? extends IStoredSettings> clazz) {
        logger = LoggerFactory.getLogger(clazz);
    }
    protected abstract Properties read();
    private Properties getSettings() {
        Properties props = read();
        props.putAll(overrides);
        return props;
    }
    /**
     * Returns the list of keys whose name starts with the specified prefix. If
     * the prefix is null or empty, all key names are returned.
     *
     * @param startingWith
     * @return list of keys
     */
    public List<String> getAllKeys(String startingWith) {
        List<String> keys = new ArrayList<String>();
        Properties props = getSettings();
        if (StringUtils.isEmpty(startingWith)) {
            keys.addAll(props.stringPropertyNames());
        } else {
            startingWith = startingWith.toLowerCase();
            for (Object o : props.keySet()) {
                String key = o.toString();
                if (key.toLowerCase().startsWith(startingWith)) {
                    keys.add(key);
                }
            }
        }
        return keys;
    }
    /**
     * Returns the boolean value for the specified key. If the key does not
     * exist or the value for the key can not be interpreted as a boolean, the
     * defaultValue is returned.
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
     */
    public boolean getBoolean(String name, boolean defaultValue) {
        Properties props = getSettings();
        if (props.containsKey(name)) {
            String value = props.getProperty(name);
            if (!StringUtils.isEmpty(value)) {
                return Boolean.parseBoolean(value.trim());
            }
        }
        return defaultValue;
    }
    /**
     * Returns the integer value for the specified key. If the key does not
     * exist or the value for the key can not be interpreted as an integer, the
     * defaultValue is returned.
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
     */
    public int getInteger(String name, int defaultValue) {
        Properties props = getSettings();
        if (props.containsKey(name)) {
            try {
                String value = props.getProperty(name);
                if (!StringUtils.isEmpty(value)) {
                    return Integer.parseInt(value.trim());
                }
            } catch (NumberFormatException e) {
                logger.warn("Failed to parse integer for " + name + " using default of "
                        + defaultValue);
            }
        }
        return defaultValue;
    }
    /**
     * Returns the long value for the specified key. If the key does not
     * exist or the value for the key can not be interpreted as an long, the
     * defaultValue is returned.
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
     */
    public long getLong(String name, long defaultValue) {
        Properties props = getSettings();
        if (props.containsKey(name)) {
            try {
                String value = props.getProperty(name);
                if (!StringUtils.isEmpty(value)) {
                    return Long.parseLong(value.trim());
                }
            } catch (NumberFormatException e) {
                logger.warn("Failed to parse long for " + name + " using default of "
                        + defaultValue);
            }
        }
        return defaultValue;
    }
    /**
     * Returns an int filesize from a string value such as 50m or 50mb
     * @param name
     * @param defaultValue
     * @return an int filesize or defaultValue if the key does not exist or can
     *         not be parsed
     */
    public int getFilesize(String name, int defaultValue) {
        String val = getString(name, null);
        if (StringUtils.isEmpty(val)) {
            return defaultValue;
        }
        return com.gitblit.utils.FileUtils.convertSizeToInt(val, defaultValue);
    }
    /**
     * Returns an long filesize from a string value such as 50m or 50mb
     * @param n
     * @param defaultValue
     * @return a long filesize or defaultValue if the key does not exist or can
     *         not be parsed
     */
    public long getFilesize(String key, long defaultValue) {
        String val = getString(key, null);
        if (StringUtils.isEmpty(val)) {
            return defaultValue;
        }
        return com.gitblit.utils.FileUtils.convertSizeToLong(val, defaultValue);
    }
    /**
     * Returns the char value for the specified key. If the key does not exist
     * or the value for the key can not be interpreted as a char, the
     * defaultValue is returned.
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
     */
    public char getChar(String name, char defaultValue) {
        Properties props = getSettings();
        if (props.containsKey(name)) {
            String value = props.getProperty(name);
            if (!StringUtils.isEmpty(value)) {
                return value.trim().charAt(0);
            }
        }
        return defaultValue;
    }
    /**
     * Returns the string value for the specified key. If the key does not exist
     * or the value for the key can not be interpreted as a string, the
     * defaultValue is returned.
     *
     * @param key
     * @param defaultValue
     * @return key value or defaultValue
     */
    public String getString(String name, String defaultValue) {
        Properties props = getSettings();
        if (props.containsKey(name)) {
            String value = props.getProperty(name);
            if (value != null) {
                return value.trim();
            }
        }
        return defaultValue;
    }
    /**
     * Returns the string value for the specified key.  If the key does not
     * exist an exception is thrown.
     *
     * @param key
     * @return key value
     */
    public String getRequiredString(String name) {
        Properties props = getSettings();
        if (props.containsKey(name)) {
            String value = props.getProperty(name);
            if (value != null) {
                return value.trim();
            }
        }
        throw new RuntimeException("Property (" + name + ") does not exist");
    }
    /**
     * Returns a list of space-separated strings from the specified key.
     *
     * @param name
     * @return list of strings
     */
    public List<String> getStrings(String name) {
        return getStrings(name, " ");
    }
    /**
     * Returns a list of strings from the specified key using the specified
     * string separator.
     *
     * @param name
     * @param separator
     * @return list of strings
     */
    public List<String> getStrings(String name, String separator) {
        List<String> strings = new ArrayList<String>();
        Properties props = getSettings();
        if (props.containsKey(name)) {
            String value = props.getProperty(name);
            strings = StringUtils.getStringsFromValue(value, separator);
        }
        return strings;
    }
    /**
     * Returns a list of space-separated integers from the specified key.
     *
     * @param name
     * @return list of strings
     */
    public List<Integer> getIntegers(String name) {
        return getIntegers(name, " ");
    }
    /**
     * Returns a list of integers from the specified key using the specified
     * string separator.
     *
     * @param name
     * @param separator
     * @return list of integers
     */
    public List<Integer> getIntegers(String name, String separator) {
        List<Integer> ints = new ArrayList<Integer>();
        Properties props = getSettings();
        if (props.containsKey(name)) {
            String value = props.getProperty(name);
            List<String> strings = StringUtils.getStringsFromValue(value, separator);
            for (String str : strings) {
                try {
                    int i = Integer.parseInt(str);
                    ints.add(i);
                } catch (NumberFormatException e) {
                }
            }
        }
        return ints;
    }
    /**
     * Returns a map of strings from the specified key.
     *
     * @param name
     * @return map of string, string
     */
    public Map<String, String> getMap(String name) {
        Map<String, String> map = new LinkedHashMap<String, String>();
        for (String string : getStrings(name)) {
            String[] kvp = string.split("=", 2);
            String key = kvp[0];
            String value = kvp[1];
            map.put(key,  value);
        }
        return map;
    }
    /**
     * Override the specified key with the specified value.
     *
     * @param key
     * @param value
     */
    public void overrideSetting(String key, String value) {
        overrides.put(key, value);
    }
    /**
     * Override the specified key with the specified value.
     *
     * @param key
     * @param value
     */
    public void overrideSetting(String key, int value) {
        overrides.put(key, "" + value);
    }
    /**
     * Override the specified key with the specified value.
     *
     * @param key
     * @param value
     */
    public void overrideSetting(String key, boolean value) {
        overrides.put(key, "" + value);
    }
    /**
     * Tests for the existence of a setting.
     *
     * @param key
     * @return true if the setting exists
     */
    public boolean hasSettings(String key) {
        return getString(key, null) != null;
    }
    /**
     * Remove a setting.
     *
     * @param key
     */
    public void removeSetting(String key) {
        getSettings().remove(key);
        overrides.remove(key);
        removals.add(key);
    }
    /**
     * Saves the current settings.
     *
     * @param map
     */
    public abstract boolean saveSettings();
    /**
     * Updates the values for the specified keys and persists the entire
     * configuration file.
     *
     * @param map
     *            of key, value pairs
     * @return true if successful
     */
    public abstract boolean saveSettings(Map<String, String> updatedSettings);
    /**
     * Merge all settings from the settings parameter into this instance.
     *
     * @param settings
     */
    public void merge(IStoredSettings settings) {
        getSettings().putAll(settings.getSettings());
        overrides.putAll(settings.overrides);
    }
}
src/main/java/com/gitblit/WebXmlSettings.java
@@ -80,6 +80,36 @@
    }
    @Override
    public synchronized boolean saveSettings() {
        try {
            Properties props = new Properties();
            // load pre-existing web-configuration
            if (overrideFile.exists()) {
                InputStream is = new FileInputStream(overrideFile);
                props.load(is);
                is.close();
            }
            // put all new settings and persist
            for (String key : removals) {
                props.remove(key);
            }
            removals.clear();
            OutputStream os = new FileOutputStream(overrideFile);
            props.store(os, null);
            os.close();
            // override current runtime settings
            properties.clear();
            properties.putAll(props);
            return true;
        } catch (Throwable t) {
            logger.error("Failed to save settings!", t);
        }
        return false;
    }
    @Override
    public synchronized boolean saveSettings(Map<String, String> settings) {
        try {
            Properties props = new Properties();
src/main/java/com/gitblit/models/ServerSettings.java
@@ -58,4 +58,8 @@
    public boolean hasKey(String key) {
        return settings.containsKey(key);
    }
    public SettingModel remove(String key) {
        return settings.remove(key);
    }
}
src/test/java/com/gitblit/tests/mock/MemorySettings.java
@@ -48,6 +48,11 @@
    }
    @Override
    public boolean saveSettings() {
        return false;
    }
    @Override
    public boolean saveSettings(Map<String, String> updatedSettings) {
        return false;
    }