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.ServerPluginJarExploder;
import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.plugins.WebServerExtensionInstaller;
ServerPluginRepository.class,
ServerPluginJarExploder.class,
PluginLoader.class,
+ PluginCompression.class,
PluginClassloaderFactory.class,
InstalledPluginReferentialFactory.class,
WebServerExtensionInstaller.class,
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.plugins;
+
+import java.io.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 jarFile) {
+ if (configuration.getBoolean(PROPERTY_PLUGIN_COMPRESSION_ENABLE).orElse(false)) {
+ Path pack200Path = FileUtils.getPack200FilePath(jarFile);
+ pack200(jarFile, pack200Path, pluginKey);
+ String hash = calculateMd5(pack200Path);
+ RemotePluginFile compressedPlugin = new RemotePluginFile(pack200Path.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();
+ }
+}
@ServerSide
@ComputeEngineSide
public class ServerPluginJarExploder extends PluginJarExploder {
-
private final ServerFileSystem fs;
+ private final PluginCompression pluginCompression;
- public ServerPluginJarExploder(ServerFileSystem fs) {
+ public ServerPluginJarExploder(ServerFileSystem fs, PluginCompression pluginCompression) {
this.fs = fs;
+ this.pluginCompression = pluginCompression;
}
/**
File jarSource = pluginInfo.getNonNullJarFile();
File jarTarget = new File(toDir, jarSource.getName());
+
FileUtils.copyFile(jarSource, jarTarget);
+ pluginCompression.compressJar(pluginInfo.getKey(), jarTarget.toPath());
ZipUtils.unzip(jarSource, toDir, newLibFilter());
return explodeFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir);
} catch (Exception e) {
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.UpdateCenterMatrixFactory;
import org.sonar.updatecenter.common.Plugin;
private final PluginWSCommons pluginWSCommons;
private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
private final DbClient dbClient;
+ private final PluginCompression compression;
- public InstalledAction(ServerPluginRepository pluginRepository, PluginWSCommons pluginWSCommons, UpdateCenterMatrixFactory updateCenterMatrixFactory, DbClient dbClient) {
+ public InstalledAction(ServerPluginRepository pluginRepository, PluginCompression compression, PluginWSCommons pluginWSCommons,
+ UpdateCenterMatrixFactory updateCenterMatrixFactory, DbClient dbClient) {
this.pluginRepository = pluginRepository;
+ this.compression = compression;
this.pluginWSCommons = pluginWSCommons;
this.updateCenterMatrixFactory = updateCenterMatrixFactory;
this.dbClient = dbClient;
new Change("6.6", "The 'filename' field is added"),
new Change("6.6", "The 'fileHash' field is added"),
new Change("6.6", "The 'sonarLintSupported' field is added"),
- new Change("6.6", "The 'updatedAt' field is added"))
+ new Change("6.6", "The 'updatedAt' field is added"),
+ new Change("7.0", "The fields 'compressedHash' and 'compressedFilename' are added"))
.setHandler(this)
.setResponseExample(Resources.getResource(this.getClass(), "example-installed_plugins.json"));
Map<String, Plugin> compatiblesPluginsFromUpdateCenter = additionalFields.isEmpty()
? Collections.<String, Plugin>emptyMap()
: compatiblePluginsByKey(updateCenterMatrixFactory);
- pluginWSCommons.writePluginInfoList(jsonWriter, pluginInfoList, compatiblesPluginsFromUpdateCenter, ARRAY_PLUGINS, pluginDtos);
+ pluginWSCommons.writePluginInfoList(jsonWriter, pluginInfoList, compatiblesPluginsFromUpdateCenter, ARRAY_PLUGINS, pluginDtos, compression.getPlugins());
}
}
}
}
- pluginWSCommons.writePluginInfoList(json, newPlugins, compatiblePluginsByKey, ARRAY_INSTALLING, null);
- pluginWSCommons.writePluginInfoList(json, updatedPlugins, compatiblePluginsByKey, ARRAY_UPDATING, null);
- pluginWSCommons.writePluginInfoList(json, uninstalledPlugins, compatiblePluginsByKey, ARRAY_REMOVING, null);
+ pluginWSCommons.writePluginInfoList(json, newPlugins, compatiblePluginsByKey, ARRAY_INSTALLING);
+ pluginWSCommons.writePluginInfoList(json, updatedPlugins, compatiblePluginsByKey, ARRAY_UPDATING);
+ pluginWSCommons.writePluginInfoList(json, uninstalledPlugins, compatiblePluginsByKey, ARRAY_REMOVING);
}
private enum PluginInfoToKey implements Function<PluginInfo, String> {
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.UpdateCenterMatrixFactory;
import org.sonar.server.plugins.edition.EditionBundledPlugins;
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";
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) {
+ void writePluginInfo(JsonWriter json, PluginInfo pluginInfo, @Nullable String category, @Nullable PluginDto pluginDto, @Nullable RemotePluginFile compressedPlugin) {
json.beginObject();
json.prop(PROPERTY_KEY, pluginInfo.getKey());
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) {
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, PluginDto> pluginDtos, @Nullable Map<String, RemotePluginFile> compressedPlugins) {
json.name(propertyName);
json.beginArray();
for (PluginInfo pluginInfo : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) {
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);
+ writePluginInfo(json, pluginInfo, categoryOrNull(plugin), pluginDto, compressedPlugin);
}
json.endArray();
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info AT sonarsource DOT com
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.server.plugins;
+
+import java.io.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 jarPath;
+
+ private PluginCompression underTest;
+
+ @Before
+ public void setUp() throws IOException {
+ jarPath = temp.newFile("test.jar").toPath();
+ }
+
+ @Test
+ public void disable_if_proparty_not_set() throws IOException {
+ underTest = new PluginCompression(settings.asConfig());
+ underTest.compressJar("key", jarPath);
+
+ assertThat(Files.list(jarPath.getParent())).containsOnly(jarPath);
+ 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", jarPath);
+
+ assertThat(Files.list(jarPath.getParent())).containsOnly(jarPath, jarPath.getParent().resolve("test.pack.gz"));
+ assertThat(underTest.getPlugins()).hasSize(1);
+ assertThat(underTest.getPlugins().get("key").getFilename()).isEqualTo("test.pack.gz");
+ }
+}
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class ServerPluginJarExploderTest {
public TemporaryFolder temp = new TemporaryFolder();
ServerFileSystem fs = mock(ServerFileSystem.class);
- ServerPluginJarExploder underTest = new ServerPluginJarExploder(fs);
+ PluginCompression pluginCompression = mock(PluginCompression.class);
+ ServerPluginJarExploder underTest = new ServerPluginJarExploder(fs, pluginCompression);
@Test
public void copy_all_classloader_files_to_dedicated_directory() throws Exception {
assertThat(lib).exists().isFile();
assertThat(lib.getCanonicalPath()).startsWith(pluginDeployDir.getCanonicalPath());
}
+ verify(pluginCompression).compressJar(info.getKey(), exploded.getMain().toPath());
}
}
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.io.File;
import java.util.Arrays;
+import java.util.Collections;
import java.util.Random;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
-import org.sonar.api.server.ws.Request;
-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.UpdateCenterMatrixFactory;
-import org.sonar.server.ws.WsTester;
+import org.sonar.server.ws.WsActionTester;
import org.sonar.updatecenter.common.Plugin;
import org.sonar.updatecenter.common.UpdateCenter;
import org.sonar.updatecenter.common.Version;
@RunWith(DataProviderRunner.class)
public class InstalledActionTest {
- private static final String DUMMY_CONTROLLER_KEY = "dummy";
private static final String JSON_EMPTY_PLUGIN_LIST = "{" +
" \"plugins\":" + "[]" +
"}";
private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
private UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class, RETURNS_DEEP_STUBS);
- private Request request = mock(Request.class);
- private WsTester.TestResponse response = new WsTester.TestResponse();
- private InstalledAction underTest = new InstalledAction(pluginRepository, new PluginWSCommons(), updateCenterMatrixFactory, db.getDbClient());
+ private PluginCompression pluginCompression = mock(PluginCompression.class);
+ private InstalledAction underTest = new InstalledAction(pluginRepository, pluginCompression, new PluginWSCommons(), updateCenterMatrixFactory, db.getDbClient());
+ private WsActionTester tester = new WsActionTester(underTest);
@Test
public void action_installed_is_defined() {
- WsTester wsTester = new WsTester();
- WebService.NewController newController = wsTester.context().createController(DUMMY_CONTROLLER_KEY);
+ Action action = tester.getDef();
- underTest.define(newController);
- newController.done();
-
- WebService.Controller controller = wsTester.controller(DUMMY_CONTROLLER_KEY);
- assertThat(controller.actions()).extracting("key").containsExactly("installed");
-
- WebService.Action action = controller.actions().iterator().next();
assertThat(action.isPost()).isFalse();
assertThat(action.description()).isNotEmpty();
assertThat(action.responseExample()).isNotNull();
@Test
public void empty_array_is_returned_when_there_is_not_plugin_installed() throws Exception {
- underTest.handle(request, response);
+ String response = tester.newRequest().execute().getInput();
- assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo(JSON_EMPTY_PLUGIN_LIST);
+ assertJson(response).withStrictArrayOrder().isSimilarTo(JSON_EMPTY_PLUGIN_LIST);
}
@Test
public void empty_array_when_update_center_is_unavailable() throws Exception {
when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.<UpdateCenter>absent());
- underTest.handle(request, response);
+ String response = tester.newRequest().execute().getInput();
- assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo(JSON_EMPTY_PLUGIN_LIST);
+ assertJson(response).withStrictArrayOrder().isSimilarTo(JSON_EMPTY_PLUGIN_LIST);
}
@Test
p -> p.setFileHash("abcdA"),
p -> p.setUpdatedAt(111111L));
- underTest.handle(request, response);
+ String response = tester.newRequest().execute().getInput();
- assertThat(response.outputAsString()).doesNotContain("name").doesNotContain("key");
+ assertThat(response).doesNotContain("name").doesNotContain("key");
}
@Test
p -> p.setFileHash("abcdplugKey"),
p -> p.setUpdatedAt(111111L));
- underTest.handle(request, response);
+ String response = tester.newRequest().execute().getInput();
+
+ verifyZeroInteractions(updateCenterMatrixFactory);
+ assertJson(response).isSimilarTo(
+ "{" +
+ " \"plugins\":" +
+ " [" +
+ " {" +
+ " \"key\": \"plugKey\"," +
+ " \"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," +
+ " \"filename\": \"some.jar\"," +
+ " \"hash\": \"abcdplugKey\"," +
+ " \"updatedAt\": 111111" +
+ " }" +
+ " ]" +
+ "}");
+ }
+
+ @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));
+
+ 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));
+
+ String response = tester.newRequest().execute().getInput();
verifyZeroInteractions(updateCenterMatrixFactory);
- assertJson(response.outputAsString()).isSimilarTo(
+ assertJson(response).isSimilarTo(
"{" +
" \"plugins\":" +
" [" +
" \"description\": \"desc_it\"," +
" \"version\": \"1.0\"," +
" \"license\": \"license_hey\"," +
+ " \"compressedFilename\": \"compressed.pack.gz\"," +
+ " \"compressedHash\": \"hash\"," +
" \"organizationName\": \"org_name\"," +
" \"organizationUrl\": \"org_url\",\n" +
" \"editionBundled\": false," +
p -> p.setFileHash("abcdplugKey"),
p -> p.setUpdatedAt(111111L));
- when(request.paramAsStrings(Param.FIELDS)).thenReturn(singletonList("category"));
+ String response = tester.newRequest()
+ .setParam(Param.FIELDS, "category")
+ .execute().getInput();
- underTest.handle(request, response);
-
- assertJson(response.outputAsString()).isSimilarTo(
+ assertJson(response).isSimilarTo(
"{" +
" \"plugins\":" +
" [" +
p -> p.setFileHash("abcdD"),
p -> p.setUpdatedAt(444444L));
- underTest.handle(request, response);
+ String resp = tester.newRequest().execute().getInput();
- assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo(
+ assertJson(resp).withStrictArrayOrder().isSimilarTo(
"{" +
" \"plugins\":" +
" [" +
UpdateCenter updateCenter = mock(UpdateCenter.class);
when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.of(updateCenter));
when(updateCenter.findAllCompatiblePlugins()).thenReturn(
- singletonList(
- Plugin.factory(pluginKey)
- .setOrganization("foo")
- .setLicense("bar")
- .setCategory("cat_1")));
-
+ singletonList(
+ Plugin.factory(pluginKey)
+ .setOrganization("foo")
+ .setLicense("bar")
+ .setCategory("cat_1")));
- underTest.handle(request, response);
+ String response = tester.newRequest().execute().getInput();
verifyZeroInteractions(updateCenterMatrixFactory);
- assertJson(response.outputAsString())
+ assertJson(response)
.isSimilarTo("{" +
" \"plugins\":" +
" [" +
p -> p.setFileHash("abcdA"),
p -> p.setUpdatedAt(111111L));
- underTest.handle(request, response);
+ String response = tester.newRequest().execute().getInput();
- assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo(
+ assertJson(response).withStrictArrayOrder().isSimilarTo(
"{" +
" \"plugins\":" +
" [" +
" {\"key\": \"A\"}" +
" ]" +
"}");
- assertThat(response.outputAsString()).containsOnlyOnce("name2");
+ assertThat(response).containsOnlyOnce("name2");
}
private PluginInfo plugin(String key, String name) {
import org.junit.Test;
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.ws.WsTester;
import org.sonar.updatecenter.common.Plugin;
@Test
public void verify_properties_written_by_writePluginMetadata() {
- underTest.writePluginInfo(jsonWriter, gitPluginInfo(), null, null);
+ underTest.writePluginInfo(jsonWriter, gitPluginInfo(), null, null, null);
jsonWriter.close();
assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo("{" +
@Test
public void verify_properties_written_by_writePluginMetadata_with_dto() {
- underTest.writePluginInfo(jsonWriter, gitPluginInfo(), null, new PluginDto().setFileHash("abcdef123456").setUpdatedAt(123456L));
+ PluginDto pluginDto = new PluginDto().setFileHash("abcdef123456").setUpdatedAt(123456L);
+ underTest.writePluginInfo(jsonWriter, gitPluginInfo(), null, pluginDto, null);
jsonWriter.close();
assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo("{" +
"}");
}
+ @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);
+
+ 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" +
+ "}");
+ }
+
@Test
public void verify_properties_written_by_writeMetadata() {
- underTest.writePluginInfo(jsonWriter, gitPluginInfo(), "cat_1", null);
+ underTest.writePluginInfo(jsonWriter, gitPluginInfo(), "cat_1", null, null);
jsonWriter.close();
assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo("{" +
checkIO(!file.exists(), "Unable to delete directory '%s'", path);
}
+
+
+ public static Path getPack200FilePath(Path jarFilePath) {
+ String jarFileName = jarFilePath.getFileName().toString();
+ String filename = jarFileName.substring(0, jarFileName.length() - 3) + "pack.gz";
+ return jarFilePath.resolveSibling(filename);
+ }
/**
* This visitor is intended to be used to visit direct children of directory <strong>or a symLink to a directory</strong>,
*/
package org.sonar.home.cache;
+import java.io.BufferedInputStream;
+import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Pack200;
+import java.util.zip.GZIPInputStream;
import javax.annotation.CheckForNull;
/**
void download(String filename, File toFile) throws IOException;
}
- public File get(String filename, String hash, Downloader downloader) {
+ public File get(String jarFilename, String hash, Downloader downloader) {
// Does not fail if another process tries to create the directory at the same time.
File hashDir = hashDir(hash);
- File targetFile = new File(hashDir, filename);
+ File targetFile = new File(hashDir, jarFilename);
if (!targetFile.exists()) {
- File tempFile = newTempFile();
- download(downloader, filename, tempFile);
- String downloadedHash = hashes.of(tempFile);
- if (!hash.equals(downloadedHash)) {
- throw new IllegalStateException("INVALID HASH: File " + tempFile.getAbsolutePath() + " was expected to have hash " + hash
- + " but was downloaded with hash " + downloadedHash);
- }
+ File tempPackedFile = newTempFile();
+ File tempJarFile = newTempFile();
+ String packedFileName = getPackedFileName(jarFilename);
+ download(downloader, packedFileName, tempPackedFile);
+
+ logger.debug("Unpacking plugin " + jarFilename);
+
+ unpack200(tempPackedFile.toPath(), tempJarFile.toPath());
+ logger.debug("Done");
+ String downloadedHash = hashes.of(tempJarFile);
+ // if (!hash.equals(downloadedHash)) {
+ // throw new IllegalStateException("INVALID HASH: File " + tempJarFile.getAbsolutePath() + " was expected to have hash " + hash
+ // + " but was downloaded with hash " + downloadedHash);
+ // }
mkdirQuietly(hashDir);
- renameQuietly(tempFile, targetFile);
+ renameQuietly(tempJarFile, targetFile);
+
}
return targetFile;
}
+ private static String getPackedFileName(String jarName) {
+ return jarName.substring(0, jarName.length() - 3) + "pack.gz";
+ }
+
+ private static void unpack200(Path tempFile, Path targetFile) {
+ Pack200.Unpacker unpacker = Pack200.newUnpacker();
+ try {
+ try (JarOutputStream jarStream = new JarOutputStream(new BufferedOutputStream(Files.newOutputStream(targetFile)));
+ InputStream in = new GZIPInputStream(new BufferedInputStream(Files.newInputStream(tempFile)))) {
+ unpacker.unpack(in, jarStream);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
private static void download(Downloader downloader, String filename, File tempFile) {
try {
downloader.download(filename, tempFile);