org.dspace.servicemanager.config.DSpaceConfigurationService.java Source code

Java tutorial

Introduction

Here is the source code for org.dspace.servicemanager.config.DSpaceConfigurationService.java

Source

/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE and NOTICE files at the root of the source
 * tree and available online at
 *
 * http://www.dspace.org/license/
 */
package org.dspace.servicemanager.config;

import java.io.File;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationConverter;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.DefaultConfigurationBuilder;
import org.dspace.services.ConfigurationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.SimpleTypeConverter;
import org.springframework.core.io.ClassPathResource;

/**
 * The central DSpace configuration service. Uses Apache Commons Configuration
 * to provide the ability to reload Property files.
 *
 * @author Tim Donohue (rewrote to use Apache Commons Config
 * @author Aaron Zeckoski
 * @author Kevin Van de Velde
 * @author Mark Diggory
 */
public final class DSpaceConfigurationService implements ConfigurationService {

    private static final Logger log = LoggerFactory.getLogger(DSpaceConfigurationService.class);

    public static final String DSPACE = "dspace";
    public static final String EXT_CONFIG = "cfg";
    public static final String DOT_CONFIG = "." + EXT_CONFIG;

    public static final String DSPACE_HOME = DSPACE + ".dir";
    public static final String DEFAULT_CONFIG_DIR = "config";
    public static final String DEFAULT_CONFIG_DEFINITION_FILE = "config-definition.xml";
    public static final String DSPACE_CONFIG_DEFINITION_PATH = DEFAULT_CONFIG_DIR + File.separatorChar
            + DEFAULT_CONFIG_DEFINITION_FILE;

    public static final String DSPACE_CONFIG_PATH = DEFAULT_CONFIG_DIR + File.separatorChar + DSPACE + DOT_CONFIG;

    // The DSpace Server ID configuration
    public static final String DSPACE_SERVER_ID = "serverId";

    // Current ConfigurationBuilder
    private DefaultConfigurationBuilder configurationBuilder = null;

    // Current Configuration
    private Configuration configuration = null;

    // Current Home directory
    private String homePath = null;

    /**
     * Initializes a ConfigurationService based on default values. The DSpace
     * Home directory is determined based on system properties / searching.
     * <P>
     * See loadInitialConfig() for more details
     */
    public DSpaceConfigurationService() {
        // init and load up current config settings
        loadInitialConfig(null);
    }

    /**
     * Initializes a ConfigurationService based on the provided home directory
     * for DSpace
     * @param providedHome provided home directory
     */
    public DSpaceConfigurationService(String providedHome) {
        loadInitialConfig(providedHome);
    }

    /**
     * Returns all loaded properties as a Properties object.
     *
     * @see org.dspace.services.ConfigurationService#getProperties()
     */
    @Override
    public Properties getProperties() {
        // Return our configuration as a set of Properties
        return ConfigurationConverter.getProperties(configuration);
    }

    /**
     * Returns all Property keys.
     *
     * @see org.dspace.services.ConfigurationService#getPropertyKeys()
     */
    @Override
    public List<String> getPropertyKeys() {

        Iterator<String> keys = configuration.getKeys();

        List<String> keyList = new ArrayList<>();
        while (keys.hasNext()) {
            keyList.add(keys.next());
        }
        return keyList;
    }

    /**
     * Returns all Property keys that begin with a given prefix.
     *
     * @see org.dspace.services.ConfigurationService#getPropertyKeys(java.lang.String)
     */
    @Override
    public List<String> getPropertyKeys(String prefix) {

        Iterator<String> keys = configuration.getKeys(prefix);

        List<String> keyList = new ArrayList<>();
        while (keys.hasNext()) {
            keyList.add(keys.next());
        }
        return keyList;
    }

    /**
     * Returns all loaded properties as a Configuration object.
     *
     * @see org.dspace.services.ConfigurationService#getConfiguration()
     */
    @Override
    public Configuration getConfiguration() {
        return configuration;
    }

    /**
     * Returns property value as an Object.
     * If property is not found, null is returned.
     *
     * @see org.dspace.services.ConfigurationService#getPropertyValue(java.lang.String)
     */
    @Override
    public Object getPropertyValue(String name) {
        return configuration.getProperty(name);
    }

