/*
* RapidMiner
*
* Copyright (C) 2001-2011 by Rapid-I and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapid-i.com
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see http://www.gnu.org/licenses/.
*/
package com.rapidminer.tools;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.rapid_i.Launcher;
import com.rapidminer.RapidMiner;
import com.rapidminer.gui.tools.VersionNumber;
import com.rapidminer.operator.ExecutionMode;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.tools.parameter.Parameter;
import com.rapidminer.tools.parameter.ParameterChangeListener;
import com.rapidminer.tools.parameter.ParameterScope;
import com.rapidminer.tools.parameter.ParameterWriter;
import com.rapidminer.tools.parameter.WindowsBatParameterWriter;
import com.rapidminer.tools.parameter.WindowsExeParameterWriter;
/**
* This class loads the RapidMiner property files and provides methods to access
* them. It also stores the values of the properties. They are still mirrored in the
* System properties for keeping compatibility but it is strongly recommended to use
* this class to get access.
*
* During init this class will try to load settings from various sources. Sources with
* a higher specificy will overwrite settings with a lower. The sequence is as follows while only the first step is executed
* if the {@link ExecutionMode} forbidds file access:
* <ol>
* <li>loading the factory defaults from /com/rapidminer/resources/rapidminerrc from the rapidminer.jar.</li>
* <li>if the system property <code>rapidminer.config.dir</code> is set, the file rapidminerrc inside this directory will be loaded.</li>
* <li>if the property is not set, the environment variable <code>RAPIDMINER_CONFIG_DIR</code> will be evaluated in the same way.</li>
* <li>the file rapidminerrc in the user's .Rapidminer5 directory will be loaded.</li>
* <li>the file rapidminerrc.os_type in the user's .Rapidminer5 directory will be loaded.</li>
* <li>the file rapidminerrc in the user's home directory will be loaded.</li>
* <li>the file rapidminerrc.os_type in the user's home directory will be loaded.</li>
* <li>the file denoted by the System property <code>rapidminer.rcfile</code> will be loaded if defined.</li>
* </ol>
* It also provides methods to create files relative to the RapidMiner home
* directory. The way to access the properties via System.getProperty is deprecated and
* should be replaced by #getProperty(String).
*
* @author Simon Fischer, Ingo Mierswa, Sebastian Land
*/
public class ParameterService {
private static final Logger LOGGER = Logger.getLogger(ParameterService.class.getSimpleName());
public static final String RAPIDMINER_CONFIG_FILE_NAME = "rapidminerrc";
/** Property specifying the root directory of the RapidMiner project sources. */
public static final String PROPERTY_RAPIDMINER_SRC_ROOT = "rapidminer.src.root";
public static final String PROPERTY_RAPIDMINER_CONFIG_DIR = "rapidminer.config.dir";
public static final String ENVIRONMENT_RAPIDMINER_CONFIG_DIR = "RAPIDMINER_CONFIG_DIR";
private static boolean intialized = false;
private static final List<ParameterChangeListener> PARAMETER_LISTENERS = new LinkedList<ParameterChangeListener>();
private static final Map<String, Parameter> PARAMETER_MAP = new TreeMap<String, Parameter>();
private static final List<ParameterWriter> PARAMETER_WRITERS = new LinkedList<ParameterWriter>();
static {
PARAMETER_WRITERS.add(new WindowsExeParameterWriter());
PARAMETER_WRITERS.add(new WindowsBatParameterWriter());
}
/**
* Reads the configuration file if allowed by the {@link com.rapidminer.operator.ExecutionMode}.
*/
public static void init() {
if (!intialized) {
// first search for factory default values of parameters
InputStream in = ParameterService.class.getResourceAsStream("/" + Tools.RESOURCE_PREFIX + "rapidminerrc");
if (in != null) {
LOGGER.info("Reading configuration resource " + Tools.RESOURCE_PREFIX + "rapidminerrc.");
try {
setParameters(in);
} catch (IOException e) {
LOGGER.warning("Failed to access resource " + Tools.RESOURCE_PREFIX + "rapidminerrc. Reason:" + e.getMessage());
}
} else {
LOGGER.warning("Resource " + Tools.RESOURCE_PREFIX + "rapidminerrc is missing.");
}
// then try to read configuration from file system if allowed to do so.
if (RapidMiner.getExecutionMode().canAccessFilesystem()) {
List<File> configFilesList = new LinkedList<File>();
// adding global config file defined by parameter or environment variable
File globalConfigFile = getGlobalConfigFile("rapidminerrc");
if (globalConfigFile != null) {
configFilesList.add(globalConfigFile);
configFilesList.add(getOSDependentFile(globalConfigFile));
}
// add user specific config file from .rapidminer5 directory
File userConfigFile = FileSystemService.getUserConfigFile("rapidminerrc");
if (userConfigFile != null) {
configFilesList.add(userConfigFile);
configFilesList.add(getOSDependentFile(userConfigFile));
}
// if user has special rapidminerrc file in his home directory, use it instead.
File userSpecifiedConfigFile = new File(System.getProperty("user.dir"), "rapidminerrc");
if (userSpecifiedConfigFile != null) {
configFilesList.add(userSpecifiedConfigFile);
configFilesList.add(getOSDependentFile(userSpecifiedConfigFile));
}
// finally if user has given a parameter to specify the RC file, this one is used
String parameterConfigFilePath = System.getProperty(RapidMiner.PROPERTY_RAPIDMINER_RC_FILE);
if (parameterConfigFilePath != null) {
configFilesList.add(new File(parameterConfigFilePath));
}
// finally read all collected files if existing
for (File configFile : configFilesList) {
if (configFile.exists()) {
try {
setParameters(configFile);
LOGGER.config("Trying rcfile '" + configFile.getAbsolutePath() + "'...success");
} catch (FileNotFoundException e) {
LOGGER.config("Trying rcfile '" + configFile.getAbsolutePath() + "'...skipped");
} catch (IOException e) {
LOGGER.config("Trying rcfile '" + configFile.getAbsolutePath() + "'...skipped");
}
}
}
} else {
LOGGER.config("Execution mode " + RapidMiner.getExecutionMode() + " does not permit file access. Ignoring all rcfiles.");
}
// set flag to avoid second call
intialized = true;
}
}
/**
* This method sets the given parameter to the given value. If the parameter is not known, yet,
* it will be added as a defined parameter with a default scope.
*/
public static void setParameterValue(ParameterType type, String value) {
Parameter parameter = PARAMETER_MAP.get(type.getKey());
if (parameter == null) {
parameter = new Parameter(type, value);
PARAMETER_MAP.put(type.getKey(), parameter);
}
setParameterValue(type.getKey(), value);
}
/**
* This method sets the parameter with the given key to the given value. If the parameter does not
* yet exist a new non defined parameter will be created. The value can then be retrieved but it won't be
* saved in any configuration file and will be lost after restarting RapidMiner.
*
* For compatibility reasons this will set the parameter also in the System properties. This might be removed
* in further versions.
*/
public static void setParameterValue(String key, String value) {
// this might be removed later. It remains only for compatibility
if (System.getProperty(key) == null)
System.setProperty(key, value);
// setting parameter
Parameter parameter = PARAMETER_MAP.get(key);
if (parameter == null) {
parameter = new Parameter(value);
PARAMETER_MAP.put(key, parameter);
}
parameter.setValue(value);
informListenerOfChange(key, value);
}
/**
* This method returns the value of the given parameter or null if this parameter is unknown.
* For compatibility reasons this will return defined parameters as well as undefined.
*/
public static String getParameterValue(String key) {
Parameter parameter = PARAMETER_MAP.get(key);
if (parameter != null)
return parameter.getValue();
return null;
}
/**
* This will return the group of the parameter with the given key. If
* the key is unknown, null will be returned.
*/
public static String getGroupKey(String key) {
Parameter parameter = PARAMETER_MAP.get(key);
if (parameter != null) {
return parameter.getGroup();
}
return null;
}
/**
* This method returns the type of the defined parameter identified by key or null if this key is unknown.
*/
public static ParameterType getParameterType(String key) {
Parameter parameter = PARAMETER_MAP.get(key);
if (parameter != null)
return parameter.getType();
return null;
}
/**
* This method returns all keys of all parameters, implicit as
* well as defined ones.
*/
public static Collection<String> getParameterKeys() {
return PARAMETER_MAP.keySet();
}
/**
* This method will return a Collection of all keys of defined parameter types.
* Undefined types will not be returned.
*/
public static Collection<String> getDefinedParameterKeys() {
LinkedList<String> keys = new LinkedList<String>();
for (Entry<String, Parameter> entry : PARAMETER_MAP.entrySet()) {
if (entry.getValue().isDefined())
keys.add(entry.getKey());
}
return keys;
}
/**
* This method will return a Collection of all keys of defined parameter types.
* Undefined types will not be returned.
*/
public static Set<ParameterType> getDefinedParameterTypes() {
HashSet<ParameterType> types = new HashSet<ParameterType>();
for (Entry<String, Parameter> entry : PARAMETER_MAP.entrySet()) {
if (entry.getValue().isDefined())
types.add(entry.getValue().getType());
}
return types;
}
/**
* This method returns the operating system dependent file of the given file.
*/
private static File getOSDependentFile(File file) {
return new File(file.getAbsoluteFile() + "." + System.getProperty("os.name"));
}
/**
* This sets the parameters to the values given by a properties file denoted
* by the given file object.
*
* @throws IOException
* @throws FileNotFoundException
*/
public static void setParameters(File file) throws FileNotFoundException, IOException {
setParameters(new FileInputStream(file));
}
/**
* This method reads the input stream that streams in a properties file and
* sets the parameter values accordingly. If the stream cannot be accessed an
* exception will be thrown.
*
* @throws IOException
*/
public static void setParameters(InputStream in) throws IOException {
Properties properties = new Properties();
properties.load(in);
for (Map.Entry<Object, Object> entry : properties.entrySet()) {
setParameterValue((String) entry.getKey(), (String) entry.getValue());
}
}
/**
* This returns the file with the given fileName from the directory denoted by first the Parameter named
* {@value #PROPERTY_RAPIDMINER_CONFIG_DIR} and if this one is not defined by the environment variable
* {@value #ENVIRONMENT_RAPIDMINER_CONFIG_DIR}.
* If neither one is defined, null is returned.
*/
public static File getGlobalConfigFile(String fileName) {
File dir = getGlobalConfigDir();
if (dir != null) {
File result = new File(dir, fileName);
if (result.exists()) {
if (result.canRead()) {
return result;
} else {
LOGGER.warning("Config file " + result.getAbsolutePath() + " is not readable.");
return null;
}
} else {
return null;
}
} else {
return null;
}
}
private static File getGlobalConfigDir() {
String configDir = System.getProperty(PROPERTY_RAPIDMINER_CONFIG_DIR);
if (configDir == null) {
configDir = System.getenv(ENVIRONMENT_RAPIDMINER_CONFIG_DIR);
}
if (configDir != null) {
File dir = new File(configDir);
if (dir.exists()) {
if (dir.canRead()) {
return dir;
} else {
LOGGER.warning("Directory " + dir.getAbsolutePath() + " specified by environment variable " + ENVIRONMENT_RAPIDMINER_CONFIG_DIR + " is not readable.");
return null;
}
} else {
LOGGER.warning("Directory " + dir.getAbsolutePath() + " specified by environment variable " + ENVIRONMENT_RAPIDMINER_CONFIG_DIR + " does not exist.");
return null;
}
} else {
return null;
}
}
/**
* This method will copy the operating system specific config file from the RapidMiner user directory
* from the last version to the next.
*/
public static void copyMainUserConfigFile(VersionNumber oldVersion, VersionNumber newVersion) {
try {
Tools.copy(FileSystemService.getVersionedUserConfigFile(oldVersion, "rapidminerrc" + "." + System.getProperty("os.name")), getMainUserConfigFile());
} catch (IOException e) {
LOGGER.warning("Could not copy version dependent configuration file!");
}
}
/**
* This method will save all currently known defined parameter types into the
* version and os dependent config file in the user's RapidMiner directory.
* This method will also generate files that are needed for preStartParameter
* that affect as environment variables the staring JVM.
* If file access isn't allowed nothing is done at all.
*/
public static void saveParameters() {
saveParameters(FileSystemService.getMainUserConfigFile());
// now export properties using all additional registered writers
for (ParameterWriter writer: PARAMETER_WRITERS) {
writer.writeParameters(PARAMETER_MAP);
}
informListenerOfSave();
}
/**
* This method will save all currently known defined parameters into
* the given file.
* If file access isn't allowed by the execution mode, nothing is done.
*
* Please notice that in contrast to {@link #saveParameters()}, no preStartParameter
* files are written.
*/
public static void saveParameters(File configFile) {
if (!RapidMiner.getExecutionMode().canAccessFilesystem()) {
LogService.getRoot().config("Ignoring request to save properties file in execution mode " + RapidMiner.getExecutionMode() + ".");
return;
}
// building properties object to save to file
Properties properties = new Properties();
for (Entry<String, Parameter> entry : PARAMETER_MAP.entrySet()) {
Parameter parameter = entry.getValue();
if (parameter.getValue() != null) {
properties.put(entry.getKey(), parameter.getValue());
}
}
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(configFile));
properties.store(out, "");
} catch (FileNotFoundException e) {
LogService.getRoot().log(Level.WARNING, "Cannot write user properties: " + e.getMessage(), e);
} catch (IOException e) {
LogService.getRoot().log(Level.WARNING, "Cannot write user properties: " + e.getMessage(), e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
LogService.getRoot().log(Level.WARNING, "Cannot close user properties file: " + e.getMessage(), e);
}
}
}
}
/**
* This method lets register the given {@link ParameterType} with defaults
* settings. To have more control over the scope and group name refer to
* any other registerParameter method.
*
* If a implicite Parameter is already defined with the key, it will be converted to an explict
* without loosing the data.
*/
public static void registerParameter(ParameterType type) {
Parameter parameter = PARAMETER_MAP.get(type.getKey());
if (parameter == null) {
parameter = new Parameter(type);
PARAMETER_MAP.put(type.getKey(), parameter);
} else {
parameter.setType(type);
}
}
/**
* This method allows to set the group explicitly rather than deriving it from the
* key.
*/
public static void registerParameter(ParameterType type, String group) {
Parameter parameter = PARAMETER_MAP.get(type.getKey());
if (parameter == null) {
parameter = new Parameter(type, group);
PARAMETER_MAP.put(type.getKey(), parameter);
} else {
parameter.setType(type);
parameter.setGroup(group);
}
}
/**
* This method can be used to register the given {@link ParameterType} on the given
* {@link ParameterScope}. This method can be used to define for example preStartParameters
* like memory size...
*/
public static void registerParameter(ParameterType type, String group, ParameterScope scope) {
Parameter parameter = PARAMETER_MAP.get(type.getKey());
if (parameter == null) {
parameter = new Parameter(type, group);
PARAMETER_MAP.put(type.getKey(), parameter);
} else {
parameter.setType(type);
parameter.setGroup(group);
}
parameter.setScope(scope);
}
/**
* Tries to find the rapidminer.home directory if the property is not set and sets
* it.
*/
public static void ensureRapidMinerHomeSet() {
Launcher.ensureRapidMinerHomeSet();
}
private static void informListenerOfChange(String key, String value) {
for (ParameterChangeListener listener: PARAMETER_LISTENERS) {
listener.informParameterChanged(key, value);
}
}
private static void informListenerOfSave() {
for (ParameterChangeListener listener: PARAMETER_LISTENERS) {
listener.informParameterSaved();
}
}
/**
* This will add the given listener to the list of listers. It will be informed
* whenever a setting is changed.
*/
public static void registerParameterChangeListener(ParameterChangeListener listener) {
PARAMETER_LISTENERS.add(listener);
}
/**
* This method will remove the given listener from the list. It won't be informed anymore.
*/
public static void removeParameterChangeListener(ParameterChangeListener listener) {
PARAMETER_LISTENERS.remove(listener);
}
/*
* Deprecated methods remaining for compatibility only.
*/
/**
* This method is deprecated and shouldn't be used anymore. To save RapidMiner's Parameters,
* use method {@link #saveProperties(File))}.
* To save the given properties you can simply call {@link Properties#store(java.io.OutputStream, String)}
* yourself.
*/
@Deprecated
public static void writeProperties(Properties properties, File file) {
if (!RapidMiner.getExecutionMode().canAccessFilesystem()) {
LogService.getRoot().config("Ignoring request to save properties file in execution mode " + RapidMiner.getExecutionMode() + ".");
return;
}
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(file));
properties.store(out, "");
} catch (IOException e) {
LogService.getRoot().log(Level.WARNING, "Cannot write user properties: " + e.getMessage(), e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
}
}
}
}
/**
* This method shouldn't be used anymore since it does two actions at one time:
* Setting and saving. You can easily replace it by subsequent calls to {@link #setParameterValue(String, String)}
* and {@link #saveParameters()}.
*/
@Deprecated
public static void writePropertyIntoMainUserConfigFile(String key, String value) {
setParameterValue(key, value);
saveParameters();
// if (!RapidMiner.getExecutionMode().canAccessFilesystem()) {
// LogService.getRoot().config("Ignoring request to save properties file in execution mode " + RapidMiner.getExecutionMode() + ".");
// return;
// }
// LogService.getRoot().config("Saving property " + key + "=" + value);
// // read old configuration
// Properties userProperties = readPropertyFile(getMainUserConfigFile());
//
// // set new property
// userProperties.setProperty(key, value);
// System.setProperty(key, value);
//
// // write complete configuration back into the file
// writeProperties(userProperties, getMainUserConfigFile());
}
/**
* Deprecated method. Remains only for compatibility.
* Please use {@link FileSystemService#getMainUserConfigFile()} instead.
*/
@Deprecated
public static File getMainUserConfigFile() {
return FileSystemService.getMainUserConfigFile();
}
/**
* Deprecated method. Remains only for compatibility.
* Please use {@link FileSystemService#getUserConfigFile(String)} instead.
*/
@Deprecated
public static File getUserConfigFile(String name) {
return FileSystemService.getUserConfigFile(name);
}
/**
* Deprecated method. Remains only for compatibility.
* Please use {@link FileSystemService#getVersionedUserConfigFile(VersionNumber, String)} instead.
*/
@Deprecated
public static File getVersionedUserConfigFile(VersionNumber versionNumber, String name) {
return FileSystemService.getVersionedUserConfigFile(versionNumber, name);
}
/**
* Deprecated method. Remains only for compatibility.
* Please use {@link FileSystemService#getUserRapidMinerDir()} instead.
*/
@Deprecated
public static File getUserRapidMinerDir() {
return FileSystemService.getUserRapidMinerDir();
}
/**
* Deprecated method. Remains only for compatibility.
* Please use {@link FileSystemService#getRapidMinerHome()} instead.
*/
@Deprecated
public static File getRapidMinerHome() throws IOException {
return FileSystemService.getRapidMinerHome();
}
/**
* Deprecated method. Remains only for compatibility.
* Please use {@link FileSystemService#getLibraryFile(String)} instead.
*/
@Deprecated
public static File getLibraryFile(String name) throws IOException {
return FileSystemService.getLibraryFile(name);
}
/**
* Deprecated method. Remains only for compatibility.
* Please use {@link FileSystemService#getSourceRoot()} instead.
*/
@Deprecated
public static File getSourceRoot() {
return FileSystemService.getSourceRoot();
}
/**
* Deprecated method. Remains only for compatibility.
* Please use {@link FileSystemService#getSourceFile(String)} instead.
*/
@Deprecated
public static File getSourceFile(String name) {
return FileSystemService.getSourceFile(name);
}
/**
* Deprecated method. Remains only for compatibility.
* Please use {@link FileSystemService#getSourceResourceFile(String)} instead.
*/
@Deprecated
public static File getSourceResourceFile(String name) {
return FileSystemService.getSourceResourceFile(name);
}
/**
* This method is deprecated and remains only for compatibility. Please use {@link #init()} instead.
*/
@Deprecated
public static void init(InputStream operatorsXMLStream) {
init();
}
/**
* Returns true if value is "true", "yes", "y" or "on". Returns false if
* value is "false", "no", "n" or "off". Otherwise returns <tt>deflt</tt>.
*/
@Deprecated
public static boolean booleanValue(String value, boolean deflt) {
if (value == null)
return deflt;
if (value.equals("true"))
return true;
else if (value.equals("yes"))
return true;
else if (value.equals("y"))
return true;
else if (value.equals("on"))
return true;
else if (value.equals("false"))
return false;
else if (value.equals("no"))
return false;
else if (value.equals("n"))
return false;
else if (value.equals("off"))
return false;
return deflt;
}
// private static Properties readPropertyFile(File file) {
// if (file.exists()) {
// InputStream in = null;
// try {
// in = new FileInputStream(file);
// } catch (IOException e) {
// LogService.getRoot().log(Level.WARNING, "Cannot read main user properties: " + e.getMessage(), e);
// return new Properties();
// }
// try {
// return readPropertyFile(in);
// } finally {
// if (in != null) {
// try {
// in.close();
// } catch (IOException e) {
// LogService.getRoot().log(Level.WARNING, "Cannot close connection to user properties: " + e.getMessage(), e);
// }
// }
// }
// } else {
// return new Properties();
// }
// }
// /** Reads a property file, possibly transforming properties as required by the {@link ParameterType}s. */
// private static Properties readPropertyFile(InputStream in) {
// Properties properties = new Properties();
// try {
// properties.load(in);
// } catch (IOException e) {
// LogService.getRoot().log(Level.WARNING, "Cannot read main user properties: " + e.getMessage(), e);
// } finally {
// if (in != null) {
// try {
// in.close();
// } catch (IOException e) {
// LogService.getRoot().log(Level.WARNING, "Cannot close connection to user properties: " + e.getMessage(), e);
// }
// }
// }
// for (Map.Entry<Object, Object> entry : properties.entrySet()) {
// String typeKey = (String) entry.getKey();
// String typeValue = (String) entry.getValue();
// if (typeValue != null) {
// for (ParameterType type : RapidMiner.getRapidMinerProperties()) {
// if (type.getKey().equals(typeKey)) {
// properties.put(typeKey, type.transformNewValue(typeValue));
// break;
// }
// }
// }
// }
// return properties;
// }
// private static void loadAllRCFiles(String rcFileName) {
// loadRCFile(rcFileName);
// loadRCFile(rcFileName + "." + System.getProperty("os.name"));
// }
// private static void loadRCFile(String rcFileName) {
// if (rcFileName == null)
// return;
// File rcFile = new File(rcFileName);
// if (!rcFile.exists()) {
// LogService.getRoot().config("Trying rcfile '" + rcFile + "'...skipped");
// return;
// }
//
// // TODO: Needs to distinguish between system and RapidMiner Properties!!!
// String userDir = System.getProperty("user.dir");
// Properties props = readPropertyFile(rcFile);
// System.getProperties().putAll(props);
// System.getProperties().put("user.dir", userDir);
// LogService.getRoot().config("Read rcfile '" + rcFile + "'.");
// }
}