]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-10591 refactor management of plugin FS on server
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Thu, 10 May 2018 19:46:03 +0000 (21:46 +0200)
committerSonarTech <sonartech@sonarsource.com>
Fri, 11 May 2018 18:20:47 +0000 (20:20 +0200)
24 files changed:
server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java
server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.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/PlatformLevel2.java
server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPlugin.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/plugins/PluginCompression.java [deleted file]
server/sonar-server/src/main/java/org/sonar/server/plugins/PluginFileSystem.java [new file with mode: 0644]
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.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/PluginWSCommons.java
server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java
server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPlugins.java
server/sonar-server/src/test/java/org/sonar/server/plugins/PluginCompressionTest.java [deleted file]
server/sonar-server/src/test/java/org/sonar/server/plugins/PluginFileSystemTest.java [new file with mode: 0644]
server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.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/PluginWSCommonsTest.java
server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
server/sonar-server/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java
sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java

index 9a93936177d2e7fcad16ba91849d89b09e14afd6..a5828ba8c780bf9c6ef8803f733afc91a38045ee 100644 (file)
@@ -102,11 +102,6 @@ public class CePluginJarExploderTest {
       throw new UnsupportedOperationException();
     }
 
-    @Override
-    public File getDeployDir() {
-      throw new UnsupportedOperationException();
-    }
-
     @Override
     public File getHomeDir() {
       throw new UnsupportedOperationException();
index 96e44e7e4be32db87f64245301de2431c4af7139..579e037dea9dc112d094f02afb6cc7bd8a68798c 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.db.plugin;
 
 import javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
 import org.apache.commons.lang.builder.ToStringBuilder;
 
 public class PluginDto {
@@ -59,7 +60,7 @@ public class PluginDto {
     return basePluginKey;
   }
 
-  public PluginDto setBasePluginKey(String s) {
+  public PluginDto setBasePluginKey(@Nullable String s) {
     this.basePluginKey = s;
     return this;
   }
index 486e955de0af8c05bb8929a00527f10af2131d2c..17524356cc2b6c9534f002dbf8135d415589e2cc 100644 (file)
@@ -24,8 +24,6 @@ import java.io.File;
 /**
  * Replaces the incomplete {@link org.sonar.api.platform.ServerFileSystem} as many directories can't be
  * published in API.
- *
- * @since 6.0
  */
 public interface ServerFileSystem {
 
@@ -35,12 +33,6 @@ public interface ServerFileSystem {
    */
   File getDataDir();
 
-  /**
-   * Directory accessible by scanners through web server
-   * @return a directory which may or not exist
-   */
-  File getDeployDir();
-
   /**
    * Root directory of the server installation
    * @return an existing directory
@@ -89,7 +81,9 @@ public interface ServerFileSystem {
   /**
    * The file listing all the installed plugins. Used by scanner only.
    * @return an existing file
+   * @deprecated see {@link org.sonar.server.startup.GeneratePluginIndex}
    */
+  @Deprecated
   File getPluginIndex();
 
   /**
index 5ef18cbfb4026e6e70b408aed24230b39f7a7783..6764f85b804ab69894a8208fd70c616c9db96d9a 100644 (file)
@@ -75,14 +75,9 @@ public class ServerFileSystemImpl implements ServerFileSystem, org.sonar.api.pla
     return dataDir;
   }
 
-  @Override
-  public File getDeployDir() {
-    return deployDir;
-  }
-
   @Override
   public File getDeployedPluginsDir() {
-    return new File(getDeployDir(), "plugins");
+    return new File(deployDir, "plugins");
   }
 
   @Override
@@ -107,7 +102,7 @@ public class ServerFileSystemImpl implements ServerFileSystem, org.sonar.api.pla
 
   @Override
   public File getPluginIndex() {
-    return new File(getDeployDir(), "plugins/index.txt");
+    return new File(deployDir, "plugins/index.txt");
   }
 
   @Override
index f54d401197a4e09cb37c1703ed53290cd80c08ec..a67ce655e5147d6276ff0e61a088179830bbb1ab 100644 (file)
@@ -36,7 +36,7 @@ import org.sonar.server.platform.db.migration.history.MigrationHistoryTable;
 import org.sonar.server.platform.db.migration.history.MigrationHistoryTableImpl;
 import org.sonar.server.platform.db.migration.version.DatabaseVersion;
 import org.sonar.server.plugins.InstalledPluginReferentialFactory;
-import org.sonar.server.plugins.PluginCompression;
+import org.sonar.server.plugins.PluginFileSystem;
 import org.sonar.server.plugins.ServerPluginJarExploder;
 import org.sonar.server.plugins.ServerPluginRepository;
 import org.sonar.server.plugins.WebServerExtensionInstaller;
@@ -62,7 +62,7 @@ public class PlatformLevel2 extends PlatformLevel {
       ServerPluginRepository.class,
       ServerPluginJarExploder.class,
       PluginLoader.class,
-      PluginCompression.class,
+      PluginFileSystem.class,
       PluginClassloaderFactory.class,
       InstalledPluginReferentialFactory.class,
       WebServerExtensionInstaller.class,
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPlugin.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPlugin.java
new file mode 100644 (file)
index 0000000..024fac6
--- /dev/null
@@ -0,0 +1,81 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.io.InputStream;
+import javax.annotation.Nullable;
+import javax.annotation.concurrent.Immutable;
+import org.apache.commons.codec.digest.DigestUtils;
+import org.apache.commons.io.FileUtils;
+import org.sonar.core.platform.PluginInfo;
+
+import static java.util.Objects.requireNonNull;
+
+@Immutable
+public class InstalledPlugin {
+  private final PluginInfo plugin;
+  private final FileAndMd5 loadedJar;
+  @Nullable
+  private final FileAndMd5 compressedJar;
+
+  public InstalledPlugin(PluginInfo plugin, FileAndMd5 loadedJar, @Nullable FileAndMd5 compressedJar) {
+    this.plugin = requireNonNull(plugin);
+    this.loadedJar = requireNonNull(loadedJar);
+    this.compressedJar = compressedJar;
+  }
+
+  public PluginInfo getPluginInfo() {
+    return plugin;
+  }
+
+  public FileAndMd5 getLoadedJar() {
+    return loadedJar;
+  }
+
+  @Nullable
+  public FileAndMd5 getCompressedJar() {
+    return compressedJar;
+  }
+
+  @Immutable
+  public static final class FileAndMd5 {
+    private final File file;
+    private final String md5;
+
+    public FileAndMd5(File file) {
+      try (InputStream fis = FileUtils.openInputStream(file)) {
+        this.file = file;
+        this.md5 = DigestUtils.md5Hex(fis);
+      } catch (IOException e) {
+        throw new IllegalStateException("Fail to compute md5 of " + file, e);
+      }
+    }
+
+    public File getFile() {
+      return file;
+    }
+
+    public String getMd5() {
+      return md5;
+    }
+  }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginCompression.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginCompression.java
deleted file mode 100644 (file)
index 9bb993e..0000000
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.BufferedInputStream;
-import java.io.BufferedOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.jar.JarInputStream;
-import java.util.jar.Pack200;
-import java.util.zip.GZIPOutputStream;
-import org.apache.commons.codec.digest.DigestUtils;
-import org.sonar.api.config.Configuration;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.api.utils.log.Profiler;
-import org.sonar.core.platform.RemotePluginFile;
-import org.sonar.core.util.FileUtils;
-
-public class PluginCompression {
-  private static final Logger LOG = Loggers.get(PluginCompression.class);
-  static final String PROPERTY_PLUGIN_COMPRESSION_ENABLE = "sonar.pluginsCompression.enable";
-
-  private final Map<String, RemotePluginFile> compressedPlugins = new HashMap<>();
-  private final Configuration configuration;
-
-  public PluginCompression(Configuration configuration) {
-    this.configuration = configuration;
-  }
-
-  public void compressJar(String pluginKey, Path sourceDir, Path targetJarFile) {
-    if (configuration.getBoolean(PROPERTY_PLUGIN_COMPRESSION_ENABLE).orElse(false)) {
-      Path targetPack200Path = FileUtils.getPack200FilePath(targetJarFile);
-      Path sourcePack200Path = sourceDir.resolve(targetPack200Path.getFileName());
-
-      // check if packed file was deployed alongside the jar. If that's the case, use it instead of generating it (SONAR-10395).
-      if (Files.isRegularFile(sourcePack200Path)) {
-        try {
-          LOG.debug("Found pack200: " + sourcePack200Path);
-          Files.copy(sourcePack200Path, targetPack200Path);
-        } catch (IOException e) {
-          throw new IllegalStateException("Failed to copy pack200 file from " + sourcePack200Path + " to " + targetPack200Path, e);
-        }
-      } else {
-        pack200(targetJarFile, targetPack200Path, pluginKey);
-      }
-
-      String hash = calculateMd5(targetPack200Path);
-      RemotePluginFile compressedPlugin = new RemotePluginFile(targetPack200Path.getFileName().toString(), hash);
-      compressedPlugins.put(pluginKey, compressedPlugin);
-    }
-  }
-
-  public Map<String, RemotePluginFile> getPlugins() {
-    return new HashMap<>(compressedPlugins);
-  }
-
-  private static String calculateMd5(Path filePath) {
-    try (InputStream fis = new BufferedInputStream(Files.newInputStream(filePath))) {
-      return DigestUtils.md5Hex(fis);
-    } catch (IOException e) {
-      throw new IllegalStateException("Fail to compute hash", e);
-    }
-  }
-
-  private static void pack200(Path jarPath, Path toPath, String pluginKey) {
-    Profiler profiler = Profiler.create(LOG);
-    profiler.startInfo("Compressing with pack200 plugin: " + pluginKey);
-    Pack200.Packer packer = Pack200.newPacker();
-
-    try (JarInputStream in = new JarInputStream(new BufferedInputStream(Files.newInputStream(jarPath)));
-      OutputStream out = new GZIPOutputStream(new BufferedOutputStream(Files.newOutputStream(toPath)))) {
-      packer.pack(in, out);
-    } catch (IOException e) {
-      throw new IllegalStateException(String.format("Fail to pack200 plugin [%s] '%s' to '%s'", pluginKey, jarPath, toPath), e);
-    }
-    profiler.stopInfo();
-  }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginFileSystem.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginFileSystem.java
new file mode 100644 (file)
index 0000000..0329ab8
--- /dev/null
@@ -0,0 +1,124 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.BufferedInputStream;
+import java.io.BufferedOutputStream;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.jar.JarInputStream;
+import java.util.jar.Pack200;
+import java.util.zip.GZIPOutputStream;
+import org.sonar.api.config.Configuration;
+import org.sonar.api.server.ServerSide;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.api.utils.log.Profiler;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.server.plugins.InstalledPlugin.FileAndMd5;
+
+import static com.google.common.base.Preconditions.checkState;
+
+@ServerSide
+public class PluginFileSystem {
+
+  public static final String PROPERTY_PLUGIN_COMPRESSION_ENABLE = "sonar.pluginsCompression.enable";
+  private static final Logger LOG = Loggers.get(PluginFileSystem.class);
+
+  private final Configuration configuration;
+  private final Map<String, InstalledPlugin> installedFiles = new HashMap<>();
+
+  public PluginFileSystem(Configuration configuration) {
+    this.configuration = configuration;
+  }
+
+  /**
+   * @param plugin
+   * @param loadedJar the JAR loaded by classloaders. It differs from {@code plugin.getJarFile()}
+   *                  which is the initial location of JAR as seen by users
+   */
+  public void addInstalledPlugin(PluginInfo plugin, File loadedJar) {
+    checkState(!installedFiles.containsKey(plugin.getKey()), "Plugin %s is already loaded", plugin.getKey());
+    checkState(loadedJar.exists(), "loadedJar does not exist: %s", loadedJar);
+
+    Optional<File> compressed = compressJar(plugin, loadedJar);
+    InstalledPlugin installedFile = new InstalledPlugin(
+      plugin,
+      new FileAndMd5(loadedJar),
+      compressed.map(FileAndMd5::new).orElse(null));
+    installedFiles.put(plugin.getKey(), installedFile);
+  }
+
+  public Optional<InstalledPlugin> getInstalledPlugin(String pluginKey) {
+    return Optional.ofNullable(installedFiles.get(pluginKey));
+  }
+
+  public Collection<InstalledPlugin> getInstalledFiles() {
+    return installedFiles.values();
+  }
+
+  private Optional<File> compressJar(PluginInfo plugin, File jar) {
+    if (!configuration.getBoolean(PROPERTY_PLUGIN_COMPRESSION_ENABLE).orElse(false)) {
+      return Optional.empty();
+    }
+
+    Path targetPack200 = getPack200Path(jar.toPath());
+    Path sourcePack200Path = getPack200Path(plugin.getNonNullJarFile().toPath());
+
+    // check if packed file was deployed alongside the jar. If that's the case, use it instead of generating it (SONAR-10395).
+    if (sourcePack200Path.toFile().exists()) {
+      try {
+        LOG.debug("Found pack200: " + sourcePack200Path);
+        Files.copy(sourcePack200Path, targetPack200);
+      } catch (IOException e) {
+        throw new IllegalStateException("Failed to copy pack200 file from " + sourcePack200Path + " to " + targetPack200, e);
+      }
+    } else {
+      pack200(jar.toPath(), targetPack200, plugin.getKey());
+    }
+    return Optional.of(targetPack200.toFile());
+  }
+
+  private static void pack200(Path jarPath, Path toPack200Path, String pluginKey) {
+    Profiler profiler = Profiler.create(LOG);
+    profiler.startInfo("Compressing plugin " + pluginKey + " [pack200]");
+
+    try (JarInputStream in = new JarInputStream(new BufferedInputStream(Files.newInputStream(jarPath)));
+      OutputStream out = new GZIPOutputStream(new BufferedOutputStream(Files.newOutputStream(toPack200Path)))) {
+      Pack200.newPacker().pack(in, out);
+    } catch (IOException e) {
+      throw new IllegalStateException(String.format("Fail to pack200 plugin [%s] '%s' to '%s'", pluginKey, jarPath, toPack200Path), e);
+    }
+    profiler.stopInfo();
+  }
+
+  private static Path getPack200Path(Path jar) {
+    String jarFileName = jar.getFileName().toString();
+    String filename = jarFileName.substring(0, jarFileName.length() - 3) + "pack.gz";
+    return jar.resolveSibling(filename);
+  }
+}
index 4a5942084e33517de9951db173e321f284c486f7..faf5c8e2f1d47c06f7c790a73c34f5f919c2b655 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.server.plugins;
 
 import java.io.File;
 import org.apache.commons.io.FileUtils;
-import org.sonar.api.ce.ComputeEngineSide;
 import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.ZipUtils;
 import org.sonar.core.platform.ExplodedPlugin;
@@ -32,14 +31,13 @@ import org.sonar.server.platform.ServerFileSystem;
 import static org.apache.commons.io.FileUtils.forceMkdir;
 
 @ServerSide
-@ComputeEngineSide
 public class ServerPluginJarExploder extends PluginJarExploder {
   private final ServerFileSystem fs;
-  private final PluginCompression pluginCompression;
+  private final PluginFileSystem pluginFileSystem;
 
-  public ServerPluginJarExploder(ServerFileSystem fs, PluginCompression pluginCompression) {
+  public ServerPluginJarExploder(ServerFileSystem fs, PluginFileSystem pluginFileSystem) {
     this.fs = fs;
-    this.pluginCompression = pluginCompression;
+    this.pluginFileSystem = pluginFileSystem;
   }
 
   /**
@@ -58,9 +56,10 @@ public class ServerPluginJarExploder extends PluginJarExploder {
       File jarTarget = new File(toDir, jarSource.getName());
 
       FileUtils.copyFile(jarSource, jarTarget);
-      pluginCompression.compressJar(pluginInfo.getKey(), jarSource.toPath().getParent(), jarTarget.toPath());
       ZipUtils.unzip(jarSource, toDir, newLibFilter());
-      return explodeFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir);
+      ExplodedPlugin explodedPlugin = explodeFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir);
+      pluginFileSystem.addInstalledPlugin(pluginInfo, jarTarget);
+      return explodedPlugin;
     } catch (Exception e) {
       throw new IllegalStateException(String.format(
         "Fail to unzip plugin [%s] %s to %s", pluginInfo.getKey(), pluginInfo.getNonNullJarFile().getAbsolutePath(), toDir.getAbsolutePath()), e);
index 0be17a3958dd5702981c9757ae65d5423fba474c..254805884d93066ad5644fa106c7324c783b0433 100644 (file)
@@ -105,6 +105,7 @@ public class ServerPluginRepository implements PluginRepository, Startable {
 
   @Override
   public void start() {
+    long begin = System.currentTimeMillis();
     loadPreInstalledPlugins();
     copyBundledPlugins();
     moveDownloadedPlugins();
index f65a2f35c15ae2db1c6559378c2ff9a1401c8066..f02219567ab738233844e8f4ce3ea22de46ec5ab 100644 (file)
@@ -21,9 +21,9 @@ package org.sonar.server.plugins.ws;
 
 import com.google.common.io.Resources;
 import java.util.Collection;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.SortedSet;
 import java.util.function.Function;
 import org.sonar.api.server.ws.Change;
@@ -31,20 +31,21 @@ import org.sonar.api.server.ws.Request;
 import org.sonar.api.server.ws.Response;
 import org.sonar.api.server.ws.WebService;
 import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.core.platform.PluginInfo;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.plugin.PluginDto;
-import org.sonar.server.plugins.PluginCompression;
-import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.InstalledPlugin;
+import org.sonar.server.plugins.PluginFileSystem;
 import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.updatecenter.common.Plugin;
 
 import static com.google.common.collect.ImmutableSortedSet.copyOf;
 import static java.lang.String.format;
+import static java.util.Collections.emptyMap;
 import static java.util.Collections.singleton;
 import static java.util.stream.Collectors.toMap;
-import static org.sonar.server.plugins.ws.PluginWSCommons.NAME_KEY_PLUGIN_METADATA_COMPARATOR;
+import static org.sonar.server.plugins.ws.PluginWSCommons.NAME_KEY_COMPARATOR;
+import static org.sonar.server.plugins.ws.PluginWSCommons.categoryOrNull;
 import static org.sonar.server.plugins.ws.PluginWSCommons.compatiblePluginsByKey;
 
 /**
@@ -54,17 +55,13 @@ public class InstalledAction implements PluginsWsAction {
   private static final String ARRAY_PLUGINS = "plugins";
   private static final String FIELD_CATEGORY = "category";
 
-  private final ServerPluginRepository pluginRepository;
-  private final PluginWSCommons pluginWSCommons;
+  private final PluginFileSystem pluginFileSystem;
   private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
   private final DbClient dbClient;
-  private final PluginCompression compression;
 
-  public InstalledAction(ServerPluginRepository pluginRepository, PluginCompression compression, PluginWSCommons pluginWSCommons,
+  public InstalledAction(PluginFileSystem pluginFileSystem,
     UpdateCenterMatrixFactory updateCenterMatrixFactory, DbClient dbClient) {
-    this.pluginRepository = pluginRepository;
-    this.compression = compression;
-    this.pluginWSCommons = pluginWSCommons;
+    this.pluginFileSystem = pluginFileSystem;
     this.updateCenterMatrixFactory = updateCenterMatrixFactory;
     this.dbClient = dbClient;
   }
@@ -93,31 +90,33 @@ public class InstalledAction implements PluginsWsAction {
 
   @Override
   public void handle(Request request, Response response) throws Exception {
-    Collection<PluginInfo> pluginInfoList = searchPluginInfoList();
-    Map<String, PluginDto> pluginDtosByKey;
+    Collection<InstalledPlugin> installedPlugins = loadInstalledPlugins();
+    Map<String, PluginDto> dtosByKey;
     try (DbSession dbSession = dbClient.openSession(false)) {
-      pluginDtosByKey = dbClient.pluginDao().selectAll(dbSession).stream().collect(toMap(PluginDto::getKee, Function.identity()));
+      dtosByKey = dbClient.pluginDao().selectAll(dbSession).stream().collect(toMap(PluginDto::getKee, Function.identity()));
     }
 
-    JsonWriter jsonWriter = response.newJsonWriter();
-    jsonWriter.setSerializeEmptys(false);
-    jsonWriter.beginObject();
+    JsonWriter json = response.newJsonWriter();
+    json.setSerializeEmptys(false);
+    json.beginObject();
 
     List<String> additionalFields = request.paramAsStrings(WebService.Param.FIELDS);
-    writePluginInfoList(jsonWriter, pluginInfoList, additionalFields == null ? Collections.emptyList() : additionalFields, pluginDtosByKey);
+    Map<String, Plugin> updateCenterPlugins = (additionalFields == null || additionalFields.isEmpty()) ? emptyMap() : compatiblePluginsByKey(updateCenterMatrixFactory);
 
-    jsonWriter.endObject();
-    jsonWriter.close();
-  }
-
-  private SortedSet<PluginInfo> searchPluginInfoList() {
-    return copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, pluginRepository.getPluginInfos());
+    json.name(ARRAY_PLUGINS);
+    json.beginArray();
+    for (InstalledPlugin installedPlugin : copyOf(NAME_KEY_COMPARATOR, installedPlugins)) {
+      PluginDto pluginDto = dtosByKey.get(installedPlugin.getPluginInfo().getKey());
+      Objects.requireNonNull(pluginDto, () -> format("Plugin %s is installed but not in DB", installedPlugin.getPluginInfo().getKey()));
+      Plugin updateCenterPlugin = updateCenterPlugins.get(installedPlugin.getPluginInfo().getKey());
+      PluginWSCommons.writePluginInfo(json, installedPlugin.getPluginInfo(), categoryOrNull(updateCenterPlugin), pluginDto, installedPlugin);
+    }
+    json.endArray();
+    json.endObject();
+    json.close();
   }
 
-  private void writePluginInfoList(JsonWriter jsonWriter, Collection<PluginInfo> pluginInfoList, List<String> additionalFields, Map<String, PluginDto> pluginDtos) {
-    Map<String, Plugin> compatiblesPluginsFromUpdateCenter = additionalFields.isEmpty()
-      ? Collections.emptyMap()
-      : compatiblePluginsByKey(updateCenterMatrixFactory);
-    pluginWSCommons.writePluginInfoList(jsonWriter, pluginInfoList, compatiblesPluginsFromUpdateCenter, ARRAY_PLUGINS, pluginDtos, compression.getPlugins());
+  private SortedSet<InstalledPlugin> loadInstalledPlugins() {
+    return copyOf(NAME_KEY_COMPARATOR, pluginFileSystem.getInstalledFiles());
   }
 }
index 0d51853dbf680f9e351603b3745e0ded1fd7e98f..1a9d17bb39ff57da6cb27cfb7cf83dd8a99e290a 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.server.plugins.ws;
 import com.google.common.base.Function;
 import com.google.common.base.Predicate;
 import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSortedSet;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Map;
@@ -42,6 +43,8 @@ import org.sonar.updatecenter.common.Plugin;
 import static com.google.common.collect.FluentIterable.from;
 import static com.google.common.collect.ImmutableSet.copyOf;
 import static com.google.common.io.Resources.getResource;
+import static org.sonar.server.plugins.ws.PluginWSCommons.NAME_KEY_PLUGIN_METADATA_COMPARATOR;
+import static org.sonar.server.plugins.ws.PluginWSCommons.categoryOrNull;
 import static org.sonar.server.plugins.ws.PluginWSCommons.compatiblePluginsByKey;
 
 /**
@@ -56,18 +59,15 @@ public class PendingAction implements PluginsWsAction {
   private final UserSession userSession;
   private final PluginDownloader pluginDownloader;
   private final ServerPluginRepository installer;
-  private final PluginWSCommons pluginWSCommons;
   private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
   private final PluginUninstaller pluginUninstaller;
 
   public PendingAction(UserSession userSession, PluginDownloader pluginDownloader,
-    ServerPluginRepository installer, PluginUninstaller pluginUninstaller,
-    PluginWSCommons pluginWSCommons, UpdateCenterMatrixFactory updateCenterMatrixFactory) {
+    ServerPluginRepository installer, PluginUninstaller pluginUninstaller, UpdateCenterMatrixFactory updateCenterMatrixFactory) {
     this.userSession = userSession;
     this.pluginDownloader = pluginDownloader;
     this.installer = installer;
     this.pluginUninstaller = pluginUninstaller;
-    this.pluginWSCommons = pluginWSCommons;
     this.updateCenterMatrixFactory = updateCenterMatrixFactory;
   }
 
@@ -111,9 +111,19 @@ public class PendingAction implements PluginsWsAction {
       }
     }
 
-    pluginWSCommons.writePluginInfoList(json, newPlugins, compatiblePluginsByKey, ARRAY_INSTALLING);
-    pluginWSCommons.writePluginInfoList(json, updatedPlugins, compatiblePluginsByKey, ARRAY_UPDATING);
-    pluginWSCommons.writePluginInfoList(json, uninstalledPlugins, compatiblePluginsByKey, ARRAY_REMOVING);
+    writePlugin(json, ARRAY_INSTALLING, newPlugins, compatiblePluginsByKey);
+    writePlugin(json, ARRAY_UPDATING, updatedPlugins, compatiblePluginsByKey);
+    writePlugin(json, ARRAY_REMOVING, uninstalledPlugins, compatiblePluginsByKey);
+  }
+
+  private static void writePlugin(JsonWriter json, String propertyName, Collection<PluginInfo> plugins, Map<String, Plugin> compatiblePluginsByKey) {
+    json.name(propertyName);
+    json.beginArray();
+    for (PluginInfo pluginInfo : ImmutableSortedSet.copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) {
+      Plugin plugin = compatiblePluginsByKey.get(pluginInfo.getKey());
+      PluginWSCommons.writePluginInfo(json, pluginInfo, categoryOrNull(plugin), null, null);
+    }
+    json.endArray();
   }
 
   private enum PluginInfoToKey implements Function<PluginInfo, String> {
index 73787be6e894f2687b4da31cd74f5c0ec691e3dd..1fd5ab02a4ee8c689f1d1db6f26d8d6c4bddfa24 100644 (file)
@@ -22,21 +22,19 @@ package org.sonar.server.plugins.ws;
 import com.google.common.annotations.VisibleForTesting;
 import com.google.common.base.Function;
 import com.google.common.base.Optional;
-import com.google.common.base.Preconditions;
 import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Maps;
 import com.google.common.collect.Ordering;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.List;
-import java.util.Map;
 import javax.annotation.CheckForNull;
 import javax.annotation.Nonnull;
 import javax.annotation.Nullable;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.RemotePluginFile;
 import org.sonar.db.plugin.PluginDto;
+import org.sonar.server.plugins.InstalledPlugin;
 import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.server.plugins.edition.EditionBundledPlugins;
 import org.sonar.updatecenter.common.Artifact;
@@ -46,7 +44,6 @@ import org.sonar.updatecenter.common.Release;
 import org.sonar.updatecenter.common.UpdateCenter;
 import org.sonar.updatecenter.common.Version;
 
-import static com.google.common.collect.ImmutableSortedSet.copyOf;
 import static com.google.common.collect.Iterables.filter;
 import static com.google.common.collect.Iterables.transform;
 import static java.lang.String.CASE_INSENSITIVE_ORDER;
@@ -57,9 +54,7 @@ public class PluginWSCommons {
   private static final String PROPERTY_KEY = "key";
   private static final String PROPERTY_NAME = "name";
   private static final String PROPERTY_HASH = "hash";
-  private static final String PROPERTY_COMPRESSED_HASH = "compressedHash";
   private static final String PROPERTY_FILENAME = "filename";
-  private static final String PROPERTY_COMPRESSED_FILENAME = "compressedFilename";
   private static final String PROPERTY_SONARLINT_SUPPORTED = "sonarLintSupported";
   private static final String PROPERTY_DESCRIPTION = "description";
   private static final String PROPERTY_LICENSE = "license";
@@ -86,6 +81,9 @@ public class PluginWSCommons {
   public static final Ordering<PluginInfo> NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural()
     .onResultOf(PluginInfo::getName)
     .compound(Ordering.natural().onResultOf(PluginInfo::getKey));
+  public static final Comparator<InstalledPlugin> NAME_KEY_COMPARATOR = Comparator
+    .comparing((java.util.function.Function<InstalledPlugin, String>) installedPluginFile -> installedPluginFile.getPluginInfo().getName())
+    .thenComparing(f -> f.getPluginInfo().getKey());
   public static final Comparator<Plugin> NAME_KEY_PLUGIN_ORDERING = Ordering.from(CASE_INSENSITIVE_ORDER)
     .onResultOf(PluginToName.INSTANCE)
     .compound(
@@ -93,22 +91,10 @@ public class PluginWSCommons {
   public static final Comparator<PluginUpdate> NAME_KEY_PLUGIN_UPDATE_ORDERING = Ordering.from(NAME_KEY_PLUGIN_ORDERING)
     .onResultOf(PluginUpdateToPlugin.INSTANCE);
 
-  void writePluginInfo(JsonWriter json, PluginInfo pluginInfo, @Nullable String category, @Nullable PluginDto pluginDto, @Nullable RemotePluginFile compressedPlugin) {
+  public static void writePluginInfo(JsonWriter json, PluginInfo pluginInfo, @Nullable String category, @Nullable PluginDto pluginDto, @Nullable InstalledPlugin installedFile) {
     json.beginObject();
-
     json.prop(PROPERTY_KEY, pluginInfo.getKey());
     json.prop(PROPERTY_NAME, pluginInfo.getName());
-    if (pluginDto != null) {
-      json.prop(PROPERTY_FILENAME, pluginInfo.getNonNullJarFile().getName());
-      json.prop(PROPERTY_SONARLINT_SUPPORTED, pluginInfo.isSonarLintSupported());
-      json.prop(PROPERTY_HASH, pluginDto.getFileHash());
-      json.prop(PROPERTY_UPDATED_AT, pluginDto.getUpdatedAt());
-    }
-    if (compressedPlugin != null) {
-      json.prop(PROPERTY_COMPRESSED_FILENAME, compressedPlugin.getFilename());
-      json.prop(PROPERTY_COMPRESSED_HASH, compressedPlugin.getHash());
-    }
-
     json.prop(PROPERTY_DESCRIPTION, pluginInfo.getDescription());
     Version version = pluginInfo.getVersion();
     if (version != null) {
@@ -123,29 +109,15 @@ public class PluginWSCommons {
     json.prop(PROPERTY_HOMEPAGE_URL, pluginInfo.getHomepageUrl());
     json.prop(PROPERTY_ISSUE_TRACKER_URL, pluginInfo.getIssueTrackerUrl());
     json.prop(PROPERTY_IMPLEMENTATION_BUILD, pluginInfo.getImplementationBuild());
-
-    json.endObject();
-  }
-
-  public void writePluginInfoList(JsonWriter json, Iterable<PluginInfo> plugins, Map<String, Plugin> compatiblePluginsByKey, String propertyName) {
-    writePluginInfoList(json, plugins, compatiblePluginsByKey, propertyName, null, null);
-  }
-
-  public void writePluginInfoList(JsonWriter json, Iterable<PluginInfo> plugins, Map<String, Plugin> compatiblePluginsByKey, String propertyName,
-    @Nullable Map<String, PluginDto> pluginDtos, @Nullable Map<String, RemotePluginFile> compressedPlugins) {
-    json.name(propertyName);
-    json.beginArray();
-    for (PluginInfo pluginInfo : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) {
-      PluginDto pluginDto = null;
-      if (pluginDtos != null) {
-        pluginDto = pluginDtos.get(pluginInfo.getKey());
-        Preconditions.checkNotNull(pluginDto, "Plugin %s is installed but not in DB", pluginInfo.getKey());
-      }
-      RemotePluginFile compressedPlugin = compressedPlugins != null ? compressedPlugins.get(pluginInfo.getKey()) : null;
-      Plugin plugin = compatiblePluginsByKey.get(pluginInfo.getKey());
-      writePluginInfo(json, pluginInfo, categoryOrNull(plugin), pluginDto, compressedPlugin);
+    if (pluginDto != null) {
+      json.prop(PROPERTY_UPDATED_AT, pluginDto.getUpdatedAt());
     }
-    json.endArray();
+    if (installedFile != null) {
+      json.prop(PROPERTY_FILENAME, installedFile.getLoadedJar().getFile().getName());
+      json.prop(PROPERTY_SONARLINT_SUPPORTED, installedFile.getPluginInfo().isSonarLintSupported());
+      json.prop(PROPERTY_HASH, installedFile.getLoadedJar().getMd5());
+    }
+    json.endObject();
   }
 
   public void writePlugin(JsonWriter jsonWriter, Plugin plugin) {
index 68b592e8731edd137d4428fda320ab3313699445..a3b7e5511677544bb7773c85aa0555ea47627335 100644 (file)
@@ -32,28 +32,33 @@ import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.api.utils.log.Profiler;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginRepository;
-import org.sonar.core.platform.RemotePlugin;
 import org.sonar.server.platform.ServerFileSystem;
+import org.sonar.server.plugins.InstalledPlugin;
+import org.sonar.server.plugins.PluginFileSystem;
 
+/**
+ * The file deploy/plugins/index.txt is required for old versions of SonarLint.
+ * They don't use the web service api/plugins/installed to get the list
+ * of installed plugins.
+ * https://jira.sonarsource.com/browse/SLCORE-146
+ */
 @ServerSide
 public final class GeneratePluginIndex implements Startable {
 
   private static final Logger LOG = Loggers.get(GeneratePluginIndex.class);
 
-  private final ServerFileSystem fileSystem;
-  private final PluginRepository repository;
+  private final ServerFileSystem serverFs;
+  private final PluginFileSystem pluginFs;
 
-  public GeneratePluginIndex(ServerFileSystem fileSystem, PluginRepository repository) {
-    this.fileSystem = fileSystem;
-    this.repository = repository;
+  public GeneratePluginIndex(ServerFileSystem serverFs, PluginFileSystem pluginFs) {
+    this.serverFs = serverFs;
+    this.pluginFs = pluginFs;
   }
 
   @Override
   public void start() {
     Profiler profiler = Profiler.create(LOG).startInfo("Generate scanner plugin index");
-    writeIndex(fileSystem.getPluginIndex());
+    writeIndex(serverFs.getPluginIndex());
     profiler.stopDebug();
   }
 
@@ -62,12 +67,12 @@ public final class GeneratePluginIndex implements Startable {
     // Nothing to do
   }
 
-  void writeIndex(File indexFile) {
+  private void writeIndex(File indexFile) {
     try {
       FileUtils.forceMkdir(indexFile.getParentFile());
       try (Writer writer = new OutputStreamWriter(new FileOutputStream(indexFile), StandardCharsets.UTF_8)) {
-        for (PluginInfo pluginInfo : repository.getPluginInfos()) {
-          writer.append(RemotePlugin.create(pluginInfo).marshal());
+        for (InstalledPlugin plugin : pluginFs.getInstalledFiles()) {
+          writer.append(toRow(plugin));
           writer.append(CharUtils.LF);
         }
         writer.flush();
@@ -76,4 +81,17 @@ public final class GeneratePluginIndex implements Startable {
       throw new IllegalStateException("Unable to generate plugin index at " + indexFile, e);
     }
   }
+
+  private static String toRow(InstalledPlugin file) {
+    StringBuilder sb = new StringBuilder();
+    sb.append(file.getPluginInfo().getKey())
+      .append(",")
+      .append(file.getPluginInfo().isSonarLintSupported())
+      .append(",")
+      .append(file.getLoadedJar().getFile().getName())
+      .append("|")
+      .append(file.getLoadedJar().getMd5());
+    return sb.toString();
+  }
+
 }
index 1f77967aaf62d086ec5c692c2712b3900898a4ad..87e5cb13436bd9abc324b0efde80afebe242f212 100644 (file)
  */
 package org.sonar.server.startup;
 
-import java.util.Collection;
 import java.util.Map;
 import java.util.stream.Collectors;
 import org.sonar.api.Startable;
+import org.sonar.api.server.ServerSide;
 import org.sonar.api.utils.System2;
 import org.sonar.api.utils.log.Logger;
 import org.sonar.api.utils.log.Loggers;
 import org.sonar.api.utils.log.Profiler;
 import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.RemotePlugin;
 import org.sonar.core.util.UuidFactory;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbSession;
 import org.sonar.db.plugin.PluginDto;
-import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.InstalledPlugin;
+import org.sonar.server.plugins.PluginFileSystem;
 
 import static java.util.function.Function.identity;
 
 /**
  * Take care to update the 'plugins' table at startup.
  */
+@ServerSide
 public class RegisterPlugins implements Startable {
 
   private static final Logger LOG = Loggers.get(RegisterPlugins.class);
 
-  private final ServerPluginRepository repository;
+  private final PluginFileSystem pluginFileSystem;
   private final DbClient dbClient;
   private final UuidFactory uuidFactory;
   private final System2 system;
 
-  public RegisterPlugins(ServerPluginRepository repository, DbClient dbClient, UuidFactory uuidFactory, System2 system) {
-    this.repository = repository;
+  public RegisterPlugins(PluginFileSystem pluginFileSystem, DbClient dbClient, UuidFactory uuidFactory, System2 system) {
+    this.pluginFileSystem = pluginFileSystem;
     this.dbClient = dbClient;
     this.uuidFactory = uuidFactory;
     this.system = system;
@@ -59,7 +60,7 @@ public class RegisterPlugins implements Startable {
   @Override
   public void start() {
     Profiler profiler = Profiler.create(LOG).startInfo("Register plugins");
-    updateDB(repository.getPluginInfos());
+    updateDB();
     profiler.stopDebug();
   }
 
@@ -68,30 +69,29 @@ public class RegisterPlugins implements Startable {
     // Nothing to do
   }
 
-  private void updateDB(Collection<PluginInfo> pluginInfos) {
+  private void updateDB() {
     long now = system.now();
     try (DbSession dbSession = dbClient.openSession(false)) {
       Map<String, PluginDto> allPreviousPluginsByKey = dbClient.pluginDao().selectAll(dbSession).stream()
         .collect(Collectors.toMap(PluginDto::getKee, identity()));
-      for (PluginInfo pluginInfo : pluginInfos) {
-        RemotePlugin remotePlugin = RemotePlugin.create(pluginInfo);
-        String newJarMd5 = remotePlugin.file().getHash();
-        PluginDto previousDto = allPreviousPluginsByKey.get(pluginInfo.getKey());
+      for (InstalledPlugin installed : pluginFileSystem.getInstalledFiles()) {
+        PluginInfo info = installed.getPluginInfo();
+        PluginDto previousDto = allPreviousPluginsByKey.get(info.getKey());
         if (previousDto == null) {
-          LOG.debug("Register new plugin {}", pluginInfo.getKey());
+          LOG.debug("Register new plugin {}", info.getKey());
           PluginDto pluginDto = new PluginDto()
             .setUuid(uuidFactory.create())
-            .setKee(pluginInfo.getKey())
-            .setBasePluginKey(pluginInfo.getBasePlugin())
-            .setFileHash(newJarMd5)
+            .setKee(info.getKey())
+            .setBasePluginKey(info.getBasePlugin())
+            .setFileHash(installed.getLoadedJar().getMd5())
             .setCreatedAt(now)
             .setUpdatedAt(now);
           dbClient.pluginDao().insert(dbSession, pluginDto);
-        } else if (!previousDto.getFileHash().equals(newJarMd5)) {
-          LOG.debug("Update plugin {}", pluginInfo.getKey());
+        } else if (!previousDto.getFileHash().equals(installed.getLoadedJar().getMd5())) {
+          LOG.debug("Update plugin {}", info.getKey());
           previousDto
-            .setBasePluginKey(pluginInfo.getBasePlugin())
-            .setFileHash(newJarMd5)
+            .setBasePluginKey(info.getBasePlugin())
+            .setFileHash(installed.getLoadedJar().getMd5())
             .setUpdatedAt(now);
           dbClient.pluginDao().update(dbSession, previousDto);
         }
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginCompressionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginCompressionTest.java
deleted file mode 100644 (file)
index 99e0d81..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2018 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.IOException;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import org.junit.Before;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.config.internal.MapSettings;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-public class PluginCompressionTest {
-  @Rule
-  public TemporaryFolder temp = new TemporaryFolder();
-
-  private MapSettings settings = new MapSettings();
-  private Path targetJarPath;
-  private Path targetFolder;
-  private Path sourceFolder;
-
-  private PluginCompression underTest;
-
-  @Before
-  public void setUp() throws IOException {
-    sourceFolder = temp.newFolder("source").toPath();
-    targetFolder = temp.newFolder("target").toPath();
-    targetJarPath = targetFolder.resolve("test.jar");
-    Files.createFile(targetJarPath);
-  }
-
-  @Test
-  public void disable_if_proparty_not_set() throws IOException {
-    underTest = new PluginCompression(settings.asConfig());
-    underTest.compressJar("key", sourceFolder, targetJarPath);
-
-    assertThat(Files.list(targetFolder)).containsOnly(targetJarPath);
-    assertThat(underTest.getPlugins()).isEmpty();
-  }
-
-  @Test
-  public void should_compress_plugin() throws IOException {
-    settings.setProperty(PluginCompression.PROPERTY_PLUGIN_COMPRESSION_ENABLE, true);
-    underTest = new PluginCompression(settings.asConfig());
-    underTest.compressJar("key", targetFolder, targetJarPath);
-
-    assertThat(Files.list(targetFolder)).containsOnly(targetJarPath, targetFolder.resolve("test.pack.gz"));
-    assertThat(underTest.getPlugins()).hasSize(1);
-    assertThat(underTest.getPlugins().get("key").getFilename()).isEqualTo("test.pack.gz");
-  }
-
-  @Test
-  public void should_use_deployed_packed_file() throws IOException {
-    Path packedPath = sourceFolder.resolve("test.pack.gz");
-    Files.write(packedPath, new byte[] {1, 2, 3});
-
-    settings.setProperty(PluginCompression.PROPERTY_PLUGIN_COMPRESSION_ENABLE, true);
-    underTest = new PluginCompression(settings.asConfig());
-    underTest.compressJar("key", sourceFolder, targetJarPath);
-
-    assertThat(Files.list(targetFolder)).containsOnly(targetJarPath, targetFolder.resolve("test.pack.gz"));
-    assertThat(underTest.getPlugins()).hasSize(1);
-    assertThat(underTest.getPlugins().get("key").getFilename()).isEqualTo("test.pack.gz");
-
-    // check that the file was copied, not generated
-    assertThat(targetFolder.resolve("test.pack.gz")).hasSameContentAs(packedPath);
-  }
-
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginFileSystemTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginFileSystemTest.java
new file mode 100644 (file)
index 0000000..0ac8757
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2018 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.nio.file.Files;
+import java.nio.file.Path;
+import org.apache.commons.io.FileUtils;
+import org.apache.commons.lang.RandomStringUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.config.internal.MapSettings;
+import org.sonar.core.platform.PluginInfo;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.plugins.PluginFileSystem.PROPERTY_PLUGIN_COMPRESSION_ENABLE;
+
+public class PluginFileSystemTest {
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  private MapSettings settings = new MapSettings();
+  private Path targetJarPath;
+  private Path targetFolder;
+  private Path sourceFolder;
+
+  @Before
+  public void setUp() throws IOException {
+    sourceFolder = temp.newFolder("source").toPath();
+    targetFolder = temp.newFolder("target").toPath();
+    targetJarPath = targetFolder.resolve("test.jar");
+    Files.createFile(targetJarPath);
+  }
+
+  @Test
+  public void add_plugin_to_list_of_installed_plugins() throws IOException {
+    File jar = touch(temp.newFolder(), "sonar-foo-plugin.jar");
+    PluginInfo info = new PluginInfo("foo");
+
+    PluginFileSystem underTest = new PluginFileSystem(settings.asConfig());
+    underTest.addInstalledPlugin(info, jar);
+
+    assertThat(underTest.getInstalledFiles()).hasSize(1);
+    InstalledPlugin installedPlugin = underTest.getInstalledPlugin("foo").get();
+    assertThat(installedPlugin.getCompressedJar()).isNull();
+    assertThat(installedPlugin.getLoadedJar().getFile().toPath()).isEqualTo(jar.toPath());
+    assertThat(installedPlugin.getPluginInfo()).isSameAs(info);
+  }
+
+  @Test
+  public void compress_jar_if_compression_enabled() throws IOException {
+    File jar = touch(temp.newFolder(), "sonar-foo-plugin.jar");
+    PluginInfo info = new PluginInfo("foo").setJarFile(jar);
+    // the JAR is copied somewhere else in order to be loaded by classloaders
+    File loadedJar = touch(temp.newFolder(), "sonar-foo-plugin.jar");
+
+    settings.setProperty(PROPERTY_PLUGIN_COMPRESSION_ENABLE, true);
+    PluginFileSystem underTest = new PluginFileSystem(settings.asConfig());
+    underTest.addInstalledPlugin(info, loadedJar);
+
+    assertThat(underTest.getInstalledFiles()).hasSize(1);
+
+    InstalledPlugin installedPlugin = underTest.getInstalledPlugin("foo").get();
+    assertThat(installedPlugin.getPluginInfo()).isSameAs(info);
+    assertThat(installedPlugin.getLoadedJar().getFile().toPath()).isEqualTo(loadedJar.toPath());
+    assertThat(installedPlugin.getCompressedJar().getFile())
+      .exists()
+      .isFile()
+      .hasName("sonar-foo-plugin.pack.gz")
+      .hasParent(loadedJar.getParentFile());
+  }
+
+  @Test
+  public void copy_and_use_existing_packed_jar_if_compression_enabled() throws IOException {
+    File jar = touch(temp.newFolder(), "sonar-foo-plugin.jar");
+    File packedJar = touch(jar.getParentFile(), "sonar-foo-plugin.pack.gz");
+    PluginInfo info = new PluginInfo("foo").setJarFile(jar);
+    // the JAR is copied somewhere else in order to be loaded by classloaders
+    File loadedJar = touch(temp.newFolder(), "sonar-foo-plugin.jar");
+
+    settings.setProperty(PROPERTY_PLUGIN_COMPRESSION_ENABLE, true);
+    PluginFileSystem underTest = new PluginFileSystem(settings.asConfig());
+    underTest.addInstalledPlugin(info, loadedJar);
+
+    assertThat(underTest.getInstalledFiles()).hasSize(1);
+
+    InstalledPlugin installedPlugin = underTest.getInstalledPlugin("foo").get();
+    assertThat(installedPlugin.getPluginInfo()).isSameAs(info);
+    assertThat(installedPlugin.getLoadedJar().getFile().toPath()).isEqualTo(loadedJar.toPath());
+    assertThat(installedPlugin.getCompressedJar().getFile())
+      .exists()
+      .isFile()
+      .hasName(packedJar.getName())
+      .hasParent(loadedJar.getParentFile())
+      .hasSameContentAs(packedJar);
+  }
+
+  private static File touch(File dir, String filename) throws IOException {
+    File file = new File(dir, filename);
+    FileUtils.write(file, RandomStringUtils.random(10));
+    return file;
+  }
+
+  //
+  // @Test
+  // public void should_use_deployed_packed_file() throws IOException {
+  // Path packedPath = sourceFolder.resolve("test.pack.gz");
+  // Files.write(packedPath, new byte[] {1, 2, 3});
+  //
+  // settings.setProperty(PROPERTY_PLUGIN_COMPRESSION_ENABLE, true);
+  // underTest = new PluginFileSystem(settings.asConfig());
+  // underTest.compressJar("key", sourceFolder, targetJarPath);
+  //
+  // assertThat(Files.list(targetFolder)).containsOnly(targetJarPath, targetFolder.resolve("test.pack.gz"));
+  // assertThat(underTest.getPlugins()).hasSize(1);
+  // assertThat(underTest.getPlugins().get("key").getFilename()).isEqualTo("test.pack.gz");
+  //
+  // // check that the file was copied, not generated
+  // assertThat(targetFolder.resolve("test.pack.gz")).hasSameContentAs(packedPath);
+  // }
+
+}
index 92ab9ff73fd6c8c2994942d912487554a3218732..71927a9c5066490584c19fcb0b163e969dcdc325 100644 (file)
@@ -37,16 +37,16 @@ public class ServerPluginJarExploderTest {
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
 
-  ServerFileSystem fs = mock(ServerFileSystem.class);
-  PluginCompression pluginCompression = mock(PluginCompression.class);
-  ServerPluginJarExploder underTest = new ServerPluginJarExploder(fs, pluginCompression);
+  private ServerFileSystem fs = mock(ServerFileSystem.class);
+  private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class);
+  private ServerPluginJarExploder underTest = new ServerPluginJarExploder(fs, pluginFileSystem);
 
   @Test
   public void copy_all_classloader_files_to_dedicated_directory() throws Exception {
     File deployDir = temp.newFolder();
     when(fs.getDeployedPluginsDir()).thenReturn(deployDir);
-    File jar = TestProjectUtils.jarOf("test-libs-plugin");
-    PluginInfo info = PluginInfo.create(jar);
+    File sourceJar = TestProjectUtils.jarOf("test-libs-plugin");
+    PluginInfo info = PluginInfo.create(sourceJar);
 
     ExplodedPlugin exploded = underTest.explode(info);
 
@@ -61,6 +61,7 @@ public class ServerPluginJarExploderTest {
       assertThat(lib).exists().isFile();
       assertThat(lib.getCanonicalPath()).startsWith(pluginDeployDir.getCanonicalPath());
     }
-    verify(pluginCompression).compressJar(info.getKey(), jar.toPath().getParent(), exploded.getMain().toPath());
+    File targetJar = new File(fs.getDeployedPluginsDir(), "testlibs/test-libs-plugin-0.1-SNAPSHOT.jar");
+    verify(pluginFileSystem).addInstalledPlugin(info, targetJar);
   }
 }
index 9a9adfd1fad0d87841ea2e6db822312bb83129d5..533e8e0bea9d8cfedb116a77141871151de8cafe 100644 (file)
 package org.sonar.server.plugins.ws;
 
 import com.google.common.base.Optional;
+import com.hazelcast.com.eclipsesource.json.Json;
+import com.hazelcast.com.eclipsesource.json.JsonObject;
 import com.tngtech.java.junit.dataprovider.DataProvider;
 import com.tngtech.java.junit.dataprovider.DataProviderRunner;
 import com.tngtech.java.junit.dataprovider.UseDataProvider;
 import java.io.File;
-import java.util.Arrays;
-import java.util.Collections;
+import java.io.IOException;
 import java.util.Random;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
 import org.junit.runner.RunWith;
+import org.sonar.api.server.ws.WebService;
 import org.sonar.api.server.ws.WebService.Action;
-import org.sonar.api.server.ws.WebService.Param;
 import org.sonar.api.utils.System2;
 import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.RemotePluginFile;
 import org.sonar.db.DbTester;
-import org.sonar.server.plugins.PluginCompression;
-import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.InstalledPlugin;
+import org.sonar.server.plugins.InstalledPlugin.FileAndMd5;
+import org.sonar.server.plugins.PluginFileSystem;
 import org.sonar.server.plugins.UpdateCenterMatrixFactory;
 import org.sonar.server.ws.WsActionTester;
 import org.sonar.updatecenter.common.Plugin;
 import org.sonar.updatecenter.common.UpdateCenter;
 import org.sonar.updatecenter.common.Version;
 
-import static com.google.common.collect.ImmutableList.of;
+import static java.util.Arrays.asList;
 import static java.util.Collections.singletonList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
@@ -62,14 +64,14 @@ public class InstalledActionTest {
 
   @Rule
   public ExpectedException expectedException = ExpectedException.none();
-
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
   @Rule
   public DbTester db = DbTester.create(System2.INSTANCE);
 
-  private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
   private UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class, RETURNS_DEEP_STUBS);
-  private PluginCompression pluginCompression = mock(PluginCompression.class);
-  private InstalledAction underTest = new InstalledAction(pluginRepository, pluginCompression, new PluginWSCommons(), updateCenterMatrixFactory, db.getDbClient());
+  private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class);
+  private InstalledAction underTest = new InstalledAction(pluginFileSystem, updateCenterMatrixFactory, db.getDbClient());
   private WsActionTester tester = new WsActionTester(underTest);
 
   @Test
@@ -98,39 +100,62 @@ public class InstalledActionTest {
   }
 
   @Test
-  public void empty_fields_are_not_serialized_to_json() {
-    when(pluginRepository.getPluginInfos()).thenReturn(
-      of(new PluginInfo("").setName("").setJarFile(new File(""))));
+  public void empty_fields_are_not_serialized_to_json() throws IOException {
+    when(pluginFileSystem.getInstalledFiles()).thenReturn(
+      singletonList(newInstalledPlugin(new PluginInfo("foo")
+        .setName("")
+        .setDescription("")
+        .setLicense("")
+        .setOrganizationName("")
+        .setOrganizationUrl("")
+        .setImplementationBuild("")
+        .setHomepageUrl("")
+        .setIssueTrackerUrl(""))));
     db.pluginDbTester().insertPlugin(
-      p -> p.setKee(""),
-      p -> p.setFileHash("abcdA"),
-      p -> p.setUpdatedAt(111111L));
+      p -> p.setKee("foo"),
+      p -> p.setUpdatedAt(100L));
 
     String response = tester.newRequest().execute().getInput();
+    JsonObject json = Json.parse(response).asObject().get("plugins").asArray().get(0).asObject();
+    assertThat(json.get("key")).isNotNull();
+    assertThat(json.get("name")).isNull();
+    assertThat(json.get("description")).isNull();
+    assertThat(json.get("license")).isNull();
+    assertThat(json.get("organizationName")).isNull();
+    assertThat(json.get("organizationUrl")).isNull();
+    assertThat(json.get("homepageUrl")).isNull();
+    assertThat(json.get("issueTrackerUrl")).isNull();
 
-    assertThat(response).doesNotContain("name").doesNotContain("key");
+  }
+
+  private InstalledPlugin newInstalledPlugin(PluginInfo plugin) throws IOException {
+    FileAndMd5 jar = new FileAndMd5(temp.newFile());
+    return new InstalledPlugin(plugin.setJarFile(jar.getFile()), jar, null);
+  }
+
+  private InstalledPlugin newInstalledPluginWithCompression(PluginInfo plugin) throws IOException {
+    FileAndMd5 jar = new FileAndMd5(temp.newFile());
+    FileAndMd5 compressedJar = new FileAndMd5(temp.newFile());
+    return new InstalledPlugin(plugin.setJarFile(jar.getFile()), jar, compressedJar);
   }
 
   @Test
-  public void verify_properties_displayed_in_json_per_plugin() throws Exception {
-    String jarFilename = getClass().getSimpleName() + "/" + "some.jar";
-    when(pluginRepository.getPluginInfos()).thenReturn(of(
-      new PluginInfo("plugKey")
-        .setName("plugName")
-        .setDescription("desc_it")
-        .setVersion(Version.create("1.0"))
-        .setLicense("license_hey")
-        .setOrganizationName("org_name")
-        .setOrganizationUrl("org_url")
-        .setHomepageUrl("homepage_url")
-        .setIssueTrackerUrl("issueTracker_url")
-        .setImplementationBuild("sou_rev_sha1")
-        .setSonarLintSupported(true)
-        .setJarFile(new File(getClass().getResource(jarFilename).toURI()))));
+  public void return_default_fields() throws Exception {
+    InstalledPlugin plugin = newInstalledPlugin(new PluginInfo("foo")
+      .setName("plugName")
+      .setDescription("desc_it")
+      .setVersion(Version.create("1.0"))
+      .setLicense("license_hey")
+      .setOrganizationName("org_name")
+      .setOrganizationUrl("org_url")
+      .setHomepageUrl("homepage_url")
+      .setIssueTrackerUrl("issueTracker_url")
+      .setImplementationBuild("sou_rev_sha1")
+      .setSonarLintSupported(true));
+    when(pluginFileSystem.getInstalledFiles()).thenReturn(singletonList(plugin));
     db.pluginDbTester().insertPlugin(
-      p -> p.setKee("plugKey"),
-      p -> p.setFileHash("abcdplugKey"),
-      p -> p.setUpdatedAt(111111L));
+      p -> p.setKee(plugin.getPluginInfo().getKey()),
+      p -> p.setUpdatedAt(100L));
 
     String response = tester.newRequest().execute().getInput();
 
@@ -140,7 +165,7 @@ public class InstalledActionTest {
         "  \"plugins\":" +
         "  [" +
         "    {" +
-        "      \"key\": \"plugKey\"," +
+        "      \"key\": \"foo\"," +
         "      \"name\": \"plugName\"," +
         "      \"description\": \"desc_it\"," +
         "      \"version\": \"1.0\"," +
@@ -152,37 +177,32 @@ public class InstalledActionTest {
         "      \"issueTrackerUrl\": \"issueTracker_url\"," +
         "      \"implementationBuild\": \"sou_rev_sha1\"," +
         "      \"sonarLintSupported\": true," +
-        "      \"filename\": \"some.jar\"," +
-        "      \"hash\": \"abcdplugKey\"," +
-        "      \"updatedAt\": 111111" +
+        "      \"filename\": \"" + plugin.getLoadedJar().getFile().getName() + "\"," +
+        "      \"hash\": \"" + plugin.getLoadedJar().getMd5() + "\"," +
+        "      \"updatedAt\": 100" +
         "    }" +
         "  ]" +
         "}");
   }
 
   @Test
-  public void add_compressed_plugin_info() throws Exception {
-    RemotePluginFile compressedInfo = new RemotePluginFile("compressed.pack.gz", "hash");
-    when(pluginCompression.getPlugins()).thenReturn(Collections.singletonMap("plugKey", compressedInfo));
+  public void return_compression_fields_if_available() throws Exception {
+    InstalledPlugin plugin = newInstalledPluginWithCompression(new PluginInfo("foo")
+      .setName("plugName")
+      .setDescription("desc_it")
+      .setVersion(Version.create("1.0"))
+      .setLicense("license_hey")
+      .setOrganizationName("org_name")
+      .setOrganizationUrl("org_url")
+      .setHomepageUrl("homepage_url")
+      .setIssueTrackerUrl("issueTracker_url")
+      .setImplementationBuild("sou_rev_sha1")
+      .setSonarLintSupported(true));
+    when(pluginFileSystem.getInstalledFiles()).thenReturn(singletonList(plugin));
 
-    String jarFilename = getClass().getSimpleName() + "/" + "some.jar";
-    when(pluginRepository.getPluginInfos()).thenReturn(of(
-      new PluginInfo("plugKey")
-        .setName("plugName")
-        .setDescription("desc_it")
-        .setVersion(Version.create("1.0"))
-        .setLicense("license_hey")
-        .setOrganizationName("org_name")
-        .setOrganizationUrl("org_url")
-        .setHomepageUrl("homepage_url")
-        .setIssueTrackerUrl("issueTracker_url")
-        .setImplementationBuild("sou_rev_sha1")
-        .setSonarLintSupported(true)
-        .setJarFile(new File(getClass().getResource(jarFilename).toURI()))));
     db.pluginDbTester().insertPlugin(
-      p -> p.setKee("plugKey"),
-      p -> p.setFileHash("abcdplugKey"),
-      p -> p.setUpdatedAt(111111L));
+      p -> p.setKee(plugin.getPluginInfo().getKey()),
+      p -> p.setUpdatedAt(100L));
 
     String response = tester.newRequest().execute().getInput();
 
@@ -192,13 +212,11 @@ public class InstalledActionTest {
         "  \"plugins\":" +
         "  [" +
         "    {" +
-        "      \"key\": \"plugKey\"," +
+        "      \"key\": \"foo\"," +
         "      \"name\": \"plugName\"," +
         "      \"description\": \"desc_it\"," +
         "      \"version\": \"1.0\"," +
         "      \"license\": \"license_hey\"," +
-        "      \"compressedFilename\": \"compressed.pack.gz\"," +
-        "      \"compressedHash\": \"hash\"," +
         "      \"organizationName\": \"org_name\"," +
         "      \"organizationUrl\": \"org_url\",\n" +
         "      \"editionBundled\": false," +
@@ -206,9 +224,9 @@ public class InstalledActionTest {
         "      \"issueTrackerUrl\": \"issueTracker_url\"," +
         "      \"implementationBuild\": \"sou_rev_sha1\"," +
         "      \"sonarLintSupported\": true," +
-        "      \"filename\": \"some.jar\"," +
-        "      \"hash\": \"abcdplugKey\"," +
-        "      \"updatedAt\": 111111" +
+        "      \"filename\": \"" + plugin.getLoadedJar().getFile().getName() + "\"," +
+        "      \"hash\": \"" + plugin.getLoadedJar().getMd5() + "\"," +
+        "      \"updatedAt\": 100" +
         "    }" +
         "  ]" +
         "}");
@@ -217,8 +235,9 @@ public class InstalledActionTest {
   @Test
   public void category_is_returned_when_in_additional_fields() throws Exception {
     String jarFilename = getClass().getSimpleName() + "/" + "some.jar";
-    when(pluginRepository.getPluginInfos()).thenReturn(of(
-      new PluginInfo("plugKey")
+    File jar = new File(getClass().getResource(jarFilename).toURI());
+    when(pluginFileSystem.getInstalledFiles()).thenReturn(asList(
+      new InstalledPlugin(new PluginInfo("plugKey")
         .setName("plugName")
         .setDescription("desc_it")
         .setVersion(Version.create("1.0"))
@@ -228,11 +247,11 @@ public class InstalledActionTest {
         .setHomepageUrl("homepage_url")
         .setIssueTrackerUrl("issueTracker_url")
         .setImplementationBuild("sou_rev_sha1")
-        .setJarFile(new File(getClass().getResource(jarFilename).toURI()))));
+        .setJarFile(jar), new FileAndMd5(jar), null)));
     UpdateCenter updateCenter = mock(UpdateCenter.class);
     when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.of(updateCenter));
     when(updateCenter.findAllCompatiblePlugins()).thenReturn(
-      Arrays.asList(
+      asList(
         Plugin.factory("plugKey")
           .setCategory("cat_1")));
 
@@ -242,7 +261,7 @@ public class InstalledActionTest {
       p -> p.setUpdatedAt(111111L));
 
     String response = tester.newRequest()
-      .setParam(Param.FIELDS, "category")
+      .setParam(WebService.Param.FIELDS, "category")
       .execute().getInput();
 
     assertJson(response).isSimilarTo(
@@ -268,9 +287,9 @@ public class InstalledActionTest {
   }
 
   @Test
-  public void plugins_are_sorted_by_name_then_key_and_only_one_plugin_can_have_a_specific_name() {
-    when(pluginRepository.getPluginInfos()).thenReturn(
-      of(
+  public void plugins_are_sorted_by_name_then_key_and_only_one_plugin_can_have_a_specific_name() throws IOException {
+    when(pluginFileSystem.getInstalledFiles()).thenReturn(
+      asList(
         plugin("A", "name2"),
         plugin("B", "name1"),
         plugin("C", "name0"),
@@ -314,14 +333,15 @@ public class InstalledActionTest {
     Random random = new Random();
     String organization = random.nextBoolean() ? "SonarSource" : "SONARSOURCE";
     String pluginKey = "plugKey";
-    when(pluginRepository.getPluginInfos()).thenReturn(of(
-      new PluginInfo(pluginKey)
+    File jar = new File(getClass().getResource(jarFilename).toURI());
+    when(pluginFileSystem.getInstalledFiles()).thenReturn(asList(
+      new InstalledPlugin(new PluginInfo(pluginKey)
         .setName("plugName")
         .setVersion(Version.create("1.0"))
         .setLicense(license)
         .setOrganizationName(organization)
         .setImplementationBuild("sou_rev_sha1")
-        .setJarFile(new File(getClass().getResource(jarFilename).toURI()))));
+        .setJarFile(jar), new FileAndMd5(jar), null)));
     db.pluginDbTester().insertPlugin(
       p -> p.setKee(pluginKey),
       p -> p.setFileHash("abcdplugKey"),
@@ -369,9 +389,9 @@ public class InstalledActionTest {
   }
 
   @Test
-  public void only_one_plugin_can_have_a_specific_name_and_key() {
-    when(pluginRepository.getPluginInfos()).thenReturn(
-      of(
+  public void only_one_plugin_can_have_a_specific_name_and_key() throws IOException {
+    when(pluginFileSystem.getInstalledFiles()).thenReturn(
+      asList(
         plugin("A", "name2"),
         plugin("A", "name2")));
 
@@ -392,8 +412,13 @@ public class InstalledActionTest {
     assertThat(response).containsOnlyOnce("name2");
   }
 
-  private PluginInfo plugin(String key, String name) {
-    return new PluginInfo(key).setName(name).setVersion(Version.create("1.0")).setJarFile(new File("sonar-" + key + "-plugin-1.0.jar"));
+  private InstalledPlugin plugin(String key, String name) throws IOException {
+    File file = temp.newFile();
+    PluginInfo info = new PluginInfo(key)
+      .setName(name)
+      .setVersion(Version.create("1.0"));
+    info.setJarFile(file);
+    return new InstalledPlugin(info, new FileAndMd5(file), null);
   }
 
 }
index b24e624793a2787f8cdaa1c987dc240eb4f9906a..3b38875b50709461d50c28162cb3a8c865ee57a0 100644 (file)
@@ -60,7 +60,7 @@ public class PendingActionTest {
   private ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
   private UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class, RETURNS_DEEP_STUBS);
   private PendingAction underTest = new PendingAction(userSession, pluginDownloader, serverPluginRepository,
-    pluginUninstaller, new PluginWSCommons(), updateCenterMatrixFactory);
+    pluginUninstaller, updateCenterMatrixFactory);
   private Request request = mock(Request.class);
   private WsTester.TestResponse response = new WsTester.TestResponse();
 
index 5ca869682899347d37ecbe50bf8b5db2ac2a2fb7..d7881941f37a15fd64d317dc36ba3b64c829a5f1 100644 (file)
 package org.sonar.server.plugins.ws;
 
 import java.io.File;
+import java.io.IOException;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
 import org.sonar.api.utils.text.JsonWriter;
 import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.RemotePluginFile;
 import org.sonar.db.plugin.PluginDto;
+import org.sonar.server.plugins.InstalledPlugin;
+import org.sonar.server.plugins.InstalledPlugin.FileAndMd5;
 import org.sonar.server.ws.WsTester;
 import org.sonar.updatecenter.common.Plugin;
 import org.sonar.updatecenter.common.PluginUpdate;
@@ -42,13 +46,16 @@ import static org.sonar.updatecenter.common.PluginUpdate.Status.REQUIRE_SONAR_UP
 
 public class PluginWSCommonsTest {
 
-  WsTester.TestResponse response = new WsTester.TestResponse();
-  JsonWriter jsonWriter = response.newJsonWriter();
-  PluginWSCommons underTest = new PluginWSCommons();
+  @Rule
+  public TemporaryFolder temp = new TemporaryFolder();
+
+  private WsTester.TestResponse response = new WsTester.TestResponse();
+  private JsonWriter jsonWriter = response.newJsonWriter();
+  private PluginWSCommons underTest = new PluginWSCommons();
 
   @Test
   public void verify_properties_written_by_writePluginMetadata() {
-    underTest.writePluginInfo(jsonWriter, gitPluginInfo(), null, null, null);
+    PluginWSCommons.writePluginInfo(jsonWriter, gitPluginInfo(), null, null, null);
 
     jsonWriter.close();
     assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo("{" +
@@ -66,8 +73,8 @@ public class PluginWSCommonsTest {
 
   @Test
   public void verify_properties_written_by_writePluginMetadata_with_dto() {
-    PluginDto pluginDto = new PluginDto().setFileHash("abcdef123456").setUpdatedAt(123456L);
-    underTest.writePluginInfo(jsonWriter, gitPluginInfo(), null, pluginDto, null);
+    PluginDto pluginDto = new PluginDto().setUpdatedAt(123456L);
+    PluginWSCommons.writePluginInfo(jsonWriter, gitPluginInfo(), null, pluginDto, null);
 
     jsonWriter.close();
     assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo("{" +
@@ -80,42 +87,40 @@ public class PluginWSCommonsTest {
       "  \"organizationUrl\": \"http://www.sonarsource.com\"," +
       "  \"homepageUrl\": \"https://redirect.sonarsource.com/plugins/scmgit.html\"," +
       "  \"issueTrackerUrl\": \"http://jira.sonarsource.com/browse/SONARSCGIT\"," +
-      "  \"filename\": \"sonar-scm-git-plugin-1.0.jar\"," +
-      "  \"hash\": \"abcdef123456\"," +
-      "  \"sonarLintSupported\": true," +
       "  \"updatedAt\": 123456" +
       "}");
   }
 
   @Test
-  public void verify_properties_written_by_writeMetadata_with_compressed_plugin() {
-    PluginDto pluginDto = new PluginDto().setFileHash("abcdef123456").setUpdatedAt(123456L);
-    RemotePluginFile compressedPlugin = new RemotePluginFile("compressed.pack.gz", "hash");
-    underTest.writePluginInfo(jsonWriter, gitPluginInfo(), null, pluginDto, compressedPlugin);
+  public void verify_properties_written_by_writeMetadata_with_compressed_plugin() throws IOException {
+    PluginDto dto = new PluginDto().setUpdatedAt(100L);
+    FileAndMd5 loadedJar = new FileAndMd5(temp.newFile());
+    FileAndMd5 compressedJar = new FileAndMd5(temp.newFile());
+    InstalledPlugin installedFile = new InstalledPlugin(gitPluginInfo(), loadedJar, compressedJar);
+
+    PluginWSCommons.writePluginInfo(jsonWriter, gitPluginInfo(), null, dto, installedFile);
 
     jsonWriter.close();
     assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo("{" +
-      "  \"key\": \"scmgit\"," +
-      "  \"name\": \"Git\"," +
-      "  \"description\": \"Git SCM Provider.\"," +
-      "  \"version\": \"1.0\"," +
-      "  \"license\": \"GNU LGPL 3\"," +
-      "  \"organizationName\": \"SonarSource\"," +
-      "  \"compressedFilename\": \"compressed.pack.gz\"," +
-      "  \"compressedHash\": \"hash\"," +
-      "  \"organizationUrl\": \"http://www.sonarsource.com\"," +
-      "  \"homepageUrl\": \"https://redirect.sonarsource.com/plugins/scmgit.html\"," +
-      "  \"issueTrackerUrl\": \"http://jira.sonarsource.com/browse/SONARSCGIT\"," +
-      "  \"filename\": \"sonar-scm-git-plugin-1.0.jar\"," +
-      "  \"hash\": \"abcdef123456\"," +
-      "  \"sonarLintSupported\": true," +
-      "  \"updatedAt\": 123456" +
+        "  \"key\": \"scmgit\"," +
+        "  \"name\": \"Git\"," +
+        "  \"description\": \"Git SCM Provider.\"," +
+        "  \"version\": \"1.0\"," +
+        "  \"license\": \"GNU LGPL 3\"," +
+        "  \"organizationName\": \"SonarSource\"," +
+        "  \"organizationUrl\": \"http://www.sonarsource.com\"," +
+        "  \"homepageUrl\": \"https://redirect.sonarsource.com/plugins/scmgit.html\"," +
+        "  \"issueTrackerUrl\": \"http://jira.sonarsource.com/browse/SONARSCGIT\"," +
+        "  \"sonarLintSupported\": true," +
+        "  \"updatedAt\": 100," +
+        "  \"filename\": \"" + loadedJar.getFile().getName() + "\"," +
+        "  \"hash\": \"" + loadedJar.getMd5() + "\"" +
       "}");
   }
 
   @Test
   public void verify_properties_written_by_writeMetadata() {
-    underTest.writePluginInfo(jsonWriter, gitPluginInfo(), "cat_1", null, null);
+    PluginWSCommons.writePluginInfo(jsonWriter, gitPluginInfo(), "cat_1", null, null);
 
     jsonWriter.close();
     assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo("{" +
index c1e56e6a362e64704219209e6334cb92a45a3f6b..d65a0c5a419a8a77b4d211bdd0586ece39de2d62 100644 (file)
@@ -21,7 +21,6 @@ package org.sonar.server.startup;
 
 import java.io.File;
 import java.io.IOException;
-import java.util.Arrays;
 import java.util.List;
 import org.apache.commons.io.FileUtils;
 import org.junit.Before;
@@ -29,9 +28,12 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginRepository;
 import org.sonar.server.platform.ServerFileSystem;
+import org.sonar.server.plugins.InstalledPlugin;
+import org.sonar.server.plugins.InstalledPlugin.FileAndMd5;
+import org.sonar.server.plugins.PluginFileSystem;
 
+import static java.util.Arrays.asList;
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
@@ -41,30 +43,31 @@ public class GeneratePluginIndexTest {
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
 
-  private ServerFileSystem fileSystem = mock(ServerFileSystem.class);
+  private ServerFileSystem serverFileSystem = mock(ServerFileSystem.class);
+  private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class);
   private File index;
 
   @Before
   public void createIndexFile() throws IOException {
     index = temp.newFile();
-    when(fileSystem.getPluginIndex()).thenReturn(index);
+    when(serverFileSystem.getPluginIndex()).thenReturn(index);
   }
 
   @Test
   public void shouldWriteIndex() throws IOException {
-    PluginRepository repository = mock(PluginRepository.class);
-    PluginInfo sqale = newInfo("sqale");
-    PluginInfo checkstyle = newInfo("checkstyle");
-    when(repository.getPluginInfos()).thenReturn(Arrays.asList(sqale, checkstyle));
+    InstalledPlugin javaPlugin = newInstalledPlugin("java", true);
+    InstalledPlugin gitPlugin = newInstalledPlugin("scmgit", false);
+    when(pluginFileSystem.getInstalledFiles()).thenReturn(asList(javaPlugin, gitPlugin));
 
-    GeneratePluginIndex underTest = new GeneratePluginIndex(fileSystem, repository);
+    GeneratePluginIndex underTest = new GeneratePluginIndex(serverFileSystem, pluginFileSystem);
     underTest.start();
-    underTest.stop(); // For coverage
 
     List<String> lines = FileUtils.readLines(index);
-    assertThat(lines).hasSize(2);
-    assertThat(lines.get(0)).contains("sqale");
-    assertThat(lines.get(1)).contains("checkstyle");
+    assertThat(lines).containsExactly(
+      "java,true," + javaPlugin.getLoadedJar().getFile().getName() + "|" + javaPlugin.getLoadedJar().getMd5(),
+      "scmgit,false," + gitPlugin.getLoadedJar().getFile().getName() + "|" + gitPlugin.getLoadedJar().getMd5());
+
+    underTest.stop();
   }
 
   @Test(expected = IllegalStateException.class)
@@ -72,14 +75,14 @@ public class GeneratePluginIndexTest {
     File wrongParent = temp.newFile();
     wrongParent.createNewFile();
     File wrongIndex = new File(wrongParent, "index.txt");
-    when(fileSystem.getPluginIndex()).thenReturn(wrongIndex);
-
-    PluginRepository repository = mock(PluginRepository.class);
+    when(serverFileSystem.getPluginIndex()).thenReturn(wrongIndex);
 
-    new GeneratePluginIndex(fileSystem, repository).start();
+    new GeneratePluginIndex(serverFileSystem, pluginFileSystem).start();
   }
 
-  private PluginInfo newInfo(String pluginKey) throws IOException {
-    return new PluginInfo(pluginKey).setJarFile(temp.newFile(pluginKey + ".jar"));
+  private InstalledPlugin newInstalledPlugin(String key, boolean supportSonarLint) throws IOException {
+    FileAndMd5 jar = new FileAndMd5(temp.newFile());
+    PluginInfo pluginInfo = new PluginInfo(key).setJarFile(jar.getFile()).setSonarLintSupported(supportSonarLint);
+    return new InstalledPlugin(pluginInfo, jar, null);
   }
 }
index 8b257202fb0469f8a8b2bfe454cba84c2ae3369c..b8f0ac2ca28e90397630301b28c396d3feff9e5f 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.server.startup;
 import java.io.File;
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
+import javax.annotation.Nullable;
 import org.apache.commons.io.FileUtils;
 import org.junit.Before;
 import org.junit.Rule;
@@ -32,7 +33,8 @@ import org.sonar.core.platform.PluginInfo;
 import org.sonar.core.util.UuidFactory;
 import org.sonar.db.DbClient;
 import org.sonar.db.DbTester;
-import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.InstalledPlugin;
+import org.sonar.server.plugins.PluginFileSystem;
 
 import static java.util.Arrays.asList;
 import static org.mockito.Mockito.mock;
@@ -46,17 +48,13 @@ public class RegisterPluginsTest {
   @Rule
   public DbTester dbTester = DbTester.create(System2.INSTANCE);
 
-  DbClient dbClient = dbTester.getDbClient();
-
-  private ServerPluginRepository serverPluginRepository;
-  private UuidFactory uuidFactory;
-  private System2 system2;
+  private DbClient dbClient = dbTester.getDbClient();
+  private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class);
+  private UuidFactory uuidFactory = mock(UuidFactory.class);
+  private System2 system2 = mock(System2.class);
 
   @Before
-  public void prepare() {
-    serverPluginRepository = mock(ServerPluginRepository.class);
-    uuidFactory = mock(UuidFactory.class);
-    system2 = mock(System2.class);
+  public void setUp() {
     when(system2.now()).thenReturn(12345L).thenThrow(new IllegalStateException("Should be called only once"));
   }
 
@@ -71,13 +69,16 @@ public class RegisterPluginsTest {
     FileUtils.write(fakeJavaJar, "fakejava", StandardCharsets.UTF_8);
     File fakeJavaCustomJar = temp.newFile();
     FileUtils.write(fakeJavaCustomJar, "fakejavacustom", StandardCharsets.UTF_8);
-    when(serverPluginRepository.getPluginInfos()).thenReturn(asList(new PluginInfo("java").setJarFile(fakeJavaJar),
-      new PluginInfo("javacustom").setJarFile(fakeJavaCustomJar).setBasePlugin("java")));
+    when(pluginFileSystem.getInstalledFiles()).thenReturn(asList(
+      newPlugin("java", fakeJavaJar, null),
+      newPlugin("javacustom", fakeJavaCustomJar, "java")));
     when(uuidFactory.create()).thenReturn("a").thenReturn("b").thenThrow(new IllegalStateException("Should be called only twice"));
-    RegisterPlugins register = new RegisterPlugins(serverPluginRepository, dbClient, uuidFactory, system2);
+    RegisterPlugins register = new RegisterPlugins(pluginFileSystem, dbClient, uuidFactory, system2);
     register.start();
-    register.stop(); // For coverage
+
     dbTester.assertDbUnit(getClass(), "insert_new_plugins-result.xml", "plugins");
+
+    register.stop();
   }
 
   /**
@@ -89,11 +90,20 @@ public class RegisterPluginsTest {
 
     File fakeJavaCustomJar = temp.newFile();
     FileUtils.write(fakeJavaCustomJar, "fakejavacustomchanged", StandardCharsets.UTF_8);
-    when(serverPluginRepository.getPluginInfos()).thenReturn(asList(new PluginInfo("javacustom").setJarFile(fakeJavaCustomJar).setBasePlugin("java2")));
+    when(pluginFileSystem.getInstalledFiles()).thenReturn(asList(
+      newPlugin("javacustom", fakeJavaCustomJar, "java2")));
 
-    new RegisterPlugins(serverPluginRepository, dbClient, uuidFactory, system2).start();
+    new RegisterPlugins(pluginFileSystem, dbClient, uuidFactory, system2).start();
 
     dbTester.assertDbUnit(getClass(), "update_only_changed_plugins-result.xml", "plugins");
   }
 
+  private static InstalledPlugin newPlugin(String key, File file, @Nullable String basePlugin) {
+    InstalledPlugin.FileAndMd5 jar = new InstalledPlugin.FileAndMd5(file);
+    PluginInfo info = new PluginInfo(key)
+      .setBasePlugin(basePlugin)
+      .setJarFile(file);
+    return new InstalledPlugin(info, jar, null);
+  }
+
 }
index c24fda1c5239cefb980dcc2fc9d649fb09cc3be6..cdaff422d187c50f2b808db92ff3d79057ec928a 100644 (file)
@@ -229,7 +229,6 @@ public class PluginInfo implements Comparable<PluginInfo> {
     return useChildFirstClassLoader;
   }
 
-  @CheckForNull
   public boolean isSonarLintSupported() {
     return sonarLintSupported;
   }