From: Simon Brandhof Date: Thu, 10 May 2018 19:46:03 +0000 (+0200) Subject: SONAR-10591 refactor management of plugin FS on server X-Git-Tag: 7.5~1209 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=cd73d0a82c060fc68d2b6306ab0a6286b5717abf;p=sonarqube.git SONAR-10591 refactor management of plugin FS on server --- diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java index 9a93936177d..a5828ba8c78 100644 --- a/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java +++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java @@ -102,11 +102,6 @@ public class CePluginJarExploderTest { throw new UnsupportedOperationException(); } - @Override - public File getDeployDir() { - throw new UnsupportedOperationException(); - } - @Override public File getHomeDir() { throw new UnsupportedOperationException(); diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java index 96e44e7e4be..579e037dea9 100644 --- a/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java +++ b/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java @@ -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; } diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java index 486e955de0a..17524356cc2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java @@ -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(); /** diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java index 5ef18cbfb40..6764f85b804 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java @@ -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 diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java index f54d401197a..a67ce655e51 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java +++ b/server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java @@ -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 index 00000000000..024fac6f271 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPlugin.java @@ -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 index 9bb993e5306..00000000000 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginCompression.java +++ /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 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 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 index 00000000000..0329ab86082 --- /dev/null +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginFileSystem.java @@ -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 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 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 getInstalledPlugin(String pluginKey) { + return Optional.ofNullable(installedFiles.get(pluginKey)); + } + + public Collection getInstalledFiles() { + return installedFiles.values(); + } + + private Optional 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); + } +} diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java index 4a5942084e3..faf5c8e2f1d 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java @@ -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); diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java index 0be17a3958d..254805884d9 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java @@ -105,6 +105,7 @@ public class ServerPluginRepository implements PluginRepository, Startable { @Override public void start() { + long begin = System.currentTimeMillis(); loadPreInstalledPlugins(); copyBundledPlugins(); moveDownloadedPlugins(); diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java index f65a2f35c15..f02219567ab 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java @@ -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 pluginInfoList = searchPluginInfoList(); - Map pluginDtosByKey; + Collection installedPlugins = loadInstalledPlugins(); + Map 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 additionalFields = request.paramAsStrings(WebService.Param.FIELDS); - writePluginInfoList(jsonWriter, pluginInfoList, additionalFields == null ? Collections.emptyList() : additionalFields, pluginDtosByKey); + Map updateCenterPlugins = (additionalFields == null || additionalFields.isEmpty()) ? emptyMap() : compatiblePluginsByKey(updateCenterMatrixFactory); - jsonWriter.endObject(); - jsonWriter.close(); - } - - private SortedSet 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 pluginInfoList, List additionalFields, Map pluginDtos) { - Map compatiblesPluginsFromUpdateCenter = additionalFields.isEmpty() - ? Collections.emptyMap() - : compatiblePluginsByKey(updateCenterMatrixFactory); - pluginWSCommons.writePluginInfoList(jsonWriter, pluginInfoList, compatiblesPluginsFromUpdateCenter, ARRAY_PLUGINS, pluginDtos, compression.getPlugins()); + private SortedSet loadInstalledPlugins() { + return copyOf(NAME_KEY_COMPARATOR, pluginFileSystem.getInstalledFiles()); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java index 0d51853dbf6..1a9d17bb39f 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java @@ -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 plugins, Map 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 { diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java index 73787be6e89..1fd5ab02a4e 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java +++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java @@ -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 NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural() .onResultOf(PluginInfo::getName) .compound(Ordering.natural().onResultOf(PluginInfo::getKey)); + public static final Comparator NAME_KEY_COMPARATOR = Comparator + .comparing((java.util.function.Function) installedPluginFile -> installedPluginFile.getPluginInfo().getName()) + .thenComparing(f -> f.getPluginInfo().getKey()); public static final Comparator NAME_KEY_PLUGIN_ORDERING = Ordering.from(CASE_INSENSITIVE_ORDER) .onResultOf(PluginToName.INSTANCE) .compound( @@ -93,22 +91,10 @@ public class PluginWSCommons { public static final Comparator 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 plugins, Map compatiblePluginsByKey, String propertyName) { - writePluginInfoList(json, plugins, compatiblePluginsByKey, propertyName, null, null); - } - - public void writePluginInfoList(JsonWriter json, Iterable plugins, Map compatiblePluginsByKey, String propertyName, - @Nullable Map pluginDtos, @Nullable Map 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) { diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java b/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java index 68b592e8731..a3b7e551167 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java +++ b/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java @@ -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(); + } + } diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPlugins.java b/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPlugins.java index 1f77967aaf6..87e5cb13436 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPlugins.java +++ b/server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPlugins.java @@ -19,38 +19,39 @@ */ 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 pluginInfos) { + private void updateDB() { long now = system.now(); try (DbSession dbSession = dbClient.openSession(false)) { Map 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 index 99e0d81410f..00000000000 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginCompressionTest.java +++ /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 index 00000000000..0ac8757706e --- /dev/null +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginFileSystemTest.java @@ -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); + // } + +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java index 92ab9ff73fd..71927a9c506 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java @@ -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); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java index 9a9adfd1fad..533e8e0bea9 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java @@ -20,32 +20,34 @@ 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); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java index b24e624793a..3b38875b507 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java @@ -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(); diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java index 5ca86968289..d7881941f37 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java @@ -20,11 +20,15 @@ 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("{" + diff --git a/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java index c1e56e6a362..d65a0c5a419 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java @@ -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 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); } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java b/server/sonar-server/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java index 8b257202fb0..b8f0ac2ca28 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java @@ -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); + } + } diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java index c24fda1c523..cdaff422d18 100644 --- a/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java @@ -229,7 +229,6 @@ public class PluginInfo implements Comparable { return useChildFirstClassLoader; } - @CheckForNull public boolean isSonarLintSupported() { return sonarLintSupported; }