
org.dspace.servicemanager.config.DSpaceConfigurationService Maven / Gradle / Ivy
/**
* 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.
*
* 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 getPropertyKeys() {
Iterator keys = configuration.getKeys();
List 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 getPropertyKeys(String prefix) {
Iterator keys = configuration.getKeys(prefix);
List 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.
*
* 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.
*
* 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 getPropertyAsType(String name, Class type) {
return convert(name, type);
}
/* (non-Javadoc)
* @see org.dspace.services.ConfigurationService#getPropertyAsType(java.lang.String, java.lang.Object)
*/
@Override
public 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 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.
*
* 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 properties) {
if (properties == null) {
throw new IllegalArgumentException("properties cannot be null");
}
ArrayList changed = new ArrayList();
// loop through each new property entry
for (Entry 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.
*
* 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.
*
* 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).
*
The initial value of {@code dspace.dir} will be:
*
* - the value of the system property {@code dspace.dir} if defined;
* - else the value of {@code providedHome} if not null;
* - else the servlet container's home + "/dspace/" if defined (see {@link DSpaceConfigurationService#getCatalina()});
* - else the user's home directory if defined;
* - else "/".
*
* @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.
*
* Note: in most cases we can just use Configuration get*() methods.
*
* @param name Key of the property to convert
* @param object type
* @return converted value
*/
private T convert(String name, Class 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);
}
}
}