]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9946 Background download and "pre-installation" of an edition
authorDuarte Meneses <duarte.meneses@sonarsource.com>
Mon, 16 Oct 2017 09:02:32 +0000 (11:02 +0200)
committerGrégoire Aubert <gregoire.aubert@sonarsource.com>
Mon, 23 Oct 2017 15:01:13 +0000 (08:01 -0700)
31 files changed:
server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java
server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java
server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginDownloader.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginUninstaller.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java
server/sonar-server/src/main/java/org/sonar/server/plugins/PluginUninstaller.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstaller.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstallerExecutor.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginDownloader.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginUninstaller.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/plugins/edition/package-info.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/plugins/ws/CancelAllAction.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UninstallAction.java
server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerExecutorTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginDownloaderTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginUninstallerTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/CancelAllActionTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstallActionTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginUpdateAggregatorTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UninstallActionTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UpdateActionTest.java

index 32412c407219d22dda13be24401a5a79f115b5df..6389cf6c017b62df78938e82f42e1757208c64cf 100644 (file)
@@ -149,5 +149,20 @@ public class CePluginJarExploderTest {
       throw new UnsupportedOperationException();
     }
 
+    @Override
+    public File getEditionDownloadedPluginsDir() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public File getUninstalledPluginsDir() {
+      throw new UnsupportedOperationException();
+    }
+
+    @Override
+    public File getEditionUninstalledPluginsDir() {
+      throw new UnsupportedOperationException();
+    }
+
   }
 }
index c001dfe050434eeb1b6344828bf15504f40ea460..7b2f99577e77de034d4ca6a3515001cd4d9522c1 100644 (file)
@@ -37,7 +37,7 @@ public interface ServerFileSystem {
 
   /**
    * Directory accessible by scanners through web server
-   * @return a non-null directory that MAY exist
+   * @return a directory which may or not exist
    */
   File getDeployDir();
 
@@ -55,27 +55,34 @@ public interface ServerFileSystem {
 
   /**
    * Files of plugins published by web server for scanners
-   * @return a non-null directory that MAY exist
+   * @return a directory which may or not exist
    */
   File getDeployedPluginsDir();
 
   /**
    * Directory of plugins downloaded through update center. Files
    * will be moved to {@link #getInstalledPluginsDir()} on startup.
-   * @return an existing directory
+   * @return a directory which may or not exist
    */
   File getDownloadedPluginsDir();
 
+  /**
+   * Directory of commercial plugins downloaded as part of the installation of an edition. Files
+   * will be moved to {@link #getInstalledPluginsDir()} on startup.
+   * @return a directory which may or not exist
+   */
+  File getEditionDownloadedPluginsDir();
+
   /**
    * Directory of currently installed plugins. Used at startup.
-   * @return an existing directory
+   * @return a directory which may or not exist
    */
   File getInstalledPluginsDir();
 
   /**
    * Directory of the plugins packaged by default with application. These
    * plugins are installed during the first fresh startup.
-   * @return an existing directory
+   * @return a directory which may or not exist
    */
   File getBundledPluginsDir();
 
@@ -84,4 +91,16 @@ public interface ServerFileSystem {
    * @return an existing file
    */
   File getPluginIndex();
+
+  /**
+   * Directory where plugins to be uninstalled are moved to.
+   * @return a directory which may or not exist
+   */
+  File getUninstalledPluginsDir();
+
+  /**
+   * Directory where plugins that are part of an edition and are to be uninstalled are moved to.
+   * @return a directory which may or not exist
+   */
+  File getEditionUninstalledPluginsDir();
 }
index 295a38e39ec61643a51576e113b65c15de6be8eb..3256b045380e06073567b7aef42909be20e9b51f 100644 (file)
@@ -35,12 +35,16 @@ public class ServerFileSystemImpl implements ServerFileSystem, org.sonar.api.pla
   private final File tempDir;
   private final File dataDir;
   private final File deployDir;
+  private final File uninstallDir;
+  private final File editionUninstallDir;
 
   public ServerFileSystemImpl(Configuration config) {
     this.homeDir = new File(config.get(ProcessProperties.PATH_HOME).get());
     this.tempDir = new File(config.get(ProcessProperties.PATH_TEMP).get());
     this.dataDir = new File(config.get(ProcessProperties.PATH_DATA).get());
     this.deployDir = new File(this.dataDir, TomcatContexts.WEB_DEPLOY_PATH_RELATIVE_TO_DATA_DIR);
+    this.uninstallDir = new File(getTempDir(), "uninstalled-plugins");
+    this.editionUninstallDir = new File(getTempDir(), "uninstalled-edition-plugins");
   }
 
   @Override
@@ -83,6 +87,11 @@ public class ServerFileSystemImpl implements ServerFileSystem, org.sonar.api.pla
     return new File(getHomeDir(), "extensions/downloads");
   }
 
+  @Override
+  public File getEditionDownloadedPluginsDir() {
+    return new File(getHomeDir(), "extensions/new-edition");
+  }
+
   @Override
   public File getInstalledPluginsDir() {
     return new File(getHomeDir(), "extensions/plugins");
@@ -98,4 +107,14 @@ public class ServerFileSystemImpl implements ServerFileSystem, org.sonar.api.pla
     return new File(getDeployDir(), "plugins/index.txt");
   }
 
+  @Override
+  public File getUninstalledPluginsDir() {
+    return uninstallDir;
+  }
+
+  @Override
+  public File getEditionUninstalledPluginsDir() {
+    return editionUninstallDir;
+  }
+
 }
index 7038d965aefd9aa4860c6aad1bcc3109b848b008..e7befa67c580c539f2560004c869c5a795a9d0ba 100644 (file)
@@ -128,7 +128,12 @@ import org.sonar.server.platform.ws.StatusAction;
 import org.sonar.server.platform.ws.SystemWs;
 import org.sonar.server.platform.ws.UpgradesAction;
 import org.sonar.server.plugins.PluginDownloader;
+import org.sonar.server.plugins.PluginUninstaller;
 import org.sonar.server.plugins.ServerExtensionInstaller;
+import org.sonar.server.plugins.edition.EditionInstaller;
+import org.sonar.server.plugins.edition.EditionInstallerExecutor;
+import org.sonar.server.plugins.edition.EditionPluginDownloader;
+import org.sonar.server.plugins.edition.EditionPluginUninstaller;
 import org.sonar.server.plugins.privileged.PrivilegedPluginsBootstraper;
 import org.sonar.server.plugins.privileged.PrivilegedPluginsStopper;
 import org.sonar.server.plugins.ws.AvailableAction;