    /**
     * Returns property value as a String.
     * If property is not found, null is returned.
     *
     * @see org.dspace.services.ConfigurationService#getProperty(java.lang.String)
     */
    @Override
    public String getProperty(String name) {
        return getProperty(name, null);
    }

    /**
     * Returns property value as a String.
     * If property is not found, default value is returned.
     *
     * @see org.dspace.services.ConfigurationService#getProperty(java.lang.String, java.lang.String)
     */
    @Override
    public String getProperty(String name, String defaultValue) {
        return (String) getPropertyAsType(name, defaultValue);
    }

    /**
     * Returns property value as an array.
     * If property is not found, an empty array is returned.
     *
     * @see org.dspace.services.ConfigurationService#getArrayProperty(java.lang.String)
     */
    @Override
    public String[] getArrayProperty(String name) {
        return getArrayProperty(name, new String[0]);
    }

    /**
     * Returns property value as an array.
     * If property is not found, default value is returned.
     *
     * @see org.dspace.services.ConfigurationService#getArrayProperty(java.lang.String, java.lang.String[])
     */
    @Override
    public String[] getArrayProperty(String name, String[] defaultValue) {
        return getPropertyAsType(name, defaultValue);
    }

    /**
     * Returns property value as a boolean value.
     * If property is not found, false is returned.
     *
     * @see org.dspace.services.ConfigurationService#getBooleanProperty(java.lang.String)
     */
    @Override
    public boolean getBooleanProperty(String name) {
        return getBooleanProperty(name, false);
    }

    /**
     * Returns property value as a boolean value.
     * If property is not found, default value is returned.
     *
     * @see org.dspace.services.ConfigurationService#getBooleanProperty(java.lang.String, boolean)
     */
    @Override
    public boolean getBooleanProperty(String name, boolean defaultValue) {
        return getPropertyAsType(name, defaultValue);
    }

    /**
     * Returns property value as an int value.
     * If property is not found, 0 is returned.
     * <P>
     * If you wish to avoid the 0 return value, you can use
     * hasProperty() to first determine whether the property
     * exits. Or, use getIntProperty(name,defaultValue).
     *
     * @see org.dspace.services.ConfigurationService#getIntProperty(java.lang.String)
     */
    @Override
    public int getIntProperty(String name) {
        return getIntProperty(name, 0);
    }

    /**
     * Returns property value as an int value.
     * If property is not found, default value is returned.
     *
     * @see org.dspace.services.ConfigurationService#getIntProperty(java.lang.String, int)
     */
    @Override
    public int getIntProperty(String name, int defaultValue) {
        return getPropertyAsType(name, defaultValue);
    }

    /**
     * Returns property value as a long value.
     * If property is not found, 0 is returned.
     * <P>
     * If you wish to avoid the 0 return value, you can use
     * hasProperty() to first determine whether the property
     * exits. Or, use getLongProperty(name,defaultValue).
     *
     * @see org.dspace.services.ConfigurationService#getLongProperty(java.lang.String)
     */
    @Override
    public long getLongProperty(String name) {
        return getLongProperty(name, 0);
    }

    /**
     * Returns property value as a long value.
     * If property is not found, default value is returned.
     *
     * @see org.dspace.services.ConfigurationService#getLongProperty(java.lang.String,long)
     */
    @Override
    public long getLongProperty(String name, long defaultValue) {
        return getPropertyAsType(name, defaultValue);
    }

    /* (non-Javadoc)
     * @see org.dspace.services.ConfigurationService#getPropertyAsType(java.lang.String, java.lang.Class)
     */
    @Override
    public <T> T getPropertyAsType(String name, Class<T> type) {
        return convert(name, type);
    }

    /* (non-Javadoc)
     * @see org.dspace.services.ConfigurationService#getPropertyAsType(java.lang.String, java.lang.Object)
     */
    @Override
    public <T> T getPropertyAsType(String name, T defaultValue) {
        return getPropertyAsType(name, defaultValue, false);
    }

