/**
* 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.config.gui;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import javax.swing.SwingUtilities;
import org.w3c.dom.Document;
import org.xml.sax.SAXException;
import com.rapidminer.gui.tools.ProgressThread;
import com.rapidminer.io.process.XMLTools;
import com.rapidminer.parameter.ParameterHandler;
import com.rapidminer.parameter.ParameterType;
import com.rapidminer.parameter.Parameters;
import com.rapidminer.parameter.UndefinedParameterError;
import com.rapidminer.repository.RepositoryException;
import com.rapidminer.repository.internal.remote.RemoteRepository;
import com.rapidminer.tools.I18N;
import com.rapidminer.tools.LogService;
import com.rapidminer.tools.PasswordInputCanceledException;
import com.rapidminer.tools.XMLException;
import com.rapidminer.tools.config.AbstractConfigurable;
import com.rapidminer.tools.config.AbstractConfigurator;
import com.rapidminer.tools.config.Configurable;
import com.rapidminer.tools.config.ConfigurationException;
import com.rapidminer.tools.config.ConfigurationManager;
import com.rapidminer.tools.config.actions.ActionResult;
import com.rapidminer.tools.config.actions.ActionResult.Result;
import com.rapidminer.tools.config.actions.ConfigurableAction;
import com.rapidminer.tools.config.actions.SimpleActionResult;
import com.rapidminer.tools.config.gui.model.ConfigurableModel;
import com.rapidminer.tools.container.Pair;
/**
* The controller for the {@link ConfigurableDialog} view.
*
* @author Marco Boeck, Sabrina Kirstein
*
*/
public class ConfigurableController {
/** the view this controller manages */
private ConfigurableDialog view;
/** the model behind the view */
private ConfigurableModel model;
/**
* Creates a new {@link ConfigurableController} instance.
*
* @param view
* @param model
*/
public ConfigurableController(ConfigurableDialog view, ConfigurableModel model) {
this.view = view;
this.model = model;
}
/**
* Checks if the given {@link Configurable} is in a valid state. If it is not, returns an error
* {@link ActionResult}.
*
* @return
*/
protected ActionResult checkConfigurableValidState(Configurable configurable) {
// Get parameter types based on Configurator implementation
AbstractConfigurator<? extends Configurable> configurator = ConfigurationManager.getInstance()
.getAbstractConfigurator(configurable.getTypeId());
ParameterHandler parameterHandler = configurator.getParameterHandler(configurable);
List<ParameterType> parameterTypes = configurator.getParameterTypes(parameterHandler);
for (ParameterType type : parameterTypes) {
if (!type.isOptional()) {
String value = configurable.getParameter(type.getKey());
if ((value == null || "".equals(value.trim())) && type.getDefaultValue() == null) {
return new SimpleActionResult(
I18N.getGUIMessage("gui.dialog.configurable_dialog.error.missing_value.label", type.getKey()),
Result.FAILURE);
}
}
}
return new SimpleActionResult(null, Result.SUCCESS);
}
/**
* Checks if the given name is unique for the specified type.
*
* @param typeId
* @param name
* @return <code>true</code> if the name is unique; <code>false</code> otherwise
*/
protected boolean isNameUniqueForType(String typeId, String name) {
return !model.getListOfUniqueNamesForType(typeId).contains(name);
}
/**
* Removes the specified {@link Configurable}.
*
* @param configurable
*/
protected void removeConfigurable(Configurable configurable) {
model.removeConfigurable(configurable);
// Update ParameterHandler
ConfigurationManager.getInstance().getAbstractConfigurator(configurable.getTypeId())
.removeCachedParameterHandler(configurable);
}
/**
* Creates a {@link Configurable} of the specified type with the given name and the given remote
* repository.
*
* @param typeId
* type of the configurable
* @param name
* name of the configurable
* @param source
* {@link RemoteRepository} (source) of the configurable, can be null in case of
* local connections
*/
protected void addConfigurable(String typeId, String name, RemoteRepository source) {
try {
Configurable newConfigurable = ConfigurationManager.getInstance().createWithoutRegistering(typeId, name, source);
model.addConfigurable(newConfigurable);
} catch (ConfigurationException e) {
LogService.getRoot().log(Level.WARNING, "com.rapidminer.tools.config.gui.ConfigurableController.unknown_type",
typeId);
}
}
/**
* Renames the specified {@link Configurable} to the new name.
*
* @param configurable
* @param newName
*/
protected void renameConfigurable(Configurable configurable, String newName) {
String oldConfigurableName = configurable.getName();
configurable.setName(newName);
// Update ParameterHandler
ConfigurationManager.getInstance().getAbstractConfigurator(configurable.getTypeId())
.reregisterCachedParameterHandler(configurable, oldConfigurableName);
}
/**
* Executes the specified {@link ConfigurableAction} in a separate {@link ProgressThread} and
* displays the {@link ActionResult} in the view.
*
* @param action
*/
protected void executeConfigurableAction(final ConfigurableAction action) {
if (action == null) {
throw new IllegalArgumentException("action must not be null!");
}
// execute action in own ProgressThread to avoid GUI blocking
ProgressThread actionThread = new ProgressThread("configurable_action") {
@Override
public void run() {
// show user we are doing something
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
view.displayActionIsRunning();
}
});
ActionResult result = null;
try {
result = action.doWork();
} catch (Exception e) {
result = new SimpleActionResult(e.getMessage(), Result.FAILURE);
}
final ActionResult finalResult = result;
// show user results
SwingUtilities.invokeLater(new Runnable() {
@Override
public void run() {
view.displayResult(finalResult);
}
});
}
};
actionThread.start();
}
/**
* Saves the {@link Parameters} for the given {@link Configurable}.
*
* @param configurable
* @param parameters
*/
protected void saveConfigurable(Configurable configurable, Parameters parameters) {
for (String key : parameters.getKeys()) {
try {
String value = parameters.getParameter(key);
// If we are an AbstractConfigurable, toString must be invoked. This is necessary
// e.g. to save passwords encrypted.
if (configurable instanceof AbstractConfigurable) {
AbstractConfigurator<? extends Configurable> configurator = ConfigurationManager.getInstance()
.getAbstractConfigurator(configurable.getTypeId());
for (ParameterType type : configurator
.getParameterTypes(configurator.getParameterHandler(configurable))) {
if (type.getKey().equals(key)) {
value = type.toString(value);
break;
}
}
}
configurable.setParameter(key, value);
} catch (UndefinedParameterError e) {
LogService.getRoot().log(Level.FINE,
"com.rapidminer.tools.config.gui.ConfigurableController.save_undef_param",
new Object[] { key, configurable.getName() });
}
}
}
/**
* Save the changes the user made to the configurables.
*/
protected void save() {
RemoteRepository repository = model.getSource();
// if this is locally changed, save it in a file
if (repository == null) {
ConfigurationManager.getInstance().replaceConfigurables(model.getConfigurables(), null);
try {
ConfigurationManager.getInstance().saveConfiguration();
} catch (ConfigurationException e) {
LogService.getRoot().log(Level.SEVERE,
"com.rapidminer.tools.config.gui.ConfigurableController.error_on_save", e);
view.displaySaveErrorDialog();
}
} else {
// else send the configurables as an XML to the server
// check if the repository has been connected before
if (repository.isConnected()) {
// check if the repository is still connected
try {
repository.resetContentManager();
} catch (RepositoryException | PasswordInputCanceledException e) {
// revert parameter and name changes
try {
model.resetConfigurables();
model.resetConnection();
} catch (RepositoryException e1) {
// could not connect to repository
}
view.collapseRemoteTaskPane(repository.getName());
view.displayConnectionErrorDialog(repository.getName(), repository.getBaseUrl().toString());
return;
}
if (model.isEditingPossible()) {
if (model.hasAdminRights()) {
// upload the configurables for each typeId
for (String typeId : ConfigurationManager.getInstance().getAllTypeIds()) {
HttpURLConnection conn = null;
try {
conn = repository.getHTTPConnection(
ConfigurationManager.RM_SERVER_CONFIGURATION_URL_PREFIX + typeId, true);
conn.setRequestMethod("POST");
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setAllowUserInteraction(false);
conn.setRequestProperty("Content-Type", "application/xml");
Document xml = ConfigurationManager.getInstance().getConfigurablesAsXML(typeId,
model.getConfigurables(), repository.getName());
// create and write xml content
XMLTools.stream(xml, conn.getOutputStream(), null);
conn.disconnect();
// check response code for result
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) {
// something went wrong
if (conn.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
LogService.getRoot().log(Level.INFO, typeId + ": " + conn.getResponseMessage());
} else if (conn.getResponseCode() == HttpURLConnection.HTTP_BAD_METHOD) {
LogService.log(LogService.getRoot(), Level.WARNING, null,
"com.rapidminer.tools.config.gui.ConfigurableController.uploading_configuration_error_server_not_up_to_date",
typeId, repository.getName(), conn.getResponseMessage());
view.displaySaveUploadErrorDialogServerNotUpToDate(repository.getName());
// revert parameter and name changes
revertChanges();
break;
} else {
LogService.log(LogService.getRoot(), Level.WARNING,
new Exception(conn.getResponseMessage()),
"com.rapidminer.tools.config.gui.ConfigurableController.uploading_configuration_error",
typeId, repository.getName(), conn.getResponseMessage());
view.displaySaveUploadErrorDialog(typeId, repository.getName(),
repository.getBaseUrl().toString());
// revert parameter and name changes
revertChanges();
break;
}
} else {
// all should be fine
Document newIdsDoc = XMLTools.parse(conn.getInputStream());
List<Pair<Integer, String>> newIds = ConfigurationManager.newIdsFromXML(newIdsDoc);
// replace ids of new created configurables (with id -1)
for (Configurable config : model.getConfigurables()) {
if (config.getTypeId().equals(typeId)) {
if (config.getId() == -1) {
for (Pair<Integer, String> pair : newIds) {
if (pair.getSecond().equals(config.getName())) {
// set the new id (given by the server)
// instead of -1
config.setId(pair.getFirst());
}
}
}
}
}
// store the configurables permanently in the
// ConfigurationManager
ConfigurationManager.getInstance().replaceConfigurables(model.getConfigurables(),
repository.getName());
LogService.getRoot().log(Level.INFO,
"com.rapidminer.tools.config.gui.ConfigurableController.uploading_configuration",
typeId);
}
} catch (IOException | RepositoryException | ConfigurationException | SAXException e) {
// revert parameter and name changes
revertChanges();
LogService.log(LogService.getRoot(), Level.WARNING, e,
"com.rapidminer.tools.config.gui.ConfigurableController.uploading_configuration_error",
typeId, repository.getName(), e.toString());
view.displaySaveUploadErrorDialog(typeId, repository.getName(),
repository.getBaseUrl().toString());
break;
} catch (XMLException e) {
// revert parameter and name changes
revertChanges();
LogService.log(LogService.getRoot(), Level.WARNING, e,
"com.rapidminer.tools.config.gui.ConfigurableController.uploading_configuration_error",
typeId, repository.getName(), e.toString());
view.displaySaveUploadErrorDialog(typeId, repository.getName(),
repository.getBaseUrl().toString());
break;
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
}
}
}
}
}
/**
* Reverts all parameter changes the user made to the {@link Configurable}s.
*/
protected void revertChanges() {
// revert parameters
Map<Configurable, Map<String, String>> backupParameters = model.getBackupParameters();
for (Configurable configurable : backupParameters.keySet()) {
// find the same Configurable and revert its parameters (which may have been altered
// by the user) to their original values
Map<String, String> backupMap = backupParameters.get(configurable);
for (String key : backupMap.keySet()) {
configurable.setParameter(key, backupMap.get(key));
}
}
// revert names
Map<Configurable, String> backupNames = model.getBackupNames();
for (Configurable configurable : backupNames.keySet()) {
configurable.setName(backupNames.get(configurable));
}
// revert permitted user groups
Map<Configurable, Set<String>> backupPermittedUserGroups = model.getBackupPermittedUserGroups();
for (Configurable configurable : backupPermittedUserGroups.keySet()) {
ConfigurationManager.getInstance().setPermittedGroupsForConfigurable(configurable,
backupPermittedUserGroups.get(configurable));
}
}
/**
* @return the model for the controller.
*/
protected ConfigurableModel getModel() {
return model;
}
/**
* @return the view for the controller
*/
protected ConfigurableDialog getView() {
return view;
}
}