Browse Source

SONAR-17678 remove usage of pack200

tags/9.8.0.63668
Matteo Mara 1 year ago
parent
commit
7cbc1b9bda
17 changed files with 61 additions and 564 deletions
  1. 0
    105
      server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginCompressor.java
  2. 3
    12
      server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPlugin.java
  3. 5
    8
      server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginManager.java
  4. 0
    128
      server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginCompressorTest.java
  5. 1
    1
      server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java
  6. 28
    22
      server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginManagerTest.java
  7. 1
    1
      server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java
  8. 1
    1
      server/sonar-webserver-core/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
  9. 1
    1
      server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java
  10. 5
    15
      server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/DownloadAction.java
  11. 7
    58
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/DownloadActionTest.java
  12. 3
    59
      server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java
  13. 0
    2
      server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java
  14. 2
    2
      server/sonar-webserver/src/test/java/org/sonar/server/platform/platformlevel/PlatformLevel2Test.java
  15. 3
    35
      sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PluginFiles.java
  16. 1
    100
      sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PluginFilesTest.java
  17. 0
    14
      sonar-ws/src/main/java/org/sonarqube/ws/client/plugins/DownloadRequest.java

+ 0
- 105
server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginCompressor.java View File

@@ -1,105 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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.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.server.plugins.PluginFilesAndMd5.FileAndMd5;

@ServerSide
public class PluginCompressor {

public static final String PROPERTY_PLUGIN_COMPRESSION_ENABLE = "sonar.pluginsCompression.enable";
private static final Logger LOG = Loggers.get(PluginCompressor.class);

private final Configuration configuration;

public PluginCompressor(Configuration configuration) {
this.configuration = configuration;
}

public boolean enabled() {
return configuration.getBoolean(PROPERTY_PLUGIN_COMPRESSION_ENABLE).orElse(false);
}

/**
* @param loadedJar the JAR loaded by classloaders. It differs from {@code jar}
* which is the initial location of JAR as seen by users
*/
public PluginFilesAndMd5 compress(String key, File jar, File loadedJar) {
Optional<File> compressed = compressJar(key, jar, loadedJar);
return new PluginFilesAndMd5(new FileAndMd5(loadedJar), compressed.map(FileAndMd5::new).orElse(null));
}

private Optional<File> compressJar(String key, File jar, File loadedJar) {
if (!configuration.getBoolean(PROPERTY_PLUGIN_COMPRESSION_ENABLE).orElse(false)) {
return Optional.empty();
}

Path targetPack200 = getPack200Path(loadedJar.toPath());
Path sourcePack200Path = getPack200Path(jar.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(loadedJar.toPath(), targetPack200, key);
}
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);
}
}

+ 3
- 12
server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPlugin.java View File

@@ -19,8 +19,6 @@
*/
package org.sonar.server.plugins;

import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.Plugin;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.plugin.PluginType;
@@ -31,19 +29,17 @@ public class ServerPlugin {
private final PluginType type;
private final Plugin instance;
private final FileAndMd5 jar;
private final FileAndMd5 compressed;
private final ClassLoader classloader;

public ServerPlugin(PluginInfo pluginInfo, PluginType type, Plugin instance, FileAndMd5 jar, @Nullable FileAndMd5 compressed) {
this(pluginInfo, type, instance, jar, compressed, instance.getClass().getClassLoader());
public ServerPlugin(PluginInfo pluginInfo, PluginType type, Plugin instance, FileAndMd5 jar) {
this(pluginInfo, type, instance, jar, instance.getClass().getClassLoader());
}

public ServerPlugin(PluginInfo pluginInfo, PluginType type, Plugin instance, FileAndMd5 jar, @Nullable FileAndMd5 compressed, ClassLoader classloader) {
public ServerPlugin(PluginInfo pluginInfo, PluginType type, Plugin instance, FileAndMd5 jar, ClassLoader classloader) {
this.pluginInfo = pluginInfo;
this.type = type;
this.instance = instance;
this.jar = jar;
this.compressed = compressed;
this.classloader = classloader;
}

@@ -63,11 +59,6 @@ public class ServerPlugin {
return jar;
}

@CheckForNull
public FileAndMd5 getCompressed() {
return compressed;
}

public ClassLoader getClassloader() {
return classloader;
}

+ 5
- 8
server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginManager.java View File

@@ -48,15 +48,13 @@ public class ServerPluginManager implements Startable {
private final PluginJarLoader pluginJarLoader;
private final PluginJarExploder pluginJarExploder;
private final PluginClassLoader pluginClassLoader;
private final PluginCompressor pluginCompressor;
private final ServerPluginRepository pluginRepository;

public ServerPluginManager(PluginClassLoader pluginClassLoader, PluginJarExploder pluginJarExploder,
PluginJarLoader pluginJarLoader, PluginCompressor pluginCompressor, ServerPluginRepository pluginRepository) {
PluginJarLoader pluginJarLoader, ServerPluginRepository pluginRepository) {
this.pluginClassLoader = pluginClassLoader;
this.pluginJarExploder = pluginJarExploder;
this.pluginJarLoader = pluginJarLoader;
this.pluginCompressor = pluginCompressor;
this.pluginRepository = pluginRepository;
}

@@ -67,7 +65,7 @@ public class ServerPluginManager implements Startable {
Collection<ExplodedPlugin> explodedPlugins = extractPlugins(loadedPlugins);
Map<String, Plugin> instancesByKey = pluginClassLoader.load(explodedPlugins);
Map<String, PluginType> typesByKey = getTypesByKey(loadedPlugins);
List<ServerPlugin> plugins = compressAndCreateServerPlugins(explodedPlugins, instancesByKey, typesByKey);
List<ServerPlugin> plugins = createServerPlugins(explodedPlugins, instancesByKey, typesByKey);
pluginRepository.addPlugins(plugins);
}

@@ -88,12 +86,11 @@ public class ServerPluginManager implements Startable {
return plugins.stream().map(pluginJarExploder::explode).collect(Collectors.toList());
}

private List<ServerPlugin> compressAndCreateServerPlugins(Collection<ExplodedPlugin> explodedPlugins, Map<String, Plugin> instancesByKey, Map<String, PluginType> typseByKey) {
private static List<ServerPlugin> createServerPlugins(Collection<ExplodedPlugin> explodedPlugins, Map<String, Plugin> instancesByKey, Map<String, PluginType> typesByKey) {
List<ServerPlugin> plugins = new ArrayList<>();
for (ExplodedPlugin p : explodedPlugins) {
PluginFilesAndMd5 installedPlugin = pluginCompressor.compress(p.getKey(), p.getPluginInfo().getNonNullJarFile(), p.getMain());
plugins.add(new ServerPlugin(p.getPluginInfo(), typseByKey.get(p.getKey()), instancesByKey.get(p.getKey()),
installedPlugin.getLoadedJar(), installedPlugin.getCompressedJar()));
plugins.add(new ServerPlugin(p.getPluginInfo(), typesByKey.get(p.getKey()), instancesByKey.get(p.getKey()),
new PluginFilesAndMd5.FileAndMd5(p.getPluginInfo().getNonNullJarFile())));
}
return plugins;
}

+ 0
- 128
server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginCompressorTest.java View File

@@ -1,128 +0,0 @@
/*
* SonarQube
* Copyright (C) 2009-2022 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.charset.StandardCharsets;
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 static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.server.plugins.PluginCompressor.PROPERTY_PLUGIN_COMPRESSION_ENABLE;

public class PluginCompressorTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();

private MapSettings settings = new MapSettings();

@Before
public void setUp() throws IOException {
Path targetFolder = temp.newFolder("target").toPath();
Path targetJarPath = targetFolder.resolve("test.jar");
Files.createFile(targetJarPath);
}

@Test
public void compress_jar_if_compression_enabled() throws IOException {
File jar = touch(temp.newFolder(), "sonar-foo-plugin.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);
PluginCompressor underTest = new PluginCompressor(settings.asConfig());

PluginFilesAndMd5 installedPlugin = underTest.compress("foo", jar, loadedJar);
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 dont_compress_jar_if_compression_disable() throws IOException {
File jar = touch(temp.newFolder(), "sonar-foo-plugin.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, false);
PluginCompressor underTest = new PluginCompressor(settings.asConfig());

PluginFilesAndMd5 installedPlugin = underTest.compress("foo", jar, loadedJar);
assertThat(installedPlugin.getLoadedJar().getFile().toPath()).isEqualTo(loadedJar.toPath());
assertThat(installedPlugin.getCompressedJar()).isNull();
assertThat(installedPlugin.getLoadedJar().getFile().getParentFile().listFiles()).containsOnly(loadedJar);
}

@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");
// 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);
PluginCompressor underTest = new PluginCompressor(settings.asConfig());

PluginFilesAndMd5 installedPlugin = underTest.compress("foo", jar, loadedJar);
assertThat(installedPlugin.getLoadedJar().getFile().toPath()).isEqualTo(loadedJar.toPath());
assertThat(installedPlugin.getCompressedJar().getFile())
.exists()
.isFile()
.hasName(packedJar.getName())
.hasParent(loadedJar.getParentFile())
.hasSameTextualContentAs(packedJar);
}

private static File touch(File dir, String filename) throws IOException {
File file = new File(dir, filename);
FileUtils.write(file, RandomStringUtils.random(10), StandardCharsets.UTF_8);
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);
// }

}

+ 1
- 1
server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java View File

@@ -162,7 +162,7 @@ public class PluginUninstallerTest {

private static ServerPlugin newPlugin(ServerPluginInfo pluginInfo) {
return new ServerPlugin(pluginInfo, pluginInfo.getType(), mock(Plugin.class),
mock(PluginFilesAndMd5.FileAndMd5.class), mock(PluginFilesAndMd5.FileAndMd5.class), mock(ClassLoader.class));
mock(PluginFilesAndMd5.FileAndMd5.class), mock(ClassLoader.class));
}

private File copyTestPluginTo(String testPluginName, File toDir) throws IOException {

+ 28
- 22
server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginManagerTest.java View File

@@ -21,9 +21,11 @@ package org.sonar.server.plugins;

import com.google.common.collect.ImmutableMap;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.Map;
import org.jetbrains.annotations.NotNull;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
@@ -36,7 +38,7 @@ import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5;
import org.sonar.updatecenter.common.Version;

import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.tuple;
import static org.junit.Assert.assertEquals;
import static org.mockito.ArgumentMatchers.anyList;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -50,9 +52,8 @@ public class ServerPluginManagerTest {
private PluginClassLoader pluginClassLoader = mock(PluginClassLoader.class);
private PluginJarExploder jarExploder = mock(PluginJarExploder.class);
private PluginJarLoader jarLoader = mock(PluginJarLoader.class);
private PluginCompressor pluginCompressor = mock(PluginCompressor.class);
private ServerPluginRepository pluginRepository = new ServerPluginRepository();
private ServerPluginManager underTest = new ServerPluginManager(pluginClassLoader, jarExploder, jarLoader, pluginCompressor, pluginRepository);
private ServerPluginManager underTest = new ServerPluginManager(pluginClassLoader, jarExploder, jarLoader, pluginRepository);

@After
public void tearDown() {
@@ -69,18 +70,20 @@ public class ServerPluginManagerTest {

Map<String, Plugin> instances = ImmutableMap.of("p1", mock(Plugin.class), "p2", mock(Plugin.class));
when(pluginClassLoader.load(anyList())).thenReturn(instances);
PluginFilesAndMd5 p1Files = newPluginFilesAndMd5("p1");
PluginFilesAndMd5 p2Files = newPluginFilesAndMd5("p2");

when(pluginCompressor.compress("p1", new File("p1.jar"), new File("p1Exploded.jar"))).thenReturn(p1Files);
when(pluginCompressor.compress("p2", new File("p2.jar"), new File("p2Exploded.jar"))).thenReturn(p2Files);

underTest.start();

assertThat(pluginRepository.getPlugins())
.extracting(ServerPlugin::getPluginInfo, ServerPlugin::getCompressed, ServerPlugin::getJar, ServerPlugin::getInstance)
.containsOnly(tuple(p1, p1Files.getCompressedJar(), p1Files.getLoadedJar(), instances.get("p1")),
tuple(p2, p2Files.getCompressedJar(), p2Files.getLoadedJar(), instances.get("p2")));
assertEquals(2, pluginRepository.getPlugins().size());

assertEquals(p1, pluginRepository.getPlugin("p1").getPluginInfo());
assertEquals(newFileAndMd5(p1.getNonNullJarFile()).getFile(), pluginRepository.getPlugin("p1").getJar().getFile());
assertEquals(newFileAndMd5(p1.getNonNullJarFile()).getMd5(), pluginRepository.getPlugin("p1").getJar().getMd5());
assertEquals(instances.get("p1"), pluginRepository.getPlugin("p1").getInstance());

assertEquals(p2, pluginRepository.getPlugin("p2").getPluginInfo());
assertEquals(newFileAndMd5(p2.getNonNullJarFile()).getFile(), pluginRepository.getPlugin("p2").getJar().getFile());
assertEquals(newFileAndMd5(p2.getNonNullJarFile()).getMd5(), pluginRepository.getPlugin("p2").getJar().getMd5());
assertEquals(instances.get("p2"), pluginRepository.getPlugin("p2").getInstance());

assertThat(pluginRepository.getPlugins()).extracting(ServerPlugin::getPluginInfo)
.allMatch(p -> logTester.logs().contains(String.format("Deploy %s / %s / %s", p.getName(), p.getVersion(), p.getImplementationBuild())));
@@ -90,7 +93,7 @@ public class ServerPluginManagerTest {
ServerPluginInfo pluginInfo = mock(ServerPluginInfo.class);
when(pluginInfo.getKey()).thenReturn(key);
when(pluginInfo.getType()).thenReturn(EXTERNAL);
when(pluginInfo.getNonNullJarFile()).thenReturn(new File(key + ".jar"));
when(pluginInfo.getNonNullJarFile()).thenReturn(getJarFile(key));
when(pluginInfo.getName()).thenReturn(key + "_name");
Version version = mock(Version.class);
when(version.getName()).thenReturn(key + "_version");
@@ -99,15 +102,18 @@ public class ServerPluginManagerTest {
return pluginInfo;
}

private static PluginFilesAndMd5 newPluginFilesAndMd5(String name) {
FileAndMd5 jar = mock(FileAndMd5.class);
when(jar.getFile()).thenReturn(new File(name));
when(jar.getMd5()).thenReturn(name + "-md5");

FileAndMd5 compressed = mock(FileAndMd5.class);
when(compressed.getFile()).thenReturn(new File(name + "-compressed"));
when(compressed.getMd5()).thenReturn(name + "-compressed-md5");
@NotNull
private static File getJarFile(String key) {
File file = new File(key + ".jar");
try {
file.createNewFile();
} catch (IOException e) {
throw new RuntimeException(e);
}
return file;
}

return new PluginFilesAndMd5(jar, compressed);
private static FileAndMd5 newFileAndMd5(File file) {
return new PluginFilesAndMd5.FileAndMd5(file);
}
}

+ 1
- 1
server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java View File

@@ -133,6 +133,6 @@ public class ServerPluginRepositoryTest {
}

private ServerPlugin newPlugin(String key, PluginType type) {
return new ServerPlugin(newPluginInfo(key), type, mock(Plugin.class), mock(FileAndMd5.class), mock(FileAndMd5.class), mock(ClassLoader.class));
return new ServerPlugin(newPluginInfo(key), type, mock(Plugin.class), mock(FileAndMd5.class), mock(ClassLoader.class));
}
}

+ 1
- 1
server/sonar-webserver-core/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java View File

@@ -86,6 +86,6 @@ public class GeneratePluginIndexTest {
private ServerPlugin 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 ServerPlugin(pluginInfo, BUNDLED, null, jar, null, null);
return new ServerPlugin(pluginInfo, BUNDLED, null, jar, null);
}
}

+ 1
- 1
server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java View File

@@ -210,7 +210,7 @@ public class RegisterPluginsTest {
PluginInfo info = new PluginInfo(key)
.setBasePlugin(basePlugin)
.setJarFile(file);
ServerPlugin serverPlugin = new ServerPlugin(info, type, null, jar, null, null);
ServerPlugin serverPlugin = new ServerPlugin(info, type, null, jar, null);
serverPluginRepository.addPlugin(serverPlugin);
return serverPlugin;
}

+ 5
- 15
server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/DownloadAction.java View File

@@ -23,6 +23,7 @@ import java.io.InputStream;
import java.util.Optional;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
@@ -32,9 +33,6 @@ import org.sonar.server.plugins.ServerPlugin;
import org.sonar.server.plugins.ServerPluginRepository;

public class DownloadAction implements PluginsWsAction {

private static final String PACK200 = "pack200";
private static final String ACCEPT_COMPRESSIONS_PARAM = "acceptCompressions";
private static final String PLUGIN_PARAM = "plugin";

private final ServerPluginRepository pluginRepository;
@@ -57,8 +55,7 @@ public class DownloadAction implements PluginsWsAction {
.setDescription("The key identifying the plugin to download")
.setExampleValue("cobol");

action.createParam(ACCEPT_COMPRESSIONS_PARAM)
.setExampleValue(PACK200);
action.setChangelog(new Change("9.8", "Parameter 'acceptCompressions' removed"));
}

@Override
@@ -71,17 +68,10 @@ public class DownloadAction implements PluginsWsAction {
}

FileAndMd5 downloadedFile;
FileAndMd5 compressedJar = file.get().getCompressed();
if (compressedJar != null && PACK200.equals(request.param(ACCEPT_COMPRESSIONS_PARAM))) {
response.stream().setMediaType("application/octet-stream");

response.setHeader("Sonar-Compression", PACK200);
response.setHeader("Sonar-UncompressedMD5", file.get().getJar().getMd5());
downloadedFile = compressedJar;
} else {
response.stream().setMediaType("application/java-archive");
downloadedFile = file.get().getJar();
}
response.stream().setMediaType("application/java-archive");
downloadedFile = file.get().getJar();

response.setHeader("Sonar-MD5", downloadedFile.getMd5());
try (InputStream input = FileUtils.openInputStream(downloadedFile.getFile())) {
IOUtils.copyLarge(input, response.stream().output());

+ 7
- 58
server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/DownloadActionTest.java View File

@@ -24,9 +24,11 @@ import java.io.IOException;
import java.util.Optional;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.assertj.core.groups.Tuple;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.WebService;
import org.sonar.core.platform.PluginInfo;
import org.sonar.server.exceptions.NotFoundException;
@@ -61,7 +63,10 @@ public class DownloadActionTest {
assertThat(def.since()).isEqualTo("7.2");
assertThat(def.params())
.extracting(WebService.Param::key)
.containsExactlyInAnyOrder("plugin", "acceptCompressions");
.containsExactlyInAnyOrder("plugin");
assertThat(def.changelog())
.extracting(Change::getVersion, Change::getDescription)
.containsExactlyInAnyOrder(new Tuple("9.8", "Parameter 'acceptCompressions' removed"));
}

@Test
@@ -88,65 +93,9 @@ public class DownloadActionTest {
verifySameContent(response, plugin.getJar().getFile());
}

@Test
public void return_uncompressed_jar_if_client_does_not_accept_compression() throws Exception {
ServerPlugin plugin = newCompressedPlugin();
when(serverPluginRepository.findPlugin(plugin.getPluginInfo().getKey())).thenReturn(Optional.of(plugin));

TestResponse response = tester.newRequest()
.setParam("plugin", plugin.getPluginInfo().getKey())
.execute();

assertThat(response.getHeader("Sonar-MD5")).isEqualTo(plugin.getJar().getMd5());
assertThat(response.getHeader("Sonar-Compression")).isNull();
assertThat(response.getHeader("Sonar-UncompressedMD5")).isNull();
assertThat(response.getMediaType()).isEqualTo("application/java-archive");
verifySameContent(response, plugin.getJar().getFile());
}

@Test
public void return_uncompressed_jar_if_client_requests_unsupported_compression() throws Exception {
ServerPlugin plugin = newCompressedPlugin();
when(serverPluginRepository.findPlugin(plugin.getPluginInfo().getKey())).thenReturn(Optional.of(plugin));

TestResponse response = tester.newRequest()
.setParam("plugin", plugin.getPluginInfo().getKey())
.setParam("acceptCompressions", "zip")
.execute();

assertThat(response.getHeader("Sonar-MD5")).isEqualTo(plugin.getJar().getMd5());
assertThat(response.getHeader("Sonar-Compression")).isNull();
assertThat(response.getHeader("Sonar-UncompressedMD5")).isNull();
assertThat(response.getMediaType()).isEqualTo("application/java-archive");
verifySameContent(response, plugin.getJar().getFile());
}

@Test
public void return_compressed_jar_if_client_accepts_pack200() throws Exception {
ServerPlugin plugin = newCompressedPlugin();
when(serverPluginRepository.findPlugin(plugin.getPluginInfo().getKey())).thenReturn(Optional.of(plugin));

TestResponse response = tester.newRequest()
.setParam("plugin", plugin.getPluginInfo().getKey())
.setParam("acceptCompressions", "pack200")
.execute();

assertThat(response.getHeader("Sonar-MD5")).isEqualTo(plugin.getCompressed().getMd5());
assertThat(response.getHeader("Sonar-UncompressedMD5")).isEqualTo(plugin.getJar().getMd5());
assertThat(response.getHeader("Sonar-Compression")).isEqualTo("pack200");
assertThat(response.getMediaType()).isEqualTo("application/octet-stream");
verifySameContent(response, plugin.getCompressed().getFile());
}

private ServerPlugin newPlugin() throws IOException {
FileAndMd5 jar = new FileAndMd5(temp.newFile());
return new ServerPlugin(new PluginInfo("foo"), PluginType.BUNDLED, null, jar, null, null);
}

private ServerPlugin newCompressedPlugin() throws IOException {
FileAndMd5 jar = new FileAndMd5(temp.newFile());
FileAndMd5 compressedJar = new FileAndMd5(temp.newFile());
return new ServerPlugin(new PluginInfo("foo"), PluginType.BUNDLED, null, jar, compressedJar, null);
return new ServerPlugin(new PluginInfo("foo"), PluginType.BUNDLED, null, jar, null);
}

private static void verifySameContent(TestResponse response, File file) throws IOException {

+ 3
- 59
server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java View File

@@ -211,13 +211,7 @@ public class InstalledActionTest {

private ServerPlugin newInstalledPlugin(PluginInfo plugin, PluginType type) throws IOException {
FileAndMd5 jar = new FileAndMd5(temp.newFile());
return new ServerPlugin(plugin, type, null, jar, null, null);
}

private ServerPlugin newInstalledPluginWithCompression(PluginInfo plugin) throws IOException {
FileAndMd5 jar = new FileAndMd5(temp.newFile());
FileAndMd5 compressedJar = new FileAndMd5(temp.newFile());
return new ServerPlugin(plugin, PluginType.BUNDLED, null, jar, compressedJar, null);
return new ServerPlugin(plugin, type, null, jar, null);
}

@Test
@@ -267,56 +261,6 @@ public class InstalledActionTest {
"}");
}

@Test
public void return_compression_fields_if_available() throws Exception {
ServerPlugin 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")
.setDocumentationPath("static/documentation.md")
.setSonarLintSupported(true));
when(serverPluginRepository.getPlugins()).thenReturn(singletonList(plugin));

db.pluginDbTester().insertPlugin(
p -> p.setKee(plugin.getPluginInfo().getKey()),
p -> p.setType(Type.EXTERNAL),
p -> p.setUpdatedAt(100L));

String response = tester.newRequest().execute().getInput();

verifyNoInteractions(updateCenterMatrixFactory);
assertJson(response).isSimilarTo(
"{" +
" \"plugins\":" +
" [" +
" {" +
" \"key\": \"foo\"," +
" \"name\": \"plugName\"," +
" \"description\": \"desc_it\"," +
" \"version\": \"1.0\"," +
" \"license\": \"license_hey\"," +
" \"organizationName\": \"org_name\"," +
" \"organizationUrl\": \"org_url\",\n" +
" \"editionBundled\": false," +
" \"homepageUrl\": \"homepage_url\"," +
" \"issueTrackerUrl\": \"issueTracker_url\"," +
" \"implementationBuild\": \"sou_rev_sha1\"," +
" \"sonarLintSupported\": true," +
" \"documentationPath\": \"static/documentation.md\"," +
" \"filename\": \"" + plugin.getJar().getFile().getName() + "\"," +
" \"hash\": \"" + plugin.getJar().getMd5() + "\"," +
" \"updatedAt\": 100" +
" }" +
" ]" +
"}");
}

@Test
public void category_is_returned_when_in_additional_fields() throws Exception {
String jarFilename = getClass().getSimpleName() + "/" + "some.jar";
@@ -433,7 +377,7 @@ public class InstalledActionTest {
.setImplementationBuild("sou_rev_sha1"),
PluginType.BUNDLED,
null,
new FileAndMd5(jar), new FileAndMd5(jar), null)));
new FileAndMd5(jar), null)));
db.pluginDbTester().insertPlugin(
p -> p.setKee(pluginKey),
p -> p.setType(Type.BUNDLED),
@@ -506,7 +450,7 @@ public class InstalledActionTest {
.setName(name)
.setVersion(Version.create("1.0"));
info.setJarFile(file);
return new ServerPlugin(info, PluginType.BUNDLED, null, new FileAndMd5(file), null, null);
return new ServerPlugin(info, PluginType.BUNDLED, null, new FileAndMd5(file), null);
}

}

+ 0
- 2
server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java View File

@@ -39,7 +39,6 @@ import org.sonar.server.platform.db.migration.charset.DatabaseCharsetChecker;
import org.sonar.server.platform.db.migration.version.DatabaseVersion;
import org.sonar.server.platform.web.WebPagesCache;
import org.sonar.server.plugins.InstalledPluginReferentialFactory;
import org.sonar.server.plugins.PluginCompressor;
import org.sonar.server.plugins.PluginJarLoader;
import org.sonar.server.plugins.ServerPluginJarExploder;
import org.sonar.server.plugins.ServerPluginManager;
@@ -75,7 +74,6 @@ public class PlatformLevel2 extends PlatformLevel {
ServerPluginManager.class,
ServerPluginJarExploder.class,
PluginClassLoader.class,
PluginCompressor.class,
PluginClassloaderFactory.class,
InstalledPluginReferentialFactory.class,
WebServerExtensionInstaller.class,

+ 2
- 2
server/sonar-webserver/src/test/java/org/sonar/server/platform/platformlevel/PlatformLevel2Test.java View File

@@ -73,7 +73,7 @@ public class PlatformLevel2Test {

verify(container).add(ServerPluginRepository.class);
verify(container).add(DatabaseCharsetChecker.class);
verify(container, times(23)).add(any());
verify(container, times(22)).add(any());
}

@Test
@@ -94,7 +94,7 @@ public class PlatformLevel2Test {

verify(container).add(ServerPluginRepository.class);
verify(container, never()).add(DatabaseCharsetChecker.class);
verify(container, times(21)).add(any());
verify(container, times(20)).add(any());
}



+ 3
- 35
sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/PluginFiles.java View File

@@ -20,7 +20,6 @@
package org.sonar.scanner.bootstrap;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -28,10 +27,7 @@ import java.net.HttpURLConnection;
import java.nio.file.Files;
import java.util.Objects;
import java.util.Optional;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import java.util.stream.Stream;
import java.util.zip.GZIPInputStream;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
import org.sonar.api.config.Configuration;
@@ -48,9 +44,6 @@ public class PluginFiles {

private static final Logger LOGGER = Loggers.get(PluginFiles.class);
private static final String MD5_HEADER = "Sonar-MD5";
private static final String COMPRESSION_HEADER = "Sonar-Compression";
private static final String PACK200 = "pack200";
private static final String UNCOMPRESSED_MD5_HEADER = "Sonar-UncompressedMD5";

private final DefaultScannerWsClient wsClient;
private final File cacheDir;
@@ -94,13 +87,6 @@ public class PluginFiles {
.setParam("plugin", plugin.key)
.setTimeOutInMs(5 * 60_000);

try {
Class.forName("java.util.jar.Pack200");
request.setParam("acceptCompressions", PACK200);
} catch (ClassNotFoundException e) {
// ignore and don't use any compression
}

File downloadedFile = newTempFile();
LOGGER.debug("Download plugin '{}' to '{}'", plugin.key, downloadedFile);

@@ -123,15 +109,9 @@ public class PluginFiles {
// un-compress if needed
String cacheMd5;
File tempJar;
Optional<String> compression = response.header(COMPRESSION_HEADER);
if (compression.isPresent() && PACK200.equals(compression.get())) {
tempJar = unpack200(plugin.key, downloadedFile);
cacheMd5 = response.header(UNCOMPRESSED_MD5_HEADER).orElseThrow(() -> new IllegalStateException(format(
"Fail to download plugin [%s]. Request to %s did not return header %s.", plugin.key, response.requestUrl(), UNCOMPRESSED_MD5_HEADER)));
} else {
tempJar = downloadedFile;
cacheMd5 = expectedMd5.get();
}

tempJar = downloadedFile;
cacheMd5 = expectedMd5.get();

// put in cache
File jarInCache = jarInCache(plugin.key, cacheMd5);
@@ -177,18 +157,6 @@ public class PluginFiles {
}
}

private File unpack200(String pluginKey, File compressedFile) {
LOGGER.debug("Unpacking plugin {}", pluginKey);
File jar = newTempFile();
try (InputStream input = new GZIPInputStream(new BufferedInputStream(FileUtils.openInputStream(compressedFile)));
JarOutputStream output = new JarOutputStream(new BufferedOutputStream(FileUtils.openOutputStream(jar)))) {
Pack200.newUnpacker().unpack(input, output);
} catch (IOException e) {
throw new IllegalStateException(format("Fail to download plugin [%s]. Pack200 error.", pluginKey), e);
}
return jar;
}

private static String computeMd5(File file) {
try (InputStream fis = new BufferedInputStream(FileUtils.openInputStream(file))) {
return DigestUtils.md5Hex(fis);

+ 1
- 100
sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/PluginFilesTest.java View File

@@ -19,26 +19,15 @@
*/
package org.sonar.scanner.bootstrap;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.util.Collections;
import java.util.Optional;
import java.util.jar.JarInputStream;
import java.util.jar.JarOutputStream;
import java.util.jar.Pack200;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
import javax.annotation.Nullable;
import okhttp3.HttpUrl;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import okio.Buffer;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FileUtils;
@@ -107,7 +96,7 @@ public class PluginFilesTest {
verifySameContent(result, tempJar);
HttpUrl requestedUrl = server.takeRequest().getRequestUrl();
assertThat(requestedUrl.encodedPath()).isEqualTo("/api/plugins/download");
assertThat(requestedUrl.encodedQuery()).isEqualTo("plugin=foo&acceptCompressions=pack200");
assertThat(requestedUrl.encodedQuery()).isEqualTo("plugin=foo");

// get from cache on second call
result = underTest.get(plugin).get();
@@ -115,24 +104,6 @@ public class PluginFilesTest {
assertThat(server.getRequestCount()).isOne();
}

@Test
public void download_compressed_and_add_uncompressed_to_cache_if_missing() throws Exception {
FileAndMd5 jar = new FileAndMd5();
enqueueCompressedDownload(jar, true);

InstalledPlugin plugin = newInstalledPlugin("foo", jar.md5);
File result = underTest.get(plugin).get();

verifySameContentAfterCompression(jar.file, result);
RecordedRequest recordedRequest = server.takeRequest();
assertThat(recordedRequest.getRequestUrl().queryParameter("acceptCompressions")).isEqualTo("pack200");

// get from cache on second call
result = underTest.get(plugin).get();
verifySameContentAfterCompression(jar.file, result);
assertThat(server.getRequestCount()).isOne();
}

@Test
public void return_empty_if_plugin_not_found_on_server() {
server.enqueue(new MockResponse().setResponseCode(404));
@@ -153,16 +124,6 @@ public class PluginFilesTest {
() -> underTest.get(plugin));
}

@Test
public void fail_if_integrity_of_compressed_download_is_not_valid() throws Exception {
FileAndMd5 jar = new FileAndMd5();
enqueueCompressedDownload(jar, false);

InstalledPlugin plugin = newInstalledPlugin("foo", jar.md5);

expectISE("foo", "was expected to have checksum invalid_hash but had ", () -> underTest.get(plugin).get());
}

@Test
public void fail_if_md5_header_is_missing_from_response() throws IOException {
File tempJar = temp.newFile();
@@ -172,19 +133,6 @@ public class PluginFilesTest {
expectISE("foo", "did not return header Sonar-MD5", () -> underTest.get(plugin));
}

@Test
public void fail_if_compressed_download_cannot_be_uncompressed() {
MockResponse response = new MockResponse().setBody("not binary");
response.setHeader("Sonar-MD5", DigestUtils.md5Hex("not binary"));
response.setHeader("Sonar-UncompressedMD5", "abc");
response.setHeader("Sonar-Compression", "pack200");
server.enqueue(response);

InstalledPlugin plugin = newInstalledPlugin("foo", "abc");

expectISE("foo", "Pack200 error", () -> underTest.get(plugin).get());
}

@Test
public void fail_if_server_returns_error() {
server.enqueue(new MockResponse().setResponseCode(500));
@@ -260,26 +208,6 @@ public class PluginFilesTest {
server.enqueue(response);
}

/**
* Enqueue download of file with a MD5 that may not be returned (null) or not valid
*/
private void enqueueCompressedDownload(FileAndMd5 jar, boolean validMd5) throws IOException {
Buffer body = new Buffer();

ByteArrayOutputStream bytes = new ByteArrayOutputStream();
try (JarInputStream in = new JarInputStream(new BufferedInputStream(Files.newInputStream(jar.file.toPath())));
OutputStream output = new GZIPOutputStream(new BufferedOutputStream(bytes))) {
Pack200.newPacker().pack(in, output);
}
body.write(bytes.toByteArray());

MockResponse response = new MockResponse().setBody(body);
response.setHeader("Sonar-MD5", validMd5 ? DigestUtils.md5Hex(bytes.toByteArray()) : "invalid_hash");
response.setHeader("Sonar-UncompressedMD5", jar.md5);
response.setHeader("Sonar-Compression", "pack200");
server.enqueue(response);
}

private static InstalledPlugin newInstalledPlugin(String pluginKey, String fileChecksum) {
InstalledPlugin plugin = new InstalledPlugin();
plugin.key = pluginKey;
@@ -293,33 +221,6 @@ public class PluginFilesTest {
assertThat(file1).hasSameContentAs(file2.file);
}

/**
* Packing and unpacking a JAR generates a different file.
*/
private void verifySameContentAfterCompression(File file1, File file2) throws IOException {
assertThat(file1).isFile().exists();
assertThat(file2).isFile().exists();
assertThat(packAndUnpackJar(file1)).hasSameContentAs(packAndUnpackJar(file2));
}

private File packAndUnpackJar(File source) throws IOException {
File packed = temp.newFile();
try (JarInputStream in = new JarInputStream(new BufferedInputStream(Files.newInputStream(source.toPath())));
OutputStream out = new GZIPOutputStream(new BufferedOutputStream(Files.newOutputStream(packed.toPath())))) {
Pack200.newPacker().pack(in, out);
}

File to = temp.newFile();
try (InputStream input = new GZIPInputStream(new BufferedInputStream(Files.newInputStream(packed.toPath())));
JarOutputStream output = new JarOutputStream(new BufferedOutputStream(Files.newOutputStream(to.toPath())))) {
Pack200.newUnpacker().unpack(input, output);
} catch (IOException e) {
throw new IllegalStateException(e);
}

return to;
}

private void expectISE(String pluginKey, String message, ThrowingCallable shouldRaiseThrowable) {
assertThatThrownBy(shouldRaiseThrowable)
.isInstanceOf(IllegalStateException.class)

+ 0
- 14
sonar-ws/src/main/java/org/sonarqube/ws/client/plugins/DownloadRequest.java View File

@@ -29,22 +29,8 @@ import javax.annotation.Generated;
*/
@Generated("sonar-ws-generator")
public class DownloadRequest {

private String acceptCompressions;
private String plugin;

/**
* Example value: "pack200"
*/
public DownloadRequest setAcceptCompressions(String acceptCompressions) {
this.acceptCompressions = acceptCompressions;
return this;
}

public String getAcceptCompressions() {
return acceptCompressions;
}

/**
* This is a mandatory parameter.
* Example value: "cobol"

Loading…
Cancel
Save