    /* (non-Javadoc)
     * @see org.dspace.services.ConfigurationService#getPropertyAsType(java.lang.String, java.lang.Object, boolean)
     */
    @Override
    public <T> T getPropertyAsType(String name, T defaultValue, boolean setDefaultIfNotFound) {

        // If this key doesn't exist, immediately return a value
        if (!hasProperty(name)) {
            // if flag is set, save the default value as the new value for this property
            if (setDefaultIfNotFound) {
                setProperty(name, defaultValue);
            }

            // Either way, return our default value as if it was the setting
            return defaultValue;
        }

        // Avoid NPE. If null defaultValue passed in, assume Object class
        Class type = Object.class;
        if (defaultValue != null) {
            // Get the class associated with our default value
            type = defaultValue.getClass();
        }

        return (T) convert(name, type);
    }

    /**
     * Determine if a given property key exists within the currently loaded configuration
     * @see org.dspace.services.ConfigurationService#hasProperty(java.lang.String)
     */
    @Override
    public boolean hasProperty(String name) {
        if (configuration.containsKey(name))
            return true;
        else
            return false;
    }

    /* (non-Javadoc)
     * @see org.dspace.services.ConfigurationService#setProperty(java.lang.String, java.lang.Object)
     */
    @Override
    public synchronized boolean setProperty(String name, Object value) {
        // If the value is a type of String, trim any leading/trailing spaces before saving it.
        if (value != null && String.class.isInstance(value)) {
            value = ((String) value).trim();
        }

        boolean changed = false;
        if (name == null) {
            throw new IllegalArgumentException("name cannot be null for setting configuration");
        } else {
            Object oldValue = configuration.getProperty(name);

            if (value == null && oldValue != null) {
                changed = true;
                configuration.clearProperty(name);
                log.info("Cleared the configuration setting for name (" + name + ")");
            } else if (value != null && !value.equals(oldValue)) {
                changed = true;
                configuration.setProperty(name, value);
            }
        }
        return changed;
    }

    /**
     * Load (i.e. Add) a series of properties into the configuration.
     * Checks to see if the settings exist or are changed and only loads
     * changes.
     * <P>
     * This only adds/updates configurations, if you wish to first clear all
     * existing configurations, see clear() method.
     *
     * @param properties a map of key to value settings
     * @return the list of changed configuration keys
     */
    public String[] loadConfiguration(Map<String, Object> properties) {
        if (properties == null) {
            throw new IllegalArgumentException("properties cannot be null");
        }

        ArrayList<String> changed = new ArrayList<String>();

        // loop through each new property entry
        for (Entry<String, Object> entry : properties.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();

            // Load this new individual key
            boolean updated = loadConfig(key, value);

            // If it was updated, add to our list of changed settings
            if (updated) {
                changed.add(key);
            }
        }

        // Return an array of updated keys
        return changed.toArray(new String[changed.size()]);
    }

    /**
     * Loads (i.e. Adds) a single additional config setting into the system.
     * @param key configuration key to add
     * @param value configuration value to add
     * @return true if the config is new or changed
     */
    public boolean loadConfig(String key, Object value) {
        if (key == null) {
            throw new IllegalArgumentException("key cannot be null");
        }

        // Check if the value has changed
        if (this.configuration.containsKey(key) && this.configuration.getProperty(key).equals(value)) {
            // no change to the value
            return false;
        } else {
            // Either this config doesn't exist, or it is not the same value,
            // so we'll update it.
            this.configuration.setProperty(key, value);
            return true;
        }
    }

    /**
     * Clears all the configuration settings.
     */
    public void clear() {
        this.configuration.clear();
        log.info("Cleared all configuration settings");
    }

    /**
     * Clears a single configuration
     * @param key key of the configuration
     */
    public void clearConfig(String key) {
        this.configuration.clearProperty(key);
    }

    // loading from files code

