/** * 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.model; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.swing.event.EventListenerList; import javax.xml.ws.WebServiceException; import com.rapidminer.gui.security.UserCredential; import com.rapidminer.gui.security.Wallet; import com.rapidminer.gui.tools.VersionNumber; import com.rapidminer.repository.RepositoryException; import com.rapidminer.repository.internal.remote.RemoteInfoService; import com.rapidminer.repository.internal.remote.RemoteRepository; import com.rapidminer.tools.Observable; import com.rapidminer.tools.Observer; import com.rapidminer.tools.PasswordInputCanceledException; import com.rapidminer.tools.config.AbstractConfigurable; import com.rapidminer.tools.config.Configurable; import com.rapidminer.tools.config.ConfigurationManager; import com.rapidminer.tools.config.gui.ConfigurableDialog; import com.rapidminer.tools.config.gui.event.ConfigurableEvent; import com.rapidminer.tools.config.gui.event.ConfigurableEvent.EventType; import com.rapidminer.tools.config.gui.event.ConfigurableModelEventListener; import com.rapidminer.tools.container.Pair; /** * The backing model of the {@link ConfigurableDialog}. * * @author Marco Boeck, Sabrina Kirstein * */ public class ConfigurableModel implements Observer<Pair<EventType, Configurable>> { /** comparator for Configurables */ private static Comparator<Configurable> COMPARATOR = new ConfigurationManager.ConfigurableComparator(); /** event listener for this model */ private final EventListenerList eventListener; /** the list of all {@link Configurable}s in this model */ private List<Configurable> listOfConfigurables; /** * this map stores the original parameters for each {@link Configurable} which existed during * construction */ private Map<Configurable, Map<String, String>> originalParameters; /** * this map stores the original names for each {@link Configurable} which existed during * construction */ private Map<Configurable, String> originalNames; /** * this map stores a set with the original user groups that have access to a remote * {@link Configurable} for each {@link Configurable}. Is empty for local {@link Configurable}s. */ private Map<Configurable, Set<String>> originalPermittedUserGroups; /** * stores the original credentials of {@link #source}, if {@link #source} is not null */ private UserCredential originalCredentials; /** * model contains the configurables with the defined source */ private RemoteRepository source = null; /** * defines whether the editing of the parameters is possible */ private boolean editingPossible = true; /** * defines whether the check was done if the editing of the parameters is possible */ private boolean checkDone = false; /** * defines whether the user is allowed to edit, add or save configurables */ private boolean adminRights = false; /** * Creates a new {@link ConfigurableModel} instance including all available {@link Configurable} * s. */ public ConfigurableModel() { this.eventListener = new EventListenerList(); // creates the model with all available configurables for all sources initModel(true, null); } /** * Creates a new {@link ConfigurableModel} instance including all available {@link Configurable} * s of the given {@link RemoteRepository}. * * @param source * only {@link Configurable}s of this {@link RemoteRepository} are stored in the * model, can be null for local connections */ public ConfigurableModel(RemoteRepository source) { this.eventListener = new EventListenerList(); // creates one model per source (and adds only the configurables with the same source of the // model) initModel(false, source); } /** * Creates the model either for all available configurables or for all configurables of the * given source * * @param allConfigurables * defines whether the model should contain all configurables or just the * configurables of a given source * @param source * defines the source of the model, if allConfigurables is false (is null otherwise) */ private void initModel(boolean allConfigurables, RemoteRepository source) { this.listOfConfigurables = Collections.synchronizedList(new LinkedList<Configurable>()); this.originalParameters = new HashMap<>(); this.originalNames = new HashMap<>(); this.originalPermittedUserGroups = new HashMap<>(); this.source = source; checkForAdminRights(); if (source != null) { originalCredentials = Wallet.getInstance().getEntry(source.getAlias(), source.getBaseUrl().toString()); if (originalCredentials == null) { originalCredentials = new UserCredential(source.getBaseUrl().toString(), source.getUsername(), null); } } for (Configurable configurable : ConfigurationManager.getInstance().getAllConfigurables()) { boolean sameLocalSource = source == null && configurable.getSource() == null; boolean sameRemoteSource = source != null && configurable.getSource() != null && configurable.getSource().getName().equals(source.getName()); if (allConfigurables || sameLocalSource || sameRemoteSource) { this.listOfConfigurables.add(configurable); // copy key-value pairs and save them in new map Map<String, String> parameterMap = new HashMap<>(); for (String key : configurable.getParameters().keySet()) { if (configurable instanceof AbstractConfigurable) { parameterMap.put(key, ((AbstractConfigurable) configurable).getParameterAsXMLString(key)); } else { parameterMap.put(key, configurable.getParameter(key)); } } originalParameters.put(configurable, new HashMap<>(parameterMap)); originalNames.put(configurable, configurable.getName()); originalPermittedUserGroups.put(configurable, ConfigurationManager.getInstance().getPermittedGroupsForConfigurable(configurable)); } } // make sure we are notified when a Configurable is registered/removed via the // ConfigurationManager ConfigurationManager.getInstance().addObserver(this, false); } /** * If the configurations have been loaded after the creation of the model, the backup parameters * need to be stored separately. */ private void updateBackup() { for (Configurable configurable : listOfConfigurables) { // copy key-value pairs and save them in new map Map<String, String> parameterMap = new HashMap<>(); for (String key : configurable.getParameters().keySet()) { if (configurable instanceof AbstractConfigurable) { parameterMap.put(key, ((AbstractConfigurable) configurable).getParameterAsXMLString(key)); } else { parameterMap.put(key, configurable.getParameter(key)); } } originalParameters.put(configurable, new HashMap<>(parameterMap)); originalNames.put(configurable, configurable.getName()); originalPermittedUserGroups.put(configurable, ConfigurationManager.getInstance().getPermittedGroupsForConfigurable(configurable)); } } /** * checks if the source is a instance of {@link RemoteRepository}, if the editing of the * containing configurables is possible */ public void checkVersion() { if (source != null) { if (!checkDone) { // check the server version RemoteInfoService versionService = source.getInfoService(); if (versionService != null) { if (versionService.getVersionNumber() != null) { VersionNumber serverVersion = new VersionNumber(versionService.getVersionNumber()); // server connections are not editable if (!serverVersion.isAtLeast(new VersionNumber(2, 4, 0, "SNAPSHOT"))) { editingPossible = false; } checkDone = true; } } } } } /** * Adds a {@link ConfigurableModelEventListener} which will be informed of all changes to this * model. * * @param listener */ public void registerEventListener(final ConfigurableModelEventListener listener) { eventListener.add(ConfigurableModelEventListener.class, listener); } /** * Removes the {@link ConfigurableModelEventListener} from this model. * * @param listener */ public void removeEventListener(final ConfigurableModelEventListener listener) { eventListener.remove(ConfigurableModelEventListener.class, listener); } /** * Returns a list of the user defined names for all {@link Configurable}s of the specified type. * * @param typeId * @return */ public List<String> getListOfUniqueNamesForType(String typeId) { List<String> list = new LinkedList<>(); for (Configurable c : listOfConfigurables) { if (c.getTypeId().equals(typeId)) { list.add(c.getName()); } } return list; } /** * @return the source of the configurables in the model, can be null (if local) */ public RemoteRepository getSource() { return source; } /** * This method returns all {@link Configurable}s which have existed during construction, as well * as the parameters at that time. * * @return */ public Map<Configurable, Map<String, String>> getBackupParameters() { return new HashMap<>(originalParameters); } /** * This method returns all {@link Configurable}s which have existed during construction, as well * as their names at that time. * * @return */ public Map<Configurable, String> getBackupNames() { return new HashMap<>(originalNames); } /** * This method returns all user groups which were originally permitted to have access to the * configurables * * @return */ public Map<Configurable, Set<String>> getBackupPermittedUserGroups() { return originalPermittedUserGroups; } /** * * @return if the editing of the contained configurables is possible */ public boolean isEditingPossible() { if (!wasVersionCheckDone()) { try { checkVersion(); } catch (WebServiceException e) { // no connection possible return false; } } return editingPossible; } /** * * @return if the check if the editing of the contained configurables is possible was done * already */ public boolean wasVersionCheckDone() { return checkDone; } /** * checks whether the user logged in has admin rights */ public void checkForAdminRights() { if (source == null) { adminRights = true; } else if (source.getUsername().equals(ConfigurationManager.RM_SERVER_CONFIGURATION_USER_ADMIN)) { adminRights = true; } } /** * * @return whether the user is allowed to edit, add, remove and save configurables */ public boolean hasAdminRights() { return adminRights; } /** * Adds a new {@link Configurable}. * * @param configurable */ public void addConfigurable(Configurable configurable) { boolean sameLocalSource = configurable.getSource() == null && this.source == null; boolean sameRemoteSource = configurable.getSource() != null && this.source != null && configurable.getSource().getName().equals(source.getName()); if (sameLocalSource || sameRemoteSource) { listOfConfigurables.add(configurable); Collections.sort(listOfConfigurables, COMPARATOR); fireConfigurableAddedEvent(configurable); } } /** * Removes the given {@link Configurable}. * * @param configurable */ public void removeConfigurable(Configurable configurable) { listOfConfigurables.remove(configurable); ConfigurationManager.getInstance().setPermittedGroupsForConfigurable(configurable, new HashSet<String>()); fireConfigurableRemovedEvent(configurable); } /** * Get all {@link Configurable}s in this model. * * @return */ public List<Configurable> getConfigurables() { return new LinkedList<>(listOfConfigurables); } /** * Fire when a {@link Configurable} has been added. * * @param configurable */ private void fireConfigurableAddedEvent(Configurable configurable) { fireEvent(EventType.CONFIGURABLE_ADDED, configurable); } /** * Fire when a {@link Configurable} has been removed. * * @param configurable */ private void fireConfigurableRemovedEvent(Configurable configurable) { fireEvent(EventType.CONFIGURABLE_REMOVED, configurable); } /** * Fire when all {@link Configurable}s have changed. * */ private void fireConfigurablesChangedEvent() { fireEvent(EventType.CONFIGURABLES_CHANGED, null); } /** * Fire when all {@link Configurable}s have been loaded. * */ private void fireLoadedFromRepositoryEvent(Configurable configurable) { fireEvent(EventType.LOADED_FROM_REPOSITORY, configurable); } /** * Fires the given {@link EventType} for the {@link Configurable} in question. * * @param type * @param configurable * can be <code>null</code> */ private void fireEvent(final EventType type, Configurable configurable) { Object[] listeners = eventListener.getListenerList(); // Process the listeners last to first for (int i = listeners.length - 2; i >= 0; i -= 2) { if (listeners[i] == ConfigurableModelEventListener.class) { ConfigurableEvent e = new ConfigurableEvent(type, configurable); ((ConfigurableModelEventListener) listeners[i + 1]).modelChanged(e); } } } @Override public void update(Observable<Pair<EventType, Configurable>> observable, Pair<EventType, Configurable> arg) { boolean sameLocalSource = arg.getSecond() != null && arg.getSecond().getSource() == null && this.source == null; boolean sameRemoteSource = arg.getSecond() != null && arg.getSecond().getSource() != null && this.source != null && arg.getSecond().getSource().getName().equals(source.getName()); if (EventType.CONFIGURABLES_CHANGED.equals(arg.getFirst())) { fireConfigurablesChangedEvent(); } else if (EventType.CONFIGURABLE_ADDED.equals(arg.getFirst())) { if (sameLocalSource || sameRemoteSource) { addConfigurable(arg.getSecond()); } } else if (EventType.CONFIGURABLE_REMOVED.equals(arg.getFirst())) { if (sameLocalSource || sameRemoteSource) { removeConfigurable(arg.getSecond()); } } else if (EventType.LOADED_FROM_REPOSITORY.equals(arg.getFirst())) { if (sameRemoteSource) { updateBackup(); Configurable configurable = arg.getSecond(); configurable.setSource(source); fireLoadedFromRepositoryEvent(configurable); } } } /** * Resets the configurables in the cache. */ public void resetConfigurables() { if (source != null) { for (Configurable originalConfig : originalNames.keySet()) { ConfigurationManager.getInstance().removeConfigurable(originalConfig.getTypeId(), originalNames.get(originalConfig), source.getName()); } listOfConfigurables.clear(); } } /** * Resets the connection to the {@link RemoteRepository} of this model * * @throws RepositoryException * if the connection could not be established */ public void resetConnection() throws RepositoryException { RepositoryException firstException = null; RepositoryException secondException = null; try { // reset the repository service source.resetContentManager(); } catch (RepositoryException e) { // if no connection could be established secondException = e; } catch (PasswordInputCanceledException e) { // ignore } try { // clear the cache source.refresh(); } catch (RepositoryException e) { // if no connection could be established firstException = e; } checkForAdminRights(); if (firstException != null) { throw firstException; } else if (secondException != null) { throw secondException; } } /** * Resets the credentials of the {@link RemoteRepository} of this model if they have changed */ public void resetCredentials() { // if the original credentials are given and changed if (originalCredentials != null) { if (!source.getUsername().equals(originalCredentials.getUsername())) { source.setUsername(originalCredentials.getUsername()); source.setPassword(originalCredentials.getPassword()); resetConfigurables(); try { resetConnection(); } catch (RepositoryException e) { // reset connection failed } } } } }