/**
* Copyright (C) 2001-2017 by RapidMiner and the contributors
*
* Complete list of developers available at our web site:
*
* http://rapidminer.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.security.AccessControlException;
import java.security.AccessController;
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.rapidminer.RapidMiner;
import com.rapidminer.operator.ExecutionMode;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.security.PluginSandboxPolicy;
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>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 rapidminer-studio-settings.cfg in the user's .Rapidminer directory will be
* loaded.</li>
* <li>the file rapidminer-studio-settings.cfg 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, Marco Boeck
*/
public class ParameterService {
private static final Logger LOGGER = Logger.getLogger(ParameterService.class.getSimpleName());
public static final String RAPIDMINER_CONFIG_FILE_NAME = "rapidminer-studio-settings.cfg";
/** 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<>();
private static final Map<String, Parameter> PARAMETER_MAP = new TreeMap<>();
private static final List<ParameterWriter> PARAMETER_WRITERS = new LinkedList<>();
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) {
// then try to read configuration from file system if allowed to do so.
if (RapidMiner.getExecutionMode().canAccessFilesystem()) {
List<File> configFilesList = new LinkedList<>();
// adding global config file defined by parameter or environment variable
File globalConfigFile = getGlobalConfigFile(RAPIDMINER_CONFIG_FILE_NAME);
if (globalConfigFile != null) {
configFilesList.add(globalConfigFile);
configFilesList.add(getOSDependentFile(globalConfigFile));
}
// add user specific config file from .RapidMiner directory
File userConfigFile = FileSystemService.getUserConfigFile(RAPIDMINER_CONFIG_FILE_NAME);
if (userConfigFile != null) {
configFilesList.add(userConfigFile);
configFilesList.add(getOSDependentFile(userConfigFile));
}
// 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);
}
// Before changing, check if the Parameter is protected
if (RapidMiner.isParameterProtected(key)) {
// If yes, only extensions with enough permissions should be allowed to change it
try {
if (System.getSecurityManager() != null) {
AccessController
.checkPermission(new RuntimePermission(PluginSandboxPolicy.RAPIDMINER_INTERNAL_PERMISSION));
}
} catch (AccessControlException e) {
return;
}
}
// setting parameter
Parameter parameter = PARAMETER_MAP.get(key);
if (parameter == null) {
parameter = new Parameter(value);
PARAMETER_MAP.put(key, parameter);
}
parameter.setValue(value);
try {
informListenerOfChange(key, value);
} catch (Throwable e) {
LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.tools.ParameterService.listener_error", e));
}
}
/**
* 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<>();
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<>();
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());
}
try {
in.close();
} catch (IOException e) {
// can't help it
}
}
/**
* 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 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().log(Level.CONFIG,
"com.rapidminer.tools.ParameterService.ignoring_request_to_save_properties",
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, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.tools.ParameterService.writing_user_properties_error", e.getMessage()), e);
} catch (IOException e) {
LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.tools.ParameterService.writing_user_properties_error", e.getMessage()), e);
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.tools.ParameterService.closing_user_properties_file_error", 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);
}
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().log(Level.CONFIG,
"com.rapidminer.tools.ParameterService.ignoring_request_to_save_properties",
RapidMiner.getExecutionMode());
return;
}
BufferedOutputStream out = null;
try {
out = new BufferedOutputStream(new FileOutputStream(file));
properties.store(out, "");
} catch (IOException e) {
LogService.getRoot().log(Level.WARNING, I18N.getMessage(LogService.getRoot().getResourceBundle(),
"com.rapidminer.tools.ParameterService.writing_user_properties_error", 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();
}
/**
* 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#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;
}
}