    /**
     * Loads up the configuration from the DSpace configuration files.
     * <P>
     * Determines the home directory of DSpace, and then loads the configurations
     * based on the configuration definition file in that location
     * (using Apache Commons Configuration).
     * @param providedHome DSpace home directory, or null.
     */
    private void loadInitialConfig(String providedHome) {
        // Determine the DSpace home directory
        homePath = getDSpaceHome(providedHome);

        // Based on homePath get full path to the configuration definition
        String configDefinition = homePath + File.separatorChar + DSPACE_CONFIG_DEFINITION_PATH;

        // Check if our configuration definition exists in the homePath
        File configDefFile = new File(configDefinition);
        if (!configDefFile.exists()) {
            try {
                //If it doesn't exist, check for a configuration definition on Classpath
                // (NOTE: This is mostly for Unit Testing to find the test config-definition.xml)
                ClassPathResource resource = new ClassPathResource(DSPACE_CONFIG_DEFINITION_PATH);
                configDefinition = resource.getFile().getAbsolutePath();
            } catch (IOException ioe) {
                log.error("Error attempting to load configuration definition from classpath", ioe);
            }
        }

        try {
            // Load our configuration definition, which in turn loads all our config files/settings
            // See: http://commons.apache.org/proper/commons-configuration/userguide_v1.10/howto_configurationbuilder.html
            configurationBuilder = new DefaultConfigurationBuilder(configDefinition);

            // Actually parser our configuration definition & return the resulting Configuration
            configuration = configurationBuilder.getConfiguration();
        } catch (ConfigurationException ce) {
            log.error("Unable to load configurations based on definition at " + configDefinition);
            System.err.println("Unable to load configurations based on definition at " + configDefinition);
            throw new RuntimeException(ce);
        }

        // Finally, set any dynamic, default properties
        setDynamicProperties();

        log.info("Started up configuration service and loaded settings: " + toString());
    }

    /**
     * Reload the configuration from the DSpace configuration files.
     * <P>
     * Uses the initialized ConfigurationBuilder to reload all configurations.
     */
    @Override
    public synchronized void reloadConfig() {
        try {
            configurationBuilder.reload();
            this.configuration = configurationBuilder.getConfiguration();

            // Finally, (re)set any dynamic, default properties
            setDynamicProperties();
        } catch (ConfigurationException ce) {
            log.error("Unable to reload configurations based on definition at "
                    + configurationBuilder.getFile().getAbsolutePath(), ce);
        }
        log.info("Reloaded configuration service: " + toString());
    }

    /**
    * Sets properties which are determined dynamically rather than
    * loaded via configuration.
    */
    private void setDynamicProperties() {
        // Ensure our DSPACE_HOME property is set to the determined homePath
        setProperty(DSPACE_HOME, homePath);

        try {
            // Attempt to set a default "serverId" property to value of hostname
            String defaultServerId = InetAddress.getLocalHost().getHostName();
            setProperty(DSPACE_SERVER_ID, defaultServerId);
        } catch (UnknownHostException e) {
            // oh well
        }
    }

    @Override
    public String toString() {
        // Get the size of the generated Properties
        Properties props = getProperties();
        int size = props != null ? props.size() : 0;

        // Return the configuration directory and number of configs loaded
        return "ConfigDir=" + configuration.getString(DSPACE_HOME) + File.separatorChar + DEFAULT_CONFIG_DIR
                + ", Size=" + size;
    }

    /**
     * This attempts to find the DSpace home directory, based on system properties,
     * and the providedHome (if not null).
     * <p>The initial value of {@code dspace.dir} will be:</p>
     * <ol>
     *  <li>the value of the system property {@code dspace.dir} if defined;</li>
     *  <li>else the value of {@code providedHome} if not null;</li>
     *  <li>else the servlet container's home + "/dspace/" if defined (see {@link DSpaceConfigurationService#getCatalina()});</li>
     *  <li>else the user's home directory if defined;</li>
     *  <li>else "/".</li>
     * </ol>
     * @param providedHome provided home directory (may be null)
     * @return full path to DSpace home
     */
    protected String getDSpaceHome(String providedHome) {
        // See if valid home specified as system property (most trusted)
        String sysProperty = System.getProperty(DSPACE_HOME);
        if (isValidDSpaceHome(sysProperty)) {
            return sysProperty;
        }

        // See if valid home passed in
        if (isValidDSpaceHome(providedHome)) {
            return providedHome;
        }

        // If still not found, attempt to determine location of our JAR
        String pathRelativeToJar = null;
        try {
            // Check location of our running JAR
            URL jarLocation = getClass().getProtectionDomain().getCodeSource().getLocation();
            // Convert to a file & get "grandparent" directory
            // This JAR should be running in [dspace]/lib/, so its parent is [dspace]/lib, and grandparent is [dspace]
            pathRelativeToJar = new File(jarLocation.toURI()).getParentFile().getParentFile().getAbsolutePath();
            // Is the grandparent directory of where the JAR resides a valid DSpace home?
            if (isValidDSpaceHome(pathRelativeToJar)) {
                return pathRelativeToJar;
            }
        } catch (URISyntaxException e) { // do nothing
        }

        // If still not valid, check Catalina
        String catalina = getCatalina();
        if (isValidDSpaceHome(catalina)) {
            return catalina;
        }

        // If still not valid, check "user.home" system property
        String userHome = System.getProperty("user.home");
        if (isValidDSpaceHome(userHome)) {
            return userHome;
        }

        // Finally, try root path ("/")
        if (isValidDSpaceHome("/")) {
            return "/";
        }

        // If none of the above worked, DSpace Kernel will fail to start.
        throw new RuntimeException("DSpace home directory could not be determined. It MUST include a subpath of "
                + "'" + File.separatorChar + DSPACE_CONFIG_DEFINITION_PATH + "'. " + "Please consider setting the '"
                + DSPACE_HOME + "' system property or ensure the dspace-api.jar is being run from [dspace]/lib/.");
    }

    /**
     * Returns whether a given path seems to have the required DSpace configurations
     * in order to make it a valid DSpace home directory
     * @param path path to validate
     * @return true if path seems valid, false otherwise
     */
    protected boolean isValidDSpaceHome(String path) {
        // If null path, return false immediately
        if (path == null)
            return false;

        // Based on path get full path to the configuration definition
        String configDefinition = path + File.separatorChar + DSPACE_CONFIG_DEFINITION_PATH;
        File configDefFile = new File(configDefinition);

        // Check if the required config exists
        if (configDefFile.exists())
            return true;
        else
            return false;
    }

    /**
     * This simply attempts to find the servlet container home for tomcat.
     * @return the path to the servlet container home OR null if it cannot be found
     */
    protected String getCatalina() {
        String catalina = System.getProperty("catalina.base");
        if (catalina == null) {
            catalina = System.getProperty("catalina.home");
        }
        return catalina;
    }

    /**
     * Convert the value of a given property to a specific object type.
     * <P>
     * Note: in most cases we can just use Configuration get*() methods.
     *
     * @param name Key of the property to convert
     * @param <T> object type
     * @return converted value
     */
    private <T> T convert(String name, Class<T> type) {

        // If this key doesn't exist, just return null
        if (!configuration.containsKey(name)) {
            // Special case. For booleans, return false if key doesn't exist
            if (Boolean.class.equals(type) || boolean.class.equals(type))
                return (T) Boolean.FALSE;
            else
                return null;
        }

        // Based on the type of class, call the appropriate
        // method of the Configuration object
        if (type.isArray())
            return (T) configuration.getStringArray(name);
        else if (String.class.equals(type) || type.isAssignableFrom(String.class))
            return (T) configuration.getString(name);
        else if (BigDecimal.class.equals(type))
            return (T) configuration.getBigDecimal(name);
        else if (BigInteger.class.equals(type))
            return (T) configuration.getBigInteger(name);
        else if (Boolean.class.equals(type) || boolean.class.equals(type))
            return (T) Boolean.valueOf(configuration.getBoolean(name));
        else if (Byte.class.equals(type) || byte.class.equals(type))
            return (T) Byte.valueOf(configuration.getByte(name));
        else if (Double.class.equals(type) || double.class.equals(type))
            return (T) Double.valueOf(configuration.getDouble(name));
        else if (Float.class.equals(type) || float.class.equals(type))
            return (T) Float.valueOf(configuration.getFloat(name));
        else if (Integer.class.equals(type) || int.class.equals(type))
            return (T) Integer.valueOf(configuration.getInt(name));
        else if (List.class.equals(type))
            return (T) configuration.getList(name);
        else if (Long.class.equals(type) || long.class.equals(type))
            return (T) Long.valueOf(configuration.getLong(name));
        else if (Short.class.equals(type) || short.class.equals(type))
            return (T) Short.valueOf(configuration.getShort(name));
        else {
            // If none of the above works, try to convert the value to the required type
            SimpleTypeConverter converter = new SimpleTypeConverter();
            return (T) converter.convertIfNecessary(configuration.getProperty(name), type);
        }
    }
}