@@ -258,6 +263,7 @@ public class PlatformLevel4 extends PlatformLevel {
       LogServerId.class,
       LogOAuthWarning.class,
       PluginDownloader.class,
+      PluginUninstaller.class,
       DeprecatedViews.class,
       PageRepository.class,
       ResourceTypes.class,
@@ -270,6 +276,12 @@ public class PlatformLevel4 extends PlatformLevel {
       IndexDefinitions.class,
       WebPagesFilter.class,
 
+      // edition
+      EditionInstaller.class,
+      EditionPluginDownloader.class,
+      EditionInstallerExecutor.class,
+      EditionPluginUninstaller.class,
+
       // batch
       BatchWsModule.class,
 
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginDownloader.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginDownloader.java
new file mode 100644 (file)
index 0000000..e29cd75
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT 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.sonar.server.plugins;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import org.apache.commons.io.FileUtils;
+import org.picocontainer.Startable;
+import org.sonar.api.utils.HttpDownloader;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.util.stream.MoreCollectors;
+import org.sonar.updatecenter.common.Release;
+
+import static org.apache.commons.io.FileUtils.copyFile;
+import static org.apache.commons.io.FileUtils.copyFileToDirectory;
+import static org.apache.commons.io.FileUtils.forceMkdir;
+import static org.apache.commons.io.FileUtils.toFile;
+import static org.apache.commons.lang.StringUtils.substringAfterLast;
+import static org.sonar.core.util.FileUtils.deleteQuietly;
+
+public class AbstractPluginDownloader implements Startable {
+  private static final Logger LOG = Loggers.get(AbstractPluginDownloader.class);
+
+  private static final String TMP_SUFFIX = "tmp";
+  private static final String PLUGIN_EXTENSION = "jar";
+
+  private final HttpDownloader downloader;
+  private final File downloadDir;
+
+  protected AbstractPluginDownloader(File downloadDir, HttpDownloader downloader) {
+    this.downloadDir = downloadDir;
+    this.downloader = downloader;
+  }
+
+  /**
+   * Deletes the temporary files remaining from previous downloads
+   */
+  @Override
+  public void start() {
+    try {
+      forceMkdir(downloadDir);
+      cleanTempFiles();
+    } catch (IOException e) {
+      throw new IllegalStateException("Fail to create the directory: " + downloadDir, e);
+    }
+  }
+
+  @Override
+  public void stop() {
+    // Nothing to do
+  }
+
+  public void cancelDownloads() {
+    try {
+      if (downloadDir.exists()) {
+        org.sonar.core.util.FileUtils.cleanDirectory(downloadDir);
+      }
+    } catch (IOException e) {
+      throw new IllegalStateException("Fail to clean the plugin downloads directory: " + downloadDir, e);
+    }
+  }
+
+  boolean hasDownloads() {
+    return !getDownloadedPluginFilenames().isEmpty();
+  }
+
+  List<String> getDownloadedPluginFilenames() {
+    List<String> names = new ArrayList<>();
+    for (File file : listPlugins(this.downloadDir)) {
+      names.add(file.getName());
+    }
+    return names;
+  }
+
+  /**
+   * @return the list of download plugins as {@link PluginInfo} instances
+   */
+  public Collection<PluginInfo> getDownloadedPlugins() {
+    return listPlugins(this.downloadDir)
+      .stream()
+      .map(PluginInfo::create)
+      .collect(MoreCollectors.toList());
+  }
+
+  protected void download(Release release) {
+    try {
+      downloadRelease(release);
+    } catch (Exception e) {
+      String message = String.format("Fail to download the plugin (%s, version %s) from %s (error is : %s)",
+        release.getArtifact().getKey(), release.getVersion().getName(), release.getDownloadUrl(), e.getMessage());
+      LOG.debug(message, e);
+      throw new IllegalStateException(message, e);
+    }
+  }
+
+  private void downloadRelease(Release release) throws URISyntaxException, IOException {
+    String url = release.getDownloadUrl();
+
+    URI uri = new URI(url);
+    if (url.startsWith("file:")) {
+      // used for tests
+      File file = toFile(uri.toURL());
+      copyFileToDirectory(file, downloadDir);
+    } else {
+      String filename = substringAfterLast(uri.getPath(), "/");
+      if (!filename.endsWith("." + PLUGIN_EXTENSION)) {
+        filename = release.getKey() + "-" + release.getVersion() + "." + PLUGIN_EXTENSION;
+      }
+      File targetFile = new File(downloadDir, filename);
+      File tempFile = new File(downloadDir, filename + "." + TMP_SUFFIX);
+      downloader.download(uri, tempFile);
+      copyFile(tempFile, targetFile);
+      deleteQuietly(tempFile);
+    }
+  }
+
+  protected void cleanTempFiles() {
+    Collection<File> tempFiles = FileUtils.listFiles(downloadDir, new String[] {TMP_SUFFIX}, false);
+    for (File tempFile : tempFiles) {
+      deleteQuietly(tempFile);
+    }
+  }
+
+  private static Collection<File> listPlugins(File dir) {
+    return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginUninstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/AbstractPluginUninstaller.java
new file mode 100644 (file)
index 0000000..d3b8892
--- /dev/null
@@ -0,0 +1,91 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT 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.sonar.server.plugins;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import org.apache.commons.io.FileUtils;
+import org.picocontainer.Startable;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.util.stream.MoreCollectors;
+
+import static java.lang.String.format;
+import static org.apache.commons.io.FileUtils.forceMkdir;
+
+public abstract class AbstractPluginUninstaller implements Startable {
+  private static final String PLUGIN_EXTENSION = "jar";
+
+  private final ServerPluginRepository serverPluginRepository;
+  private final File uninstallDir;
+
+  protected AbstractPluginUninstaller(ServerPluginRepository serverPluginRepository, File uninstallDir) {
+    this.serverPluginRepository = serverPluginRepository;
+    this.uninstallDir = uninstallDir;
+  }
+
+  @Override
+  public void start() {
+    try {
+      forceMkdir(uninstallDir);
+    } catch (IOException e) {
+      throw new IllegalStateException("Fail to create the directory: " + uninstallDir, e);
+    }
+  }
+
+  @Override
+  public void stop() {
+    // Nothing to do
+  }
+
+  public void uninstall(String pluginKey) {
+    ensurePluginIsInstalled(pluginKey);
+    serverPluginRepository.uninstall(pluginKey, uninstallDir);
+  }
+
+  public void cancelUninstalls() {
+    serverPluginRepository.cancelUninstalls(uninstallDir);
+  }
+
+  /**
+   * @return the list of plugins to be uninstalled as {@link PluginInfo} instances
+   */
+  public Collection<PluginInfo> getUninstalledPlugins() {
+    return listJarFiles(uninstallDir)
+      .stream()
+      .map(PluginInfo::create)
+      .collect(MoreCollectors.toList());
+  }
+
+  private static Collection<File> listJarFiles(File dir) {
+    if (dir.exists()) {
+      return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false);
+    }
+    return Collections.emptyList();
+  }
+
+  private void ensurePluginIsInstalled(String key) {
+    if (!serverPluginRepository.hasPlugin(key)) {
+      throw new IllegalArgumentException(format("Plugin [%s] is not installed", key));
+    }
+  }
+
+}
index 1a6fa83b6fed85a89ca5fa5670beb119dd9a3537..a2e087ae19d12a4368a8842640a1453770008bf0 100644 (file)
 package org.sonar.server.plugins;
 
 import com.google.common.base.Optional;
-import java.io.File;
-import java.io.IOException;
-import java.net.URI;
-import java.net.URISyntaxException;
-import java.util.ArrayList;
-import java.util.Collection;
 import java.util.List;
-import org.apache.commons.io.FileUtils;
-import org.picocontainer.Startable;
 import org.sonar.api.utils.HttpDownloader;
-import org.sonar.api.utils.SonarException;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.server.platform.ServerFileSystem;
 import org.sonar.updatecenter.common.Release;
 import org.sonar.updatecenter.common.UpdateCenter;
 import org.sonar.updatecenter.common.Version;
 
-import static org.apache.commons.io.FileUtils.copyFile;
-import static org.apache.commons.io.FileUtils.copyFileToDirectory;
-import static org.apache.commons.io.FileUtils.forceMkdir;
-import static org.apache.commons.io.FileUtils.toFile;
-import static org.apache.commons.lang.StringUtils.substringAfterLast;
-import static org.sonar.core.util.FileUtils.deleteQuietly;
 import static org.sonar.server.ws.WsUtils.checkRequest;
 
 /**
  * Downloads plugins from update center. Files are copied in the directory extensions/downloads and then
  * moved to extensions/plugins after server restart.
  */
-public class PluginDownloader implements Startable {
-
-  private static final Logger LOG = Loggers.get(PluginDownloader.class);
-  private static final String TMP_SUFFIX = "tmp";
-  private static final String PLUGIN_EXTENSION = "jar";
-
+public class PluginDownloader extends AbstractPluginDownloader {
   private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
-  private final HttpDownloader downloader;
-  private final File downloadDir;
 
-  public PluginDownloader(UpdateCenterMatrixFactory updateCenterMatrixFactory, HttpDownloader downloader,
-    ServerFileSystem fileSystem) {
+  public PluginDownloader(UpdateCenterMatrixFactory updateCenterMatrixFactory, HttpDownloader downloader, ServerFileSystem fileSystem) {
+    super(fileSystem.getDownloadedPluginsDir(), downloader);
     this.updateCenterMatrixFactory = updateCenterMatrixFactory;
-    this.downloader = downloader;
-    this.downloadDir = fileSystem.getDownloadedPluginsDir();
-  }
-
-  /**
-   * Deletes the temporary files remaining from previous downloads
-   */
-  @Override
-  public void start() {
-    try {
-      forceMkdir(downloadDir);
-      for (File tempFile : listTempFile(this.downloadDir)) {
-        deleteQuietly(tempFile);
-      }
-    } catch (IOException e) {
-      throw new IllegalStateException("Fail to create the directory: " + downloadDir, e);
-    }
-  }
-
-  @Override
-  public void stop() {
-    // Nothing to do
-  }
-
-  public void cancelDownloads() {
-    try {
-      if (downloadDir.exists()) {
-        org.sonar.core.util.FileUtils.cleanDirectory(downloadDir);
-      }
-    } catch (IOException e) {
-      throw new IllegalStateException("Fail to clean the plugin downloads directory: " + downloadDir, e);
-    }
-  }
-
-  public boolean hasDownloads() {
-    return !getDownloadedPluginFilenames().isEmpty();
-  }
-
-  public List<String> getDownloadedPluginFilenames() {
-    List<String> names = new ArrayList<>();
-    for (File file : listPlugins(this.downloadDir)) {
-      names.add(file.getName());
-    }
-    return names;
-  }
-
-  /**
-   * @return the list of download plugins as {@link PluginInfo} instances
-   */
-  public Collection<PluginInfo> getDownloadedPlugins() {
-    return listPlugins(this.downloadDir)
-      .stream()
-      .map(PluginInfo::create)
-      .collect(MoreCollectors.toList());
   }
 
   public void download(String pluginKey, Version version) {
@@ -126,45 +46,9 @@ public class PluginDownloader implements Startable {
     if (updateCenter.isPresent()) {
       List<Release> installablePlugins = updateCenter.get().findInstallablePlugins(pluginKey, version);
       checkRequest(!installablePlugins.isEmpty(), "Error while downloading plugin '%s' with version '%s'. No compatible plugin found.", pluginKey, version.getName());
-      for (Release release : installablePlugins) {
-        try {
-          downloadRelease(release);
-        } catch (Exception e) {
-          String message = String.format("Fail to download the plugin (%s, version %s) from %s (error is : %s)",
-            release.getArtifact().getKey(), release.getVersion().getName(), release.getDownloadUrl(), e.getMessage());
-          LOG.debug(message);
-          throw new SonarException(message, e);
-        }
-      }
-    }
-  }
-
-  private void downloadRelease(Release release) throws URISyntaxException, IOException {
-    String url = release.getDownloadUrl();
-
-    URI uri = new URI(url);
-    if (url.startsWith("file:")) {
-      // used for tests
-      File file = toFile(uri.toURL());
-      copyFileToDirectory(file, downloadDir);
-    } else {
-      String filename = substringAfterLast(uri.getPath(), "/");
-      if (!filename.endsWith("." + PLUGIN_EXTENSION)) {
-        filename = release.getKey() + "-" + release.getVersion() + "." + PLUGIN_EXTENSION;
+      for (Release r : installablePlugins) {
+        super.download(r);
       }
-      File targetFile = new File(downloadDir, filename);
-      File tempFile = new File(downloadDir, filename + "." + TMP_SUFFIX);
-      downloader.download(uri, tempFile);
-      copyFile(tempFile, targetFile);
-      deleteQuietly(tempFile);
     }
   }
-
-  private static Collection<File> listTempFile(File dir) {
-    return FileUtils.listFiles(dir, new String[] {TMP_SUFFIX}, false);
-  }
-
-  private static Collection<File> listPlugins(File dir) {
-    return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false);
-  }
 }
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginUninstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginUninstaller.java
new file mode 100644 (file)
index 0000000..44d2f16
--- /dev/null
@@ -0,0 +1,28 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT 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.sonar.server.plugins;
+
+import org.sonar.server.platform.ServerFileSystem;
+
+public class PluginUninstaller extends AbstractPluginUninstaller {
+  public PluginUninstaller(ServerPluginRepository serverPluginRepository, ServerFileSystem fs) {
+    super(serverPluginRepository, fs.getUninstalledPluginsDir());
+  }
+}
index 45d8a0c9cf2a0a0653a2b11918ba33b4a1069b76..bd2834c84dcbe6af6b4a3a02bb8c7a142edd7fd8 100644 (file)
@@ -47,7 +47,6 @@ import org.sonar.api.utils.log.Loggers;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.core.platform.PluginLoader;
 import org.sonar.core.platform.PluginRepository;
-import org.sonar.core.util.stream.MoreCollectors;
 import org.sonar.server.platform.ServerFileSystem;
 import org.sonar.updatecenter.common.Version;
 
@@ -92,8 +91,7 @@ public class ServerPluginRepository implements PluginRepository, Startable {
   private final Map<String, Plugin> pluginInstancesByKeys = new HashMap<>();
   private final Map<ClassLoader, String> keysByClassLoader = new HashMap<>();
 
-  public ServerPluginRepository(SonarRuntime runtime, ServerUpgradeStatus upgradeStatus,
-    ServerFileSystem fs, PluginLoader loader) {
+  public ServerPluginRepository(SonarRuntime runtime, ServerUpgradeStatus upgradeStatus, ServerFileSystem fs, PluginLoader loader) {
     this.runtime = runtime;
     this.upgradeStatus = upgradeStatus;
     this.fs = fs;
@@ -110,6 +108,7 @@ public class ServerPluginRepository implements PluginRepository, Startable {
     loadPreInstalledPlugins();
     copyBundledPlugins();
     moveDownloadedPlugins();
+    moveDownloadedEditionPlugins();
     unloadIncompatiblePlugins();
     logInstalledPlugins();
     loadInstances();
@@ -156,6 +155,14 @@ public class ServerPluginRepository implements PluginRepository, Startable {
     }
   }
 
+  private void moveDownloadedEditionPlugins() {
+    if (fs.getEditionDownloadedPluginsDir().exists()) {
+      for (File sourceFile : listJarFiles(fs.getEditionDownloadedPluginsDir())) {
+        overrideAndRegisterPlugin(sourceFile, true);
+      }
+    }
+  }
+
   /**
    * Copies the plugins bundled with SonarQube distribution to directory extensions/plugins.
    * Does nothing if not a fresh installation.
@@ -302,28 +309,36 @@ public class ServerPluginRepository implements PluginRepository, Startable {
   /**
    * Uninstall a plugin and its dependents
    */
-  public void uninstall(String pluginKey) {
-    checkState(started.get(), NOT_STARTED_YET);
-
+  public void uninstall(String pluginKey, File uninstallDir) {
     Set<String> uninstallKeys = new HashSet<>();
     uninstallKeys.add(pluginKey);
     appendDependentPluginKeys(pluginKey, uninstallKeys);
 
     for (String uninstallKey : uninstallKeys) {
-      PluginInfo info = pluginInfosByKeys.get(uninstallKey);
+      PluginInfo info = getPluginInfo(uninstallKey);
       try {
         LOG.info("Uninstalling plugin {} [{}]", info.getName(), info.getKey());
         // we don't reuse info.getFile() just to be sure that file is located in from extensions/plugins
         File masterFile = new File(fs.getInstalledPluginsDir(), info.getNonNullJarFile().getName());
-        moveFileToDirectory(masterFile, uninstalledPluginsDir(), true);
+        moveFileToDirectory(masterFile, uninstallDir, true);
       } catch (IOException e) {
         throw new IllegalStateException(format("Fail to uninstall plugin %s [%s]", info.getName(), info.getKey()), e);
       }
     }
   }
 
+  public void cancelUninstalls(File uninstallDir) {
+    for (File file : listJarFiles(uninstallDir)) {
+      try {
+        moveFileToDirectory(file, fs.getInstalledPluginsDir(), false);
+      } catch (IOException e) {
+        throw new IllegalStateException("Fail to cancel plugin uninstalls", e);
+      }
+    }
+  }
+
   private void appendDependentPluginKeys(String pluginKey, Set<String> appendTo) {
-    for (PluginInfo otherPlugin : pluginInfosByKeys.values()) {
+    for (PluginInfo otherPlugin : getPluginInfos()) {
       if (!otherPlugin.getKey().equals(pluginKey)) {
         for (PluginInfo.RequiredPlugin requirement : otherPlugin.getRequiredPlugins()) {
           if (requirement.getKey().equals(pluginKey)) {
@@ -335,30 +350,6 @@ public class ServerPluginRepository implements PluginRepository, Startable {
     }
   }
 
-  public List<String> getUninstalledPluginFilenames() {
-    return listJarFiles(uninstalledPluginsDir()).stream().map(File::getName).collect(MoreCollectors.toList());
-  }
-
-  /**
-   * @return the list of plugins to be uninstalled as {@link PluginInfo} instances
-   */
-  public Collection<PluginInfo> getUninstalledPlugins() {
-    return listJarFiles(uninstalledPluginsDir())
-      .stream()
-      .map(PluginInfo::create)
-      .collect(MoreCollectors.toList());
-  }
-
-  public void cancelUninstalls() {
-    for (File file : listJarFiles(uninstalledPluginsDir())) {
-      try {
-        moveFileToDirectory(file, fs.getInstalledPluginsDir(), false);
-      } catch (IOException e) {
-        throw new IllegalStateException("Fail to cancel plugin uninstalls", e);
-      }
-    }
-  }
-
   public Map<String, PluginInfo> getPluginInfosByKeys() {
     return pluginInfosByKeys;
   }
@@ -393,19 +384,6 @@ public class ServerPluginRepository implements PluginRepository, Startable {
     return pluginInfosByKeys.containsKey(key);
   }
 
-  /**
-   * @return existing trash dir
-   */
-  private File uninstalledPluginsDir() {
-    File dir = new File(fs.getTempDir(), "uninstalled-plugins");
-    try {
-      FileUtils.forceMkdir(dir);
-      return dir;
-    } catch (IOException e) {
-      throw new IllegalStateException("Fail to create temp directory: " + dir.getAbsolutePath(), e);
-    }
-  }
-
   private static Collection<File> listJarFiles(File dir) {
     if (dir.exists()) {
       return FileUtils.listFiles(dir, JAR_FILE_EXTENSIONS, false);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstaller.java
new file mode 100644 (file)
index 0000000..966aa26
--- /dev/null
@@ -0,0 +1,100 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT 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.sonar.server.plugins.edition;
+
+import java.util.Set;
+import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Collectors;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.server.plugins.ServerPluginRepository;
+
+public class EditionInstaller {
+  private final ReentrantLock lock = new ReentrantLock();
+  private final EditionInstallerExecutor executor;
+  private final EditionPluginDownloader editionPluginDownloader;
+  private final EditionPluginUninstaller editionPluginUninstaller;
+  private final ServerPluginRepository pluginRepository;
+
+  public EditionInstaller(EditionPluginDownloader editionDownloader, EditionPluginUninstaller editionPluginUninstaller,
+    ServerPluginRepository pluginRepository, EditionInstallerExecutor executor) {
+    this.editionPluginDownloader = editionDownloader;
+    this.editionPluginUninstaller = editionPluginUninstaller;
+    this.pluginRepository = pluginRepository;
+    this.executor = executor;
+  }
+
+  public void install(Set<String> editionPlugins) {
+    if (lock.tryLock()) {
+      try {
+        if (!requiresInstallationChange(editionPlugins)) {
+          return;
+        }
+
+        executor.execute(() -> asyncInstall(editionPlugins));
+      } catch (RuntimeException e) {
+        lock.unlock();
+        throw e;
+      }
+    } else {
+      throw new IllegalStateException("Another installation of an edition is already running");
+    }
+  }
+
+  public boolean requiresInstallationChange(Set<String> editionPluginKeys) {
+    return !pluginsToInstall(editionPluginKeys).isEmpty() || !pluginsToRemove(editionPluginKeys).isEmpty();
+  }
+
+  private void asyncInstall(Set<String> editionPluginKeys) {
+    try {
+      // TODO clean previously staged edition installations, or fail?
+      // editionPluginDownloader.cancelDownloads();
+      // editionPluginUninstaller.cancelUninstalls();
+      editionPluginDownloader.installEdition(pluginsToInstall(editionPluginKeys));
+      for (String pluginKey : pluginsToRemove(editionPluginKeys)) {
+        editionPluginUninstaller.uninstall(pluginKey);
+      }
+    } finally {
+      lock.unlock();
+    }
+  }
+
+  private Set<String> pluginsToInstall(Set<String> editionPluginKeys) {
+    Set<String> installedKeys = pluginRepository.getPluginInfosByKeys().keySet();
+    return editionPluginKeys.stream()
+      .filter(p -> !installedKeys.contains(p))
+      .collect(Collectors.toSet());
+  }
+
+  private Set<String> pluginsToRemove(Set<String> editionPluginKeys) {
+    Set<String> installedCommercialPluginKeys = pluginRepository.getPluginInfos().stream()
+      .filter(EditionInstaller::isSonarSourceCommercialPlugin)
+      .map(PluginInfo::getKey)
+      .collect(Collectors.toSet());
+
+    return installedCommercialPluginKeys.stream()
+      .filter(p -> !editionPluginKeys.contains(p))
+      .collect(Collectors.toSet());
+  }
+
+  private static boolean isSonarSourceCommercialPlugin(PluginInfo pluginInfo) {
+    return "Commercial".equals(pluginInfo.getLicense()) && "SonarSource".equals(pluginInfo.getOrganizationName());
+  }
+
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstallerExecutor.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionInstallerExecutor.java
new file mode 100644 (file)
index 0000000..9c5228d
--- /dev/null
@@ -0,0 +1,45 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT 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.sonar.server.plugins.edition;
+
+import com.google.common.util.concurrent.ThreadFactoryBuilder;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ThreadFactory;
+import org.sonar.server.util.AbstractStoppableExecutorService;
+
+public class EditionInstallerExecutor extends AbstractStoppableExecutorService<ExecutorService> {
+  public EditionInstallerExecutor() {
+    super(createExecutor());
+  }
+
+  @Override
+  public void execute(Runnable r) {
+    delegate.submit(r);
+  }
+
+  private static ExecutorService createExecutor() {
+    ThreadFactory threadFactory = new ThreadFactoryBuilder()
+      .setDaemon(true)
+      .setNameFormat("edition-installation-%d")
+      .build();
+    return Executors.newSingleThreadExecutor(threadFactory);
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginDownloader.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginDownloader.java
new file mode 100644 (file)
index 0000000..6ff8887
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT 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.sonar.server.plugins.edition;
+
+import com.google.common.base.Optional;
+import java.util.HashSet;
+import java.util.Set;
+import org.sonar.api.utils.HttpDownloader;
+import org.sonar.server.platform.ServerFileSystem;
+import org.sonar.server.plugins.AbstractPluginDownloader;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
+import org.sonar.updatecenter.common.Release;
+import org.sonar.updatecenter.common.UpdateCenter;
+import org.sonar.updatecenter.common.Version;
+
+public class EditionPluginDownloader extends AbstractPluginDownloader {
+  private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
+
+  public EditionPluginDownloader(UpdateCenterMatrixFactory updateCenterMatrixFactory, HttpDownloader downloader, ServerFileSystem fileSystem) {
+    super(fileSystem.getEditionDownloadedPluginsDir(), downloader);
+    this.updateCenterMatrixFactory = updateCenterMatrixFactory;
+  }
+
+  public void installEdition(Set<String> pluginKeys) {
+    try {
+      Optional<UpdateCenter> updateCenter = updateCenterMatrixFactory.getUpdateCenter(true);
+      if (updateCenter.isPresent()) {
+        Set<Release> pluginsToInstall = new HashSet<>();
+        for (String pluginKey : pluginKeys) {
+          pluginsToInstall.addAll(updateCenter.get().findInstallablePlugins(pluginKey, Version.create("")));
+        }
+
+        for (Release r : pluginsToInstall) {
+          super.download(r);
+        }
+      }
+    } catch (Exception e) {
+      cleanTempFiles();
+      throw e;
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginUninstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/EditionPluginUninstaller.java
new file mode 100644 (file)
index 0000000..2cb8fb6
--- /dev/null
@@ -0,0 +1,31 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT 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.sonar.server.plugins.edition;
+
+import org.sonar.server.platform.ServerFileSystem;
+import org.sonar.server.plugins.AbstractPluginUninstaller;
+import org.sonar.server.plugins.ServerPluginRepository;
+
+public class EditionPluginUninstaller extends AbstractPluginUninstaller {
+
+  public EditionPluginUninstaller(ServerPluginRepository serverPluginRepository, ServerFileSystem fs) {
+    super(serverPluginRepository, fs.getEditionUninstalledPluginsDir());
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/package-info.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/edition/package-info.java
new file mode 100644 (file)
index 0000000..dfb86bc
--- /dev/null
@@ -0,0 +1,24 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.server.plugins.edition;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
index db6c24d22c1fb15b4d2b12b534776887c3ea2bad..24dc3076c057afd42ed318e0356d5ff1effddc99 100644 (file)
@@ -23,18 +23,18 @@ import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.server.plugins.PluginDownloader;
-import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.PluginUninstaller;
 import org.sonar.server.user.UserSession;
 
 public class CancelAllAction implements PluginsWsAction {
 
   private final PluginDownloader pluginDownloader;
-  private final ServerPluginRepository pluginRepository;
+  private final PluginUninstaller pluginUninstaller;
   private final UserSession userSession;
 
-  public CancelAllAction(PluginDownloader pluginDownloader, ServerPluginRepository pluginRepository, UserSession userSession) {
+  public CancelAllAction(PluginDownloader pluginDownloader, PluginUninstaller pluginUninstaller, UserSession userSession) {
     this.pluginDownloader = pluginDownloader;
-    this.pluginRepository = pluginRepository;
+    this.pluginUninstaller = pluginUninstaller;
     this.userSession = userSession;
   }
 
@@ -54,7 +54,7 @@ public class CancelAllAction implements PluginsWsAction {
     userSession.checkIsSystemAdministrator();
 
     pluginDownloader.cancelDownloads();
-    pluginRepository.cancelUninstalls();
+    pluginUninstaller.cancelUninstalls();
 
     response.noContent();
   }
index ac821b5e67a5d9847c1a2aa1205fff78587adaaf..95adaff20a8492a7afe4913fe28bc125ed9ff373 100644 (file)
@@ -33,6 +33,7 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.server.plugins.PluginDownloader;
+import org.sonar.server.plugins.PluginUninstaller;
 import org.sonar.server.plugins.ServerPluginRepository;
 import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.server.user.UserSession;
@@ -57,13 +58,15 @@ public class PendingAction implements PluginsWsAction {
   private final ServerPluginRepository installer;
   private final PluginWSCommons pluginWSCommons;
   private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
+  private final PluginUninstaller pluginUninstaller;
 
   public PendingAction(UserSession userSession, PluginDownloader pluginDownloader,
-    ServerPluginRepository installer,
+    ServerPluginRepository installer, PluginUninstaller pluginUninstaller,
     PluginWSCommons pluginWSCommons, UpdateCenterMatrixFactory updateCenterMatrixFactory) {
     this.userSession = userSession;
     this.pluginDownloader = pluginDownloader;
     this.installer = installer;
+    this.pluginUninstaller = pluginUninstaller;
     this.pluginWSCommons = pluginWSCommons;
     this.updateCenterMatrixFactory = updateCenterMatrixFactory;
   }
@@ -93,7 +96,7 @@ public class PendingAction implements PluginsWsAction {
   }
 
   private void writePlugins(JsonWriter json, Map<String, Plugin> compatiblePluginsByKey) {
-    Collection<PluginInfo> uninstalledPlugins = installer.getUninstalledPlugins();
+    Collection<PluginInfo> uninstalledPlugins = pluginUninstaller.getUninstalledPlugins();
     Collection<PluginInfo> downloadedPlugins = pluginDownloader.getDownloadedPlugins();
     Collection<PluginInfo> installedPlugins = installer.getPluginInfos();
     MatchPluginKeys matchPluginKeys = new MatchPluginKeys(from(installedPlugins).transform(PluginInfoToKey.INSTANCE).toSet());
index 3d18adbc20abae8c5f0144352cbb5efd0312eec4..7cf974e8d680e7d7263384188437addcd56ba527 100644 (file)
@@ -22,22 +22,20 @@ package org.sonar.server.plugins.ws;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
-import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.PluginUninstaller;
 import org.sonar.server.user.UserSession;
 
-import static java.lang.String.format;
-
 /**
  * Implementation of the {@code uninstall} action for the Plugins WebService.
  */
 public class UninstallAction implements PluginsWsAction {
   private static final String PARAM_KEY = "key";
 
-  private final ServerPluginRepository pluginRepository;
+  private final PluginUninstaller pluginUninstaller;
   private final UserSession userSession;
 
-  public UninstallAction(ServerPluginRepository pluginRepository, UserSession userSession) {
-    this.pluginRepository = pluginRepository;
+  public UninstallAction(PluginUninstaller pluginUninstaller, UserSession userSession) {
+    this.pluginUninstaller = pluginUninstaller;
     this.userSession = userSession;
   }
 
@@ -61,15 +59,8 @@ public class UninstallAction implements PluginsWsAction {
     userSession.checkIsSystemAdministrator();
 
     String key = request.mandatoryParam(PARAM_KEY);
-    ensurePluginIsInstalled(key);
-    pluginRepository.uninstall(key);
+    pluginUninstaller.uninstall(key);
     response.noContent();
   }
 
-  // FIXME should be moved to ServerPluginRepository#uninstall(String)
-  private void ensurePluginIsInstalled(String key) {
-    if (!pluginRepository.hasPlugin(key)) {
-      throw new IllegalArgumentException(format("Plugin [%s] is not installed", key));
-    }
-  }
 }
index a25f586dacc1f03f2004d21cf1df22ae7c05c5e2..4325e0f8d25506484498ddcb43a67944a19411fd 100644 (file)
@@ -32,7 +32,6 @@ import org.mockito.ArgumentMatcher;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
 import org.sonar.api.utils.HttpDownloader;
-import org.sonar.api.utils.SonarException;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.server.exceptions.BadRequestException;
 import org.sonar.server.platform.ServerFileSystem;
@@ -77,9 +76,9 @@ public class PluginDownloaderTest {
     when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter));
 
     httpDownloader = mock(HttpDownloader.class);
-    doAnswer(new Answer() {
+    doAnswer(new Answer<Void>() {
       @Override
-      public Object answer(InvocationOnMock inv) throws Throwable {
+      public Void answer(InvocationOnMock inv) throws Throwable {
         File toFile = (File) inv.getArguments()[1];
         touch(toFile);
         return null;
@@ -212,7 +211,7 @@ public class PluginDownloaderTest {
     try {
       pluginDownloader.download("foo", create("1.0"));
       fail();
-    } catch (SonarException e) {
+    } catch (IllegalStateException e) {
       // ok
     }
   }
@@ -230,7 +229,7 @@ public class PluginDownloaderTest {
     try {
       pluginDownloader.download("foo", create("1.0"));
       fail();
-    } catch (SonarException e) {
+    } catch (IllegalStateException e) {
       // ok
     }
   }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java
new file mode 100644 (file)
index 0000000..90fb048
--- /dev/null
@@ -0,0 +1,105 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT 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.sonar.server.plugins;
+
+import java.io.File;
+import java.io.IOException;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.server.platform.ServerFileSystem;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+public class PluginUninstallerTest {
+  @Rule
+  public TemporaryFolder testFolder = new TemporaryFolder();
+
+  @Rule
+  public ExpectedException exception = ExpectedException.none();
+
+  private File uninstallDir;
+  private PluginUninstaller underTest;
+  private ServerPluginRepository serverPluginRepository;
+  private ServerFileSystem fs;
+
+  @Before
+  public void setUp() throws IOException {
+    serverPluginRepository = mock(ServerPluginRepository.class);
+    uninstallDir = testFolder.newFolder("uninstall");
+    fs = mock(ServerFileSystem.class);
+    when(fs.getUninstalledPluginsDir()).thenReturn(uninstallDir);
+    underTest = new PluginUninstaller(serverPluginRepository, fs);
+  }
+
+  @Test
+  public void uninstall() throws Exception {
+    when(serverPluginRepository.hasPlugin("plugin")).thenReturn(true);
+    underTest.uninstall("plugin");
+    verify(serverPluginRepository).uninstall("plugin", uninstallDir);
+  }
+
+  @Test
+  public void fail_uninstall_if_plugin_not_installed() {
+    when(serverPluginRepository.hasPlugin("plugin")).thenReturn(false);
+    exception.expect(IllegalArgumentException.class);
+    exception.expectMessage("Plugin [plugin] is not installed");
+    underTest.uninstall("plugin");
+    verifyZeroInteractions(serverPluginRepository);
+  }
+
+  @Test
+  public void create_uninstall_dir() {
+    File dir = new File(testFolder.getRoot(), "dir");
+    when(fs.getUninstalledPluginsDir()).thenReturn(dir);
+    underTest = new PluginUninstaller(serverPluginRepository, fs);
+    underTest.start();
+    assertThat(dir).isDirectory();
+  }
+
+  @Test
+  public void cancel() {
+    underTest.cancelUninstalls();
+    verify(serverPluginRepository).cancelUninstalls(uninstallDir);
+    verifyNoMoreInteractions(serverPluginRepository);
+  }
+
+  @Test
+  public void list_uninstalled_plugins() throws IOException {
+    new File(uninstallDir, "file1").createNewFile();
+    copyTestPluginTo("test-base-plugin", uninstallDir);
+    assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase");
+  }
+
+  private File copyTestPluginTo(String testPluginName, File toDir) throws IOException {
+    File jar = TestProjectUtils.jarOf(testPluginName);
+    // file is copied because it's supposed to be moved by the test
+    FileUtils.copyFileToDirectory(jar, toDir);
+    return new File(toDir, jar.getName());
+  }
+}
index 679ddddb2158c49a9cf54d05ba3616bed6c544b5..b77b9dc40460373df251dd56f9c159b9896a750c 100644 (file)
@@ -119,7 +119,6 @@ public class ServerPluginRepositoryTest {
 
     assertThat(underTest.getPluginInfos()).isEmpty();
     assertThat(underTest.getPluginInfosByKeys()).isEmpty();
-    assertThat(underTest.getUninstalledPlugins()).isEmpty();
     assertThat(underTest.hasPlugin("testbase")).isFalse();
   }
 
@@ -227,45 +226,30 @@ public class ServerPluginRepositoryTest {
   @Test
   public void uninstall() throws Exception {
     File installedJar = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir());
-
+    File uninstallDir = temp.newFolder("uninstallDir");
+    
     underTest.start();
     assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
-    underTest.uninstall("testbase");
+    underTest.uninstall("testbase", uninstallDir);
 
     assertThat(installedJar).doesNotExist();
     // still up. Will be dropped after next startup
     assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
-    assertThat(underTest.getUninstalledPluginFilenames()).containsOnly(installedJar.getName());
-    assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase");
+    assertThat(uninstallDir.list()).containsOnly(installedJar.getName());
   }
 
   @Test
   public void uninstall_dependents() throws Exception {
     File base = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir());
     File extension = copyTestPluginTo("test-require-plugin", fs.getInstalledPluginsDir());
+    File uninstallDir = temp.newFolder("uninstallDir");
 
     underTest.start();
     assertThat(underTest.getPluginInfos()).hasSize(2);
-    underTest.uninstall("testbase");
-
+    underTest.uninstall("testbase", uninstallDir);
     assertThat(base).doesNotExist();
     assertThat(extension).doesNotExist();
-    assertThat(underTest.getUninstalledPluginFilenames()).containsOnly(base.getName(), extension.getName());
-    assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase", "testrequire");
-  }
-
-  @Test
-  public void cancel_uninstall() throws Exception {
-    File base = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir());
-    underTest.start();
-
-    underTest.uninstall("testbase");
-    assertThat(base).doesNotExist();
-
-    underTest.cancelUninstalls();
-    assertThat(base).exists();
-    assertThat(underTest.getUninstalledPluginFilenames()).isEmpty();
-    assertThat(underTest.getUninstalledPlugins()).isEmpty();
+    assertThat(uninstallDir.list()).containsOnly(base.getName(), extension.getName());
   }
 
   @Test
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerExecutorTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerExecutorTest.java
new file mode 100644 (file)
index 0000000..7b13f42
--- /dev/null
@@ -0,0 +1,35 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT 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.sonar.server.plugins.edition;
+
+import org.junit.Test;
+
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.verify;
+
+public class EditionInstallerExecutorTest {
+  @Test
+  public void execute() {
+    Runnable r = mock(Runnable.class);
+    new EditionInstallerExecutor().execute(r);
+    verify(r, timeout(5000)).run();
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionInstallerTest.java
new file mode 100644 (file)
index 0000000..47a6a58
--- /dev/null
@@ -0,0 +1,102 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT 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.sonar.server.plugins.edition;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.server.plugins.ServerPluginRepository;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+public class EditionInstallerTest {
+  private static final String pluginKey = "key";
+  @Mock
+  private EditionPluginDownloader downloader;
+  @Mock
+  private EditionPluginUninstaller uninstaller;
+  @Mock
+  private ServerPluginRepository pluginRepository;
+
+  private EditionInstallerExecutor executor = new EditionInstallerExecutor() {
+    public void execute(Runnable r) {
+      r.run();
+    }
+  };
+
+  private EditionInstaller installer;
+
+  @Before
+  public void before() {
+    MockitoAnnotations.initMocks(this);
+    installer = new EditionInstaller(downloader, uninstaller, pluginRepository, executor);
+  }
+
+  @Test
+  public void install() {
+    installer.install(Collections.singleton(pluginKey));
+    verify(downloader).installEdition(Collections.singleton(pluginKey));
+  }
+
+  @Test
+  public void check_plugins_to_install_and_remove() {
+    PluginInfo commercial1 = createPluginInfo("p1", true);
+    PluginInfo commercial2 = createPluginInfo("p2", true);
+    PluginInfo open1 = createPluginInfo("p3", false);
+    mockPluginRepository(commercial1, commercial2, open1);
+
+    Set<String> editionPlugins = new HashSet<>();
+    editionPlugins.add("p1");
+    editionPlugins.add("p4");
+    installer.install(editionPlugins);
+
+    verify(downloader).installEdition(Collections.singleton("p4"));
+    verify(uninstaller).uninstall("p2");
+    verifyNoMoreInteractions(uninstaller);
+    verifyNoMoreInteractions(downloader);
+
+  }
+
+  private void mockPluginRepository(PluginInfo... installedPlugins) {
+    Map<String, PluginInfo> pluginsByKey = Arrays.asList(installedPlugins).stream().collect(Collectors.toMap(p -> p.getKey(), p -> p));
+    when(pluginRepository.getPluginInfosByKeys()).thenReturn(pluginsByKey);
+    when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(installedPlugins));
+  }
+
+  private static PluginInfo createPluginInfo(String pluginKey, boolean commercial) {
+    PluginInfo info = new PluginInfo(pluginKey);
+    if (commercial) {
+      info.setOrganizationName("SonarSource");
+      info.setLicense("Commercial");
+    }
+    return info;
+  }
+
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginDownloaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginDownloaderTest.java
new file mode 100644 (file)
index 0000000..b2288f9
--- /dev/null
@@ -0,0 +1,95 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT 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.sonar.server.plugins.edition;
+
+import com.google.common.base.Optional;
+import java.io.File;
+import java.io.IOException;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collections;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.sonar.api.utils.HttpDownloader;
+import org.sonar.server.platform.ServerFileSystem;
+import org.sonar.server.plugins.UpdateCenterMatrixFactory;
+import org.sonar.updatecenter.common.Release;
+import org.sonar.updatecenter.common.UpdateCenter;
+import org.sonar.updatecenter.common.Version;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Matchers.anyBoolean;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+public class EditionPluginDownloaderTest {
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+  @Mock
+  private UpdateCenterMatrixFactory updateCenterMatrixFactory;
+  @Mock
+  private UpdateCenter updateCenter;
+  @Mock
+  private ServerFileSystem fs;
+  @Mock
+  private HttpDownloader httpDownloader;
+
+  private File downloadDir;
+  private EditionPluginDownloader downloader;
+
+  @Before
+  public void setUp() throws IOException {
+    MockitoAnnotations.initMocks(this);
+    downloadDir = temp.newFolder();
+    when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.of(updateCenter));
+    when(fs.getEditionDownloadedPluginsDir()).thenReturn(downloadDir);
+    downloader = new EditionPluginDownloader(updateCenterMatrixFactory, httpDownloader, fs);
+  }
+
+  @Test
+  public void download_plugin_to_tmp_and_rename_it() throws IOException, URISyntaxException {
+    String url = "http://host/plugin.jar";
+    Release release = createRelease("pluginKey", "1.0", url);
+
+    // mock file downloaded
+    File tmp = new File(downloadDir, "plugin.jar.tmp");
+    tmp.createNewFile();
+
+    when(updateCenter.findInstallablePlugins("pluginKey", Version.create(""))).thenReturn(Collections.singletonList(release));
+    downloader.installEdition(Collections.singleton("pluginKey"));
+
+    verify(httpDownloader).download(new URI(url), tmp);
+    assertThat(tmp).doesNotExist();
+    assertThat(new File(downloadDir, "plugin.jar")).isFile();
+  }
+
+  private static Release createRelease(String key, String version, String url) {
+    Release release = mock(Release.class);
+    when(release.getKey()).thenReturn(key);
+    when(release.getVersion()).thenReturn(Version.create(version));
+    when(release.getDownloadUrl()).thenReturn(url);
+    return release;
+  }
+}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginUninstallerTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/edition/EditionPluginUninstallerTest.java
new file mode 100644 (file)
index 0000000..c368766
--- /dev/null
@@ -0,0 +1,52 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT 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.sonar.server.plugins.edition;
+
+import java.io.File;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.server.platform.ServerFileSystem;
+import org.sonar.server.plugins.ServerPluginRepository;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+public class EditionPluginUninstallerTest {
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  @Test
+  public void start_creates_uninstall_directory() {
+    File dir = new File(temp.getRoot(), "uninstall");
+    ServerPluginRepository repo = mock(ServerPluginRepository.class);
+    ServerFileSystem fs = mock(ServerFileSystem.class);
+    when(fs.getEditionUninstalledPluginsDir()).thenReturn(dir);
+    EditionPluginUninstaller uninstaller = new EditionPluginUninstaller(repo, fs);
+
+    uninstaller.start();
+    verify(fs).getEditionUninstalledPluginsDir();
+    verifyNoMoreInteractions(fs);
+    assertThat(dir).isDirectory();
+  }
+}
index 95f1bb89faa34ca7730fe0f270e16de4448e7c21..5aea270bb83186e926bc75db7f8d72eb7718006f 100644 (file)
@@ -40,10 +40,9 @@ import static org.sonar.updatecenter.common.Version.create;
 
 public abstract class AbstractUpdateCenterBasedPluginsWsActionTest {
   protected static final String DUMMY_CONTROLLER_KEY = "dummy";
-  protected static final String JSON_EMPTY_PLUGIN_LIST =
-    "{" +
-      "  \"plugins\":" + "[]" +
-      "}";
+  protected static final String JSON_EMPTY_PLUGIN_LIST = "{" +
+    "  \"plugins\":" + "[]" +
+    "}";
   protected static final Plugin PLUGIN_1 = Plugin.factory("pkey1").setName("p_name_1");
   protected static final Plugin PLUGIN_2 = Plugin.factory("pkey2").setName("p_name_2").setDescription("p_desc_2");
 
@@ -68,8 +67,7 @@ public abstract class AbstractUpdateCenterBasedPluginsWsActionTest {
   protected static PluginUpdate pluginUpdate(String key, String name) {
     return PluginUpdate.createWithStatus(
       new Release(Plugin.factory(key).setName(name), Version.create("1.0")),
-      COMPATIBLE
-      );
+      COMPATIBLE);
   }
 
   @Before
index 8afbd90df8b9f2f4fabd7971c7c895752dc1c809..bf3166325c6e4d4fc5419e89f29827c879e52963 100644 (file)
@@ -26,6 +26,7 @@ import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.plugins.PluginDownloader;
+import org.sonar.server.plugins.PluginUninstaller;
 import org.sonar.server.plugins.ServerPluginRepository;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.WsTester;
@@ -44,8 +45,8 @@ public class CancelAllActionTest {
   public ExpectedException expectedException = ExpectedException.none();
 
   private PluginDownloader pluginDownloader = mock(PluginDownloader.class);
-  private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
-  private CancelAllAction underTest = new CancelAllAction(pluginDownloader, pluginRepository, userSessionRule);
+  private PluginUninstaller pluginUninstaller = mock(PluginUninstaller.class);
+  private CancelAllAction underTest = new CancelAllAction(pluginDownloader, pluginUninstaller, userSessionRule);
 
   private Request request = mock(Request.class);
   private WsTester.TestResponse response = new WsTester.TestResponse();
@@ -94,7 +95,7 @@ public class CancelAllActionTest {
     underTest.handle(request, response);
 
     verify(pluginDownloader, times(1)).cancelDownloads();
-    verify(pluginRepository, times(1)).cancelUninstalls();
+    verify(pluginUninstaller, times(1)).cancelUninstalls();
     assertThat(response.outputAsString()).isEmpty();
   }
 
index cf44b13927c42d603fa860d59e6ea57fd13f0638..17f9004c9f13da31c038a04d2add7caa22fbe0c6 100644 (file)
@@ -147,8 +147,7 @@ public class InstallActionTest {
     logInAsSystemAdministrator();
     Version version = Version.create("1.0");
     when(updateCenter.findAvailablePlugins()).thenReturn(ImmutableList.of(
-      PluginUpdate.createWithStatus(new Release(Plugin.factory(PLUGIN_KEY), version), PluginUpdate.Status.COMPATIBLE)
-      ));
+      PluginUpdate.createWithStatus(new Release(Plugin.factory(PLUGIN_KEY), version), PluginUpdate.Status.COMPATIBLE)));
 
     WsTester.Result result = validRequest.execute();
 
index eee591dc71a2ba45b30b461a563e9ecb4ce3761a..6824c101484ef29f1c9730d8fe66b1ef63635a99 100644 (file)
@@ -30,6 +30,7 @@ import org.sonar.api.server.ws.WebService;
 import org.sonar.core.platform.PluginInfo;
 import org.sonar.server.exceptions.ForbiddenException;
 import org.sonar.server.plugins.PluginDownloader;
+import org.sonar.server.plugins.PluginUninstaller;
 import org.sonar.server.plugins.ServerPluginRepository;
 import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.server.tester.UserSessionRule;
@@ -55,9 +56,11 @@ public class PendingActionTest {
   public ExpectedException expectedException = ExpectedException.none();
 
   private PluginDownloader pluginDownloader = mock(PluginDownloader.class);
+  private PluginUninstaller pluginUninstaller = mock(PluginUninstaller.class);
   private ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
   private UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class, RETURNS_DEEP_STUBS);
-  private PendingAction underTest = new PendingAction(userSession, pluginDownloader, serverPluginRepository, new PluginWSCommons(), updateCenterMatrixFactory);
+  private PendingAction underTest = new PendingAction(userSession, pluginDownloader, serverPluginRepository,
+    pluginUninstaller, new PluginWSCommons(), updateCenterMatrixFactory);
   private Request request = mock(Request.class);
   private WsTester.TestResponse response = new WsTester.TestResponse();
 
@@ -157,7 +160,7 @@ public class PendingActionTest {
   @Test
   public void verify_properties_displayed_in_json_per_removing_plugin() throws Exception {
     logInAsSystemAdministrator();
-    when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(newScmGitPluginInfo()));
+    when(pluginUninstaller.getUninstalledPlugins()).thenReturn(of(newScmGitPluginInfo()));
 
     underTest.handle(request, response);
 
@@ -214,7 +217,7 @@ public class PendingActionTest {
 
     newUpdateCenter("scmgit");
     when(serverPluginRepository.getPluginInfos()).thenReturn(of(installed));
-    when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(removedPlugin));
+    when(pluginUninstaller.getUninstalledPlugins()).thenReturn(of(removedPlugin));
     when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(newPlugin, installed));
 
     underTest.handle(request, response);
@@ -277,7 +280,7 @@ public class PendingActionTest {
   @Test
   public void removing_plugins_are_sorted_and_unique() throws Exception {
     logInAsSystemAdministrator();
-    when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(
+    when(pluginUninstaller.getUninstalledPlugins()).thenReturn(of(
       newPluginInfo(0).setName("Foo"),
       newPluginInfo(3).setName("Bar"),
       newPluginInfo(2).setName("Bar")));
index 33a97bb4c8da888a3ac29bd79aacdfee6351cc6d..3db9d6d45ab488dcbd765ce7c5b67acf7042a2c9 100644 (file)
@@ -53,8 +53,7 @@ public class PluginUpdateAggregatorTest {
       createPluginUpdate("key1"),
       createPluginUpdate("key0"),
       createPluginUpdate("key2"),
-      createPluginUpdate("key0")
-      ));
+      createPluginUpdate("key0")));
 
     assertThat(aggregates).hasSize(3);
     assertThat(aggregates).extracting("plugin.key").containsOnlyOnce("key1", "key0", "key2");
@@ -68,8 +67,7 @@ public class PluginUpdateAggregatorTest {
     Collection<PluginUpdateAggregator.PluginUpdateAggregate> aggregates = underTest.aggregate(ImmutableList.of(
       pluginUpdate1,
       pluginUpdate2,
-      pluginUpdate3
-      ));
+      pluginUpdate3));
 
     assertThat(aggregates).hasSize(1);
     Collection<PluginUpdate> releases = aggregates.iterator().next().getUpdates();
index a0c5ea72136ac3dda17a21ff6f7a5715575594c7..75902e3a98267c309221822192150a31edd7ac10 100644 (file)
@@ -25,14 +25,13 @@ import org.junit.rules.ExpectedException;
 import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.server.exceptions.ForbiddenException;
-import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.PluginUninstaller;
 import org.sonar.server.tester.UserSessionRule;
 import org.sonar.server.ws.WsTester;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
 public class UninstallActionTest {
   private static final String DUMMY_CONTROLLER_KEY = "dummy";
@@ -46,8 +45,8 @@ public class UninstallActionTest {
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
 
-  private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
-  private UninstallAction underTest = new UninstallAction(pluginRepository, userSessionRule);
+  private PluginUninstaller pluginUninstaller = mock(PluginUninstaller.class);
+  private UninstallAction underTest = new UninstallAction(pluginUninstaller, userSessionRule);
 
   private WsTester wsTester = new WsTester(new PluginsWs(underTest));
   private Request invalidRequest = wsTester.newGetRequest(CONTROLLER_KEY, ACTION_KEY);
@@ -106,24 +105,13 @@ public class UninstallActionTest {
     underTest.handle(invalidRequest, response);
   }
 
-  @Test
-  public void IAE_is_raised_when_plugin_is_not_installed() throws Exception {
-    logInAsSystemAdministrator();
-
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Plugin [findbugs] is not installed");
-
-    underTest.handle(validRequest, response);
-  }
-
   @Test
   public void if_plugin_is_installed_uninstallation_is_triggered() throws Exception {
     logInAsSystemAdministrator();
-    when(pluginRepository.hasPlugin(PLUGIN_KEY)).thenReturn(true);
 
     underTest.handle(validRequest, response);
 
-    verify(pluginRepository).uninstall(PLUGIN_KEY);
+    verify(pluginUninstaller).uninstall(PLUGIN_KEY);
     assertThat(response.outputAsString()).isEmpty();
   }
 
index bc1bbfda0d48085ad52d5b0362c628f038adc62b..aeecbd715a2416b0fab00a23039c333010db0436 100644 (file)
@@ -151,8 +151,7 @@ public class UpdateActionTest {
     logInAsSystemAdministrator();
     Version version = Version.create("1.0");
     when(updateCenter.findPluginUpdates()).thenReturn(ImmutableList.of(
-      PluginUpdate.createWithStatus(new Release(Plugin.factory(PLUGIN_KEY), version), Status.COMPATIBLE)
-    ));
+      PluginUpdate.createWithStatus(new Release(Plugin.factory(PLUGIN_KEY), version), Status.COMPATIBLE)));
 
     underTest.handle(validRequest, response);