/* * SonarLint for Eclipse * Copyright (C) 2015-2017 SonarSource SA * sonarlint@sonarsource.com * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ package org.sonarlint.eclipse.core.internal.server; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import javax.annotation.CheckForNull; import javax.annotation.Nullable; import org.eclipse.core.runtime.preferences.DefaultScope; import org.eclipse.core.runtime.preferences.IEclipsePreferences; import org.eclipse.core.runtime.preferences.IEclipsePreferences.INodeChangeListener; import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; import org.eclipse.core.runtime.preferences.IEclipsePreferences.NodeChangeEvent; import org.eclipse.core.runtime.preferences.InstanceScope; import org.eclipse.equinox.security.storage.ISecurePreferences; import org.eclipse.equinox.security.storage.SecurePreferencesFactory; import org.eclipse.equinox.security.storage.StorageException; import org.osgi.service.prefs.BackingStoreException; import org.osgi.service.prefs.Preferences; import org.sonarlint.eclipse.core.internal.SonarLintCorePlugin; import org.sonarlint.eclipse.core.internal.utils.StringUtils; import org.sonarsource.sonarlint.core.client.api.connected.ConnectedGlobalConfiguration; public class ServersManager { static final String PREF_SERVERS = "servers"; static final String AUTH_ATTRIBUTE = "auth"; static final String URL_ATTRIBUTE = "url"; static final String ORG_ATTRIBUTE = "org"; static final String USERNAME_ATTRIBUTE = "username"; static final String PASSWORD_ATTRIBUTE = "password"; private static final byte EVENT_ADDED = 0; private static final byte EVENT_CHANGED = 1; private static final byte EVENT_REMOVED = 2; private final Map<String, IServer> serversById = new LinkedHashMap<>(); private final List<IServerLifecycleListener> serverListeners = new ArrayList<>(); private final IPreferenceChangeListener serverChangeListener = event -> { try { if (!event.getNode().nodeExists("") || !event.getNode().parent().nodeExists("")) { // Deletion in progress return; } } catch (BackingStoreException e) { throw new IllegalStateException(e); } String serverId = getServerIdFromNodeName(event.getNode().name()); Server old = (Server) serversById.get(serverId); if (old != null) { loadServer(event.getNode(), old); old.stop(); fireServerEvent(old, EVENT_CHANGED); } else { Server newServer = new Server(serverId); loadServer(event.getNode(), newServer); serversById.put(newServer.getId(), newServer); fireServerEvent(newServer, EVENT_ADDED); } }; private final INodeChangeListener serversNodeChangeListener = new INodeChangeListener() { @Override public void removed(NodeChangeEvent event) { String serverId = getServerIdFromNodeName(event.getChild().name()); IServer serverToRemove = serversById.get(serverId); serversById.remove(serverId); if (serverToRemove != null) { fireServerEvent(serverToRemove, EVENT_REMOVED); } } @Override public void added(NodeChangeEvent event) { ((IEclipsePreferences) event.getChild()).addPreferenceChangeListener(serverChangeListener); } }; private final INodeChangeListener rootNodeChangeListener = new INodeChangeListener() { @Override public void removed(NodeChangeEvent event) { if (event.getChild().name().equals(PREF_SERVERS)) { Collection<IServer> removedServers = new ArrayList<>(serversById.values()); serversById.clear(); for (IServer server : removedServers) { fireServerEvent(server, EVENT_REMOVED); } // Reload default values serversById.putAll(loadServersList(DefaultScope.INSTANCE.getNode(SonarLintCorePlugin.PLUGIN_ID).node(PREF_SERVERS))); for (IServer server : serversById.values()) { fireServerEvent(server, EVENT_ADDED); } } } @Override public void added(NodeChangeEvent event) { if (event.getChild().name().equals(PREF_SERVERS)) { // Default servers Collection<IServer> removedServers = new ArrayList<>(serversById.values()); serversById.clear(); for (IServer server : removedServers) { fireServerEvent(server, EVENT_REMOVED); } ((IEclipsePreferences) event.getChild()).addNodeChangeListener(serversNodeChangeListener); } } }; public void init() { IEclipsePreferences rootNode = getSonarLintPreferenceNode(); rootNode.addNodeChangeListener(rootNodeChangeListener); try { if (rootNode.nodeExists(PREF_SERVERS)) { Preferences serversNode = rootNode.node(PREF_SERVERS); ((IEclipsePreferences) serversNode).addNodeChangeListener(serversNodeChangeListener); serversById.putAll(loadServersList(serversNode)); for (String serverNodeName : serversNode.childrenNames()) { IEclipsePreferences serverNode = ((IEclipsePreferences) serversNode.node(serverNodeName)); serverNode.addPreferenceChangeListener(serverChangeListener); } } else { serversById.putAll(loadServersList(DefaultScope.INSTANCE.getNode(SonarLintCorePlugin.PLUGIN_ID).node(PREF_SERVERS))); } } catch (BackingStoreException e) { throw new IllegalStateException("Unable to load server list", e); } } public void stop() { IEclipsePreferences rootNode = getSonarLintPreferenceNode(); rootNode.removeNodeChangeListener(rootNodeChangeListener); try { if (rootNode.nodeExists(PREF_SERVERS)) { Preferences serversNode = rootNode.node(PREF_SERVERS); ((IEclipsePreferences) serversNode).removeNodeChangeListener(serversNodeChangeListener); for (String serverNodeName : serversNode.childrenNames()) { IEclipsePreferences serverNode = ((IEclipsePreferences) serversNode.node(serverNodeName)); serverNode.removePreferenceChangeListener(serverChangeListener); } } } catch (BackingStoreException e) { throw new IllegalStateException("Unable to load server list", e); } serverListeners.clear(); } public void addServerLifecycleListener(IServerLifecycleListener listener) { synchronized (serverListeners) { serverListeners.add(listener); } } public void removeServerLifecycleListener(IServerLifecycleListener listener) { synchronized (serverListeners) { serverListeners.remove(listener); } } private void fireServerEvent(final IServer server, byte b) { if (serverListeners.isEmpty()) { return; } List<IServerLifecycleListener> clone = new ArrayList<>(); clone.addAll(serverListeners); for (IServerLifecycleListener srl : clone) { if (b == EVENT_ADDED) { srl.serverAdded(server); } else if (b == EVENT_CHANGED) { srl.serverChanged(server); } else { srl.serverRemoved(server); } } } private static IEclipsePreferences getSonarLintPreferenceNode() { return InstanceScope.INSTANCE.getNode(SonarLintCorePlugin.PLUGIN_ID); } private static Map<String, IServer> loadServersList(Preferences serversNode) { Map<String, IServer> result = new LinkedHashMap<>(); try { for (String serverNodeName : serversNode.childrenNames()) { Preferences serverNode = serversNode.node(serverNodeName); String serverId = getServerIdFromNodeName(serverNodeName); Server s = new Server(serverId); loadServer(serverNode, s); result.put(s.getId(), s); } } catch (BackingStoreException e) { throw new IllegalStateException("Unable to load server list", e); } return result; } private static void loadServer(Preferences serverNode, Server server) { update(server, serverNode.get(URL_ATTRIBUTE, ""), serverNode.get(ORG_ATTRIBUTE, null), serverNode.getBoolean(AUTH_ATTRIBUTE, false)); } private static String getServerIdFromNodeName(String name) { return StringUtils.urlDecode(name); } public void addServer(IServer server, String username, String password) { if (serversById.containsKey(server.getId())) { throw new IllegalStateException("There is already a server with id '" + server.getId() + "'"); } if (server.hasAuth()) { storeCredentials(server, username, password); } addOrUpdateProperties(server); serversById.put(server.getId(), server); fireServerEvent(server, EVENT_ADDED); } private static void storeCredentials(IServer server, String username, String password) { try { ISecurePreferences secureServersNode = getSecureServersNode(); ISecurePreferences secureServerNode = secureServersNode.node(getServerNodeName(server)); secureServerNode.put(USERNAME_ATTRIBUTE, username, true); secureServerNode.put(PASSWORD_ATTRIBUTE, password, true); secureServersNode.flush(); } catch (StorageException | IOException e) { throw new IllegalStateException("Unable to store server credentials in secure storage: " + e.getMessage(), e); } } public void removeServer(IServer server) { String serverNodeName = getServerNodeName(server); try { IEclipsePreferences rootNode = getSonarLintPreferenceNode(); Preferences serversNode = rootNode.node(PREF_SERVERS); if (serversNode.nodeExists(serverNodeName)) { // No need to notify listener for every deleted property ((IEclipsePreferences) serversNode.node(serverNodeName)).removePreferenceChangeListener(serverChangeListener); serversNode.node(serverNodeName).removeNode(); serversNode.flush(); } } catch (BackingStoreException e) { throw new IllegalStateException("Unable to save server list", e); } tryRemoveSecureProperties(serverNodeName); } private static void tryRemoveSecureProperties(String serverNodeName) { ISecurePreferences secureServersNode = getSecureServersNode(); if (secureServersNode.nodeExists(serverNodeName)) { secureServersNode.node(serverNodeName).removeNode(); } } /** * Returns an array containing all servers. * * @return an array containing all servers */ public List<IServer> getServers() { return getServersNoInit(); } public List<IServer> getServersNoInit() { return Collections.unmodifiableList(new ArrayList<>(serversById.values())); } /** * Returns the server with the given id. * * @param id a server id * @return a server or null if id is null */ @CheckForNull public IServer getServer(@Nullable String id) { if (id == null) { return null; } return serversById.get(id); } public void updateServer(IServer server, String username, String password) { if (server == null) { return; } if (!serversById.containsKey(server.getId())) { throw new IllegalStateException("There is no server with id '" + server.getId() + "'"); } addOrUpdateProperties(server); if (server.hasAuth()) { storeCredentials(server, username, password); } Server serverToUpdate = (Server) serversById.get(server.getId()); update(serverToUpdate, server.getHost(), server.getOrganization(), server.hasAuth()); fireServerEvent(serverToUpdate, EVENT_CHANGED); } private void addOrUpdateProperties(IServer server) { IEclipsePreferences rootNode = getSonarLintPreferenceNode(); Preferences serversNode = rootNode.node(PREF_SERVERS); IEclipsePreferences serverNode = (IEclipsePreferences) serversNode.node(getServerNodeName(server)); serverNode.removePreferenceChangeListener(serverChangeListener); try { serverNode.put(URL_ATTRIBUTE, server.getHost()); if (StringUtils.isNotBlank(server.getOrganization())) { serverNode.put(ORG_ATTRIBUTE, server.getOrganization()); } serverNode.putBoolean(AUTH_ATTRIBUTE, server.hasAuth()); serversNode.flush(); } catch (BackingStoreException e) { throw new IllegalStateException("Unable to save server list", e); } finally { serverNode.addPreferenceChangeListener(serverChangeListener); } } public static String getUsername(IServer server) throws StorageException { return getFromSecure(server, USERNAME_ATTRIBUTE); } public static String getPassword(IServer server) throws StorageException { return getFromSecure(server, PASSWORD_ATTRIBUTE); } private static String getFromSecure(IServer server, String attribute) throws StorageException { ISecurePreferences secureServersNode = getSecureServersNode(); if (!secureServersNode.nodeExists(getServerNodeName(server))) { return null; } ISecurePreferences secureServerNode = secureServersNode.node(getServerNodeName(server)); return secureServerNode.get(attribute, null); } private static ISecurePreferences getSecureServersNode() { return SecurePreferencesFactory.getDefault().node(SonarLintCorePlugin.PLUGIN_ID).node(ServersManager.PREF_SERVERS); } private static String getServerNodeName(IServer server) { // Should not contain any "/" return StringUtils.urlEncode(server.getId()); } public String validate(String serverId, String serverUrl, boolean editExisting) { if (StringUtils.isBlank(serverUrl)) { return "Server url must be specified"; } if (StringUtils.isBlank(serverId)) { return "Server id must be specified"; } if (!editExisting && serversById.containsKey(serverId)) { return "Server id already exists"; } try { // Validate server ID format ConnectedGlobalConfiguration.builder() .setServerId(serverId) .build(); } catch (Exception e) { return e.getMessage(); } return null; } public IServer create(String id, String url, @Nullable String organization, String username, String password) { return update(new Server(id), url, organization, StringUtils.isNotBlank(username) || StringUtils.isNotBlank(password)); } private static Server update(Server server, String url, @Nullable String organization, boolean hasAuth) { return server.setHost(url) .setOrganization(organization) .setHasAuth(hasAuth); } }