Browse Source

SONAR-10591 refactor management of plugin FS on server

tags/7.5
Simon Brandhof 6 years ago
parent
commit
cd73d0a82c
24 changed files with 669 additions and 485 deletions
  1. 0
    5
      server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java
  2. 2
    1
      server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java
  3. 2
    8
      server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java
  4. 2
    7
      server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java
  5. 2
    2
      server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java
  6. 81
    0
      server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPlugin.java
  7. 0
    101
      server/sonar-server/src/main/java/org/sonar/server/plugins/PluginCompression.java
  8. 124
    0
      server/sonar-server/src/main/java/org/sonar/server/plugins/PluginFileSystem.java
  9. 6
    7
      server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java
  10. 1
    0
      server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
  11. 29
    30
      server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java
  12. 17
    7
      server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java
  13. 13
    41
      server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java
  14. 30
    12
      server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java
  15. 20
    20
      server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPlugins.java
  16. 0
    89
      server/sonar-server/src/test/java/org/sonar/server/plugins/PluginCompressionTest.java
  17. 142
    0
      server/sonar-server/src/test/java/org/sonar/server/plugins/PluginFileSystemTest.java
  18. 7
    6
      server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java
  19. 107
    82
      server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java
  20. 1
    1
      server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java
  21. 35
    30
      server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java
  22. 22
    19
      server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
  23. 26
    16
      server/sonar-server/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java
  24. 0
    1
      sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java

+ 0
- 5
server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginJarExploderTest.java View 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();

+ 2
- 1
server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java View 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;
}

+ 2
- 8
server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystem.java View 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();

/**

+ 2
- 7
server/sonar-server/src/main/java/org/sonar/server/platform/ServerFileSystemImpl.java View 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

+ 2
- 2
server/sonar-server/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java View 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,

+ 81
- 0
server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPlugin.java View File

@@ -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;
}
}
}

+ 0
- 101
server/sonar-server/src/main/java/org/sonar/server/plugins/PluginCompression.java View File

@@ -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();
}
}

+ 124
- 0
server/sonar-server/src/main/java/org/sonar/server/plugins/PluginFileSystem.java View File

@@ -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);
}
}

+ 6
- 7
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java View 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);

+ 1
- 0
server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java View File

@@ -105,6 +105,7 @@ public class ServerPluginRepository implements PluginRepository, Startable {

@Override
public void start() {
long begin = System.currentTimeMillis();
loadPreInstalledPlugins();
copyBundledPlugins();
moveDownloadedPlugins();

+ 29
- 30
server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java View 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());
}
}

+ 17
- 7
server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingAction.java View 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> {

+ 13
- 41
server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java View 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) {

+ 30
- 12
server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java View 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();
}

}

+ 20
- 20
server/sonar-server/src/main/java/org/sonar/server/startup/RegisterPlugins.java View File

@@ -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<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);
}

+ 0
- 89
server/sonar-server/src/test/java/org/sonar/server/plugins/PluginCompressionTest.java View File

@@ -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);
}

}

+ 142
- 0
server/sonar-server/src/test/java/org/sonar/server/plugins/PluginFileSystemTest.java View File

@@ -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);
// }

}

+ 7
- 6
server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java View 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);
}
}

+ 107
- 82
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java View File

@@ -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);
}

}

+ 1
- 1
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java View 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();


+ 35
- 30
server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java View File

@@ -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("{" +

+ 22
- 19
server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java View 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);
}
}

+ 26
- 16
server/sonar-server/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java View 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);
}

}

+ 0
- 1
sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java View File

@@ -229,7 +229,6 @@ public class PluginInfo implements Comparable<PluginInfo> {
return useChildFirstClassLoader;
}

@CheckForNull
public boolean isSonarLintSupported() {
return sonarLintSupported;
}

Loading…
Cancel
Save