private void loadQualityProfiles(ScannerReport.Metadata reportMetadata, Organization organization) {
checkQualityProfilesConsistency(reportMetadata, organization);
- analysisMetadata.setQProfilesByLanguage(reportMetadata.getQprofilesPerLanguage().values().stream()
+ analysisMetadata.setQProfilesByLanguage(reportMetadata.getQprofilesPerLanguageMap().values().stream()
.collect(toMap(
QProfile::getLanguage,
qp -> new QualityProfile(qp.getKey(), qp.getName(), qp.getLanguage(), new Date(qp.getRulesUpdatedAt())))));
- analysisMetadata.setScannerPluginsByKey(reportMetadata.getPluginsByKey().values().stream()
+ analysisMetadata.setScannerPluginsByKey(reportMetadata.getPluginsByKeyMap().values().stream()
.collect(toMap(
Plugin::getKey,
p -> new ScannerPlugin(p.getKey(), getBasePluginKey(p), p.getUpdatedAt()))));
* Check that the Quality profiles sent by scanner correctly relate to the project organization.
*/
private void checkQualityProfilesConsistency(ScannerReport.Metadata metadata, Organization organization) {
- List<String> profileKeys = metadata.getQprofilesPerLanguage().values().stream()
+ List<String> profileKeys = metadata.getQprofilesPerLanguageMap().values().stream()
.map(QProfile::getKey)
- .collect(toList(metadata.getQprofilesPerLanguage().size()));
+ .collect(toList(metadata.getQprofilesPerLanguageMap().size()));
try (DbSession dbSession = dbClient.openSession(false)) {
List<QProfileDto> profiles = dbClient.qualityProfileDao().selectByUuids(dbSession, profileKeys);
String badKeys = profiles.stream()
throw new UnsupportedOperationException();
}
+ @Override
+ public Collection<Plugin> getPluginInstances() {
+ throw new UnsupportedOperationException();
+ }
+
@Override
public boolean hasPlugin(String key) {
return pluginsMap.containsKey(key);
File jarTarget = new File(toDir, jarSource.getName());
FileUtils.copyFile(jarSource, jarTarget);
ZipUtils.unzip(jarSource, toDir, newLibFilter());
- return explodeFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir);
+ return explodeFromUnzippedDir(pluginInfo, jarTarget, toDir);
} catch (Exception e) {
throw new IllegalStateException(String.format(
"Fail to unzip plugin [%s] %s to %s", pluginInfo.getKey(), pluginInfo.getNonNullJarFile().getAbsolutePath(), toDir.getAbsolutePath()), e);
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.stream.Collectors;
import org.apache.commons.io.FileUtils;
import org.picocontainer.Startable;
import org.sonar.api.Plugin;
import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.platform.ExplodedPlugin;
+import org.sonar.core.platform.PluginClassLoader;
import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginLoader;
import org.sonar.core.platform.PluginRepository;
import org.sonar.server.platform.ServerFileSystem;
private static final String NOT_STARTED_YET = "not started yet";
private final ServerFileSystem fs;
- private final PluginLoader loader;
+ private final PluginClassLoader loader;
+ private final CePluginJarExploder cePluginJarExploder;
private final AtomicBoolean started = new AtomicBoolean(false);
// following fields are available after startup
private final Map<String, PluginInfo> pluginInfosByKeys = new HashMap<>();
private final Map<String, Plugin> pluginInstancesByKeys = new HashMap<>();
- public CePluginRepository(ServerFileSystem fs, PluginLoader loader) {
+ public CePluginRepository(ServerFileSystem fs, PluginClassLoader loader, CePluginJarExploder cePluginJarExploder) {
this.fs = fs;
this.loader = loader;
+ this.cePluginJarExploder = cePluginJarExploder;
}
@Override
Loggers.get(getClass()).info("Load plugins");
registerPluginsFromDir(fs.getInstalledBundledPluginsDir());
registerPluginsFromDir(fs.getInstalledExternalPluginsDir());
- pluginInstancesByKeys.putAll(loader.load(pluginInfosByKeys));
+ Map<String, ExplodedPlugin> explodedPluginsByKey = extractPlugins(pluginInfosByKeys);
+ pluginInstancesByKeys.putAll(loader.load(explodedPluginsByKey));
started.set(true);
}
}
}
+ private Map<String, ExplodedPlugin> extractPlugins(Map<String, PluginInfo> pluginsByKey) {
+ return pluginsByKey.values().stream()
+ .map(cePluginJarExploder::explode)
+ .collect(Collectors.toMap(ExplodedPlugin::getKey, p -> p));
+ }
+
@Override
public void stop() {
// close classloaders
return plugin;
}
+ @Override
+ public Collection<Plugin> getPluginInstances() {
+ return pluginInstancesByKeys.values();
+ }
+
@Override
public boolean hasPlugin(String key) {
checkState(started.get(), NOT_STARTED_YET);
import org.sonar.core.platform.Module;
import org.sonar.core.platform.PlatformEditionProvider;
import org.sonar.core.platform.PluginClassloaderFactory;
-import org.sonar.core.platform.PluginLoader;
+import org.sonar.core.platform.PluginClassLoader;
import org.sonar.core.util.UuidFactoryImpl;
import org.sonar.db.DBSessionsImpl;
import org.sonar.db.DaoModule;
// plugins
PluginClassloaderFactory.class,
CePluginJarExploder.class,
- PluginLoader.class,
+ PluginClassLoader.class,
CePluginRepository.class,
InstalledPluginReferentialFactory.class,
ComputeEngineExtensionInstaller.class,
package org.sonar.ce.container;
import java.io.File;
+import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
import org.junit.After;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import org.mockito.Mockito;
import org.sonar.api.Plugin;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginLoader;
+import org.sonar.core.platform.ExplodedPlugin;
+import org.sonar.core.platform.PluginClassLoader;
import org.sonar.server.platform.ServerFileSystem;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@Rule
public TemporaryFolder temp = new TemporaryFolder();
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
private ServerFileSystem fs = mock(ServerFileSystem.class, Mockito.RETURNS_DEEP_STUBS);
- private PluginLoader pluginLoader = new DumbPluginLoader();
- private CePluginRepository underTest = new CePluginRepository(fs, pluginLoader);
+ private PluginClassLoader pluginClassLoader = new DumbPluginClassLoader();
+ private CePluginJarExploder cePluginJarExploder = new CePluginJarExploder(fs);
+ private CePluginRepository underTest = new CePluginRepository(fs, pluginClassLoader, cePluginJarExploder);
@After
public void tearDown() {
}
@Test
- public void load_plugins() {
+ public void load_plugins() throws IOException {
String pluginKey = "test";
+ when(fs.getTempDir()).thenReturn(temp.newFolder());
when(fs.getInstalledExternalPluginsDir()).thenReturn(new File("src/test/plugins/sonar-test-plugin/target"));
underTest.start();
assertThat(underTest.getPluginInfos()).extracting("key").containsOnly(pluginKey);
assertThat(underTest.getPluginInfo(pluginKey).getKey()).isEqualTo(pluginKey);
assertThat(underTest.getPluginInstance(pluginKey)).isNotNull();
+ assertThat(underTest.getPluginInstances()).isNotEmpty();
assertThat(underTest.hasPlugin(pluginKey)).isTrue();
}
@Test
public void getPluginInfo_fails_if_plugin_does_not_exist() throws Exception {
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Plugin [foo] does not exist");
-
// empty folder
when(fs.getInstalledExternalPluginsDir()).thenReturn(temp.newFolder());
underTest.start();
- underTest.getPluginInfo("foo");
+ assertThatThrownBy(() -> underTest.getPluginInfo("foo"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Plugin [foo] does not exist");
}
@Test
public void getPluginInstance_fails_if_plugin_does_not_exist() throws Exception {
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("Plugin [foo] does not exist");
-
// empty folder
when(fs.getInstalledExternalPluginsDir()).thenReturn(temp.newFolder());
underTest.start();
- underTest.getPluginInstance("foo");
+ assertThatThrownBy(() -> underTest.getPluginInstance("foo"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Plugin [foo] does not exist");
}
@Test
public void getPluginInstance_throws_ISE_if_repo_is_not_started() {
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("not started yet");
-
- underTest.getPluginInstance("foo");
+ assertThatThrownBy(() -> underTest.getPluginInstance("foo"))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("not started yet");
}
@Test
public void getPluginInfo_throws_ISE_if_repo_is_not_started() {
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("not started yet");
-
- underTest.getPluginInfo("foo");
+ assertThatThrownBy(() -> underTest.getPluginInfo("foo"))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("not started yet");
}
@Test
public void hasPlugin_throws_ISE_if_repo_is_not_started() {
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("not started yet");
-
- underTest.hasPlugin("foo");
+ assertThatThrownBy(() -> underTest.hasPlugin("foo"))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("not started yet");
}
@Test
public void getPluginInfos_throws_ISE_if_repo_is_not_started() {
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("not started yet");
-
- underTest.getPluginInfos();
+ assertThatThrownBy(() -> underTest.getPluginInfos())
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("not started yet");
}
- private static class DumbPluginLoader extends PluginLoader {
+ private static class DumbPluginClassLoader extends PluginClassLoader {
- public DumbPluginLoader() {
- super(null, null);
+ public DumbPluginClassLoader() {
+ super(null);
}
/**
* Does nothing except returning the specified list of plugins
*/
@Override
- public Map<String, Plugin> load(Map<String, PluginInfo> infoByKeys) {
+ public Map<String, Plugin> load(Map<String, ExplodedPlugin> infoByKeys) {
Map<String, Plugin> result = new HashMap<>();
for (String pluginKey : infoByKeys.keySet()) {
result.put(pluginKey, mock(Plugin.class));
public class PluginDto {
/** Technical unique identifier, can't be null */
- private String uuid;
+ private String uuid = null;
/** Plugin key, unique, can't be null */
- private String kee;
+ private String kee = null;
/** Base plugin key, can be null */
- private String basePluginKey;
+ private String basePluginKey = null;
/** JAR file MD5 checksum, can't be null */
- private String fileHash;
+ private String fileHash = null;
+ // core feature or not
+ private Type type = null;
/** Time plugin was first installed */
- private long createdAt;
+ private long createdAt = 0L;
/** Time of last plugin update (=md5 change) */
- private long updatedAt;
+ private long updatedAt = 0L;
public String getUuid() {
return uuid;
return this;
}
+ public Type getType() {
+ return type;
+ }
+
+ public PluginDto setType(Type type) {
+ this.type = type;
+ return this;
+ }
+
+ public enum Type {
+ BUNDLED,
+ EXTERNAL
+ }
+
@Override
public String toString() {
return new ToStringBuilder(this)
kee,
base_plugin_key as basePluginKey,
file_hash as fileHash,
+ type,
created_at as createdAt,
updated_at as updatedAt
</sql>
kee,
base_plugin_key,
file_hash,
+ type,
created_at,
updated_at
) values (
#{kee,jdbcType=VARCHAR},
#{basePluginKey,jdbcType=VARCHAR},
#{fileHash,jdbcType=VARCHAR},
+ #{type,jdbcType=VARCHAR},
#{createdAt,jdbcType=TIMESTAMP},
#{updatedAt,jdbcType=TIMESTAMP}
)
update plugins set
base_plugin_key=#{basePluginKey,jdbcType=VARCHAR},
file_hash=#{fileHash,jdbcType=VARCHAR},
+ type=#{type,jdbcType=VARCHAR},
updated_at=#{updatedAt,jdbcType=BIGINT}
where
uuid=#{uuid,jdbcType=VARCHAR}
"BASE_PLUGIN_KEY" VARCHAR(200),
"FILE_HASH" VARCHAR(200) NOT NULL,
"CREATED_AT" BIGINT NOT NULL,
- "UPDATED_AT" BIGINT NOT NULL
+ "UPDATED_AT" BIGINT NOT NULL,
+ "TYPE" VARCHAR(10) NOT NULL
);
ALTER TABLE "PLUGINS" ADD CONSTRAINT "PK_PLUGINS" PRIMARY KEY("UUID");
CREATE UNIQUE INDEX "PLUGINS_KEY" ON "PLUGINS"("KEE");
*/
package org.sonar.db.plugin;
-import java.util.Optional;
import javax.annotation.Nullable;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.sonar.api.utils.System2;
import org.sonar.db.DbTester;
+import org.sonar.db.plugin.PluginDto.Type;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.db.plugin.PluginDto.Type.BUNDLED;
+import static org.sonar.db.plugin.PluginDto.Type.EXTERNAL;
public class PluginDaoTest {
@Test
public void selectByKey() {
- insertPlugin("a", "java", null, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 1500000000000L, 1600000000000L);
- insertPlugin("b", "javacustom", "java", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 1500000000000L, 1600000000000L);
+ insertPlugins();
assertThat(underTest.selectByKey(db.getSession(), "java2")).isEmpty();
- Optional<PluginDto> plugin = underTest.selectByKey(db.getSession(), "java");
- assertThat(plugin.isPresent()).isTrue();
- assertThat(plugin.get().getUuid()).isEqualTo("a");
- assertThat(plugin.get().getKee()).isEqualTo("java");
- assertThat(plugin.get().getBasePluginKey()).isNull();
- assertThat(plugin.get().getFileHash()).isEqualTo("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
- assertThat(plugin.get().getCreatedAt()).isEqualTo(1500000000000L);
- assertThat(plugin.get().getUpdatedAt()).isEqualTo(1600000000000L);
+ assertPlugin("java", "a", null, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", BUNDLED, 1500000000000L, 1600000000000L);
}
@Test
public void selectAll() {
- insertPlugin("a", "java", null, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 1500000000000L, 1600000000000L);
- insertPlugin("b", "javacustom", "java", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 1500000000000L, 1600000000000L);
-
+ insertPlugins();
assertThat(underTest.selectAll(db.getSession())).hasSize(2);
}
@Test
public void insert() {
- insertPlugin("a", "java", null, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 1500000000000L, 1600000000000L);
- insertPlugin("b", "javacustom", "java", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 1500000000000L, 1600000000000L);
+ insertPlugins();
underTest.insert(db.getSession(), new PluginDto()
.setUuid("c")
.setKee("javascript")
.setBasePluginKey("java")
.setFileHash("cccccccccccccccccccccccccccccccc")
+ .setType(EXTERNAL)
.setCreatedAt(1L)
.setUpdatedAt(2L));
- Optional<PluginDto> plugin = underTest.selectByKey(db.getSession(), "javascript");
- assertThat(plugin.isPresent()).isTrue();
- assertThat(plugin.get().getUuid()).isEqualTo("c");
- assertThat(plugin.get().getKee()).isEqualTo("javascript");
- assertThat(plugin.get().getBasePluginKey()).isEqualTo("java");
- assertThat(plugin.get().getFileHash()).isEqualTo("cccccccccccccccccccccccccccccccc");
- assertThat(plugin.get().getCreatedAt()).isEqualTo(1L);
- assertThat(plugin.get().getUpdatedAt()).isEqualTo(2L);
+ assertPlugin("javascript", "c", "java", "cccccccccccccccccccccccccccccccc", EXTERNAL, 1L, 2L);
}
@Test
public void update() {
- insertPlugin("a", "java", null, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 1500000000000L, 1600000000000L);
- insertPlugin("b", "javacustom", "java", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", 1500000000000L, 1600000000000L);
-
+ insertPlugins();
PluginDto plugin = underTest.selectByKey(db.getSession(), "java").get();
plugin.setBasePluginKey("foo");
plugin.setUpdatedAt(3L);
underTest.update(db.getSession(), plugin);
+ assertPlugin("java", "a", "foo", "abc", BUNDLED, 1500000000000L, 3L);
+ }
+
+ private void assertPlugin(String key, String uuid, @Nullable String basePluginKey, String fileHash, Type type, long cretedAt, long updatedAt) {
+ PluginDto plugin = underTest.selectByKey(db.getSession(), key).get();
+ assertThat(plugin.getUuid()).isEqualTo(uuid);
+ assertThat(plugin.getKee()).isEqualTo(key);
+ assertThat(plugin.getBasePluginKey()).isEqualTo(basePluginKey);
+ assertThat(plugin.getFileHash()).isEqualTo(fileHash);
+ assertThat(plugin.getType()).isEqualTo(type);
+ assertThat(plugin.getCreatedAt()).isEqualTo(cretedAt);
+ assertThat(plugin.getUpdatedAt()).isEqualTo(updatedAt);
+ }
- plugin = underTest.selectByKey(db.getSession(), "java").get();
- assertThat(plugin.getUuid()).isEqualTo("a");
- assertThat(plugin.getKee()).isEqualTo("java");
- assertThat(plugin.getBasePluginKey()).isEqualTo("foo");
- assertThat(plugin.getFileHash()).isEqualTo("abc");
- assertThat(plugin.getCreatedAt()).isEqualTo(1500000000000L);
- assertThat(plugin.getUpdatedAt()).isEqualTo(3L);
+ private void insertPlugins() {
+ insertPlugin("a", "java", null, "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", BUNDLED, 1500000000000L, 1600000000000L);
+ insertPlugin("b", "javacustom", "java", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", EXTERNAL, 1500000000000L, 1600000000000L);
}
- private void insertPlugin(String uuid, String key, @Nullable String basePluginKey, String fileHash, long createdAt, long updatedAt) {
+ private void insertPlugin(String uuid, String key, @Nullable String basePluginKey, String fileHash, Type type, long createdAt, long updatedAt) {
db.executeInsert("PLUGINS",
"uuid", uuid,
"kee", key,
"base_plugin_key", basePluginKey,
"file_hash", fileHash,
+ "type", type.name(),
"created_at", createdAt,
"updated_at", updatedAt);
db.commit();
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.platform.db.migration.version.v85;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.def.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.AddColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class AddTypeToPlugins extends DdlChange {
+ private static final String TABLE_NAME = "plugins";
+ private static final String COLUMN_NAME = "type";
+
+ public AddTypeToPlugins(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute(new AddColumnsBuilder(getDialect(), TABLE_NAME).addColumn(VarcharColumnDef.newVarcharColumnDefBuilder()
+ .setLimit(10)
+ .setIsNullable(true)
+ .setColumnName(COLUMN_NAME)
+ .build()).build());
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.platform.db.migration.version.v85;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.def.VarcharColumnDef;
+import org.sonar.server.platform.db.migration.sql.AlterColumnsBuilder;
+import org.sonar.server.platform.db.migration.step.DdlChange;
+
+public class AlterTypeInPluginNotNullable extends DdlChange {
+ private static final String TABLE_NAME = "plugins";
+ private static final String COLUMN_NAME = "type";
+
+ public AlterTypeInPluginNotNullable(Database db) {
+ super(db);
+ }
+
+ @Override
+ public void execute(Context context) throws SQLException {
+ context.execute(new AlterColumnsBuilder(getDialect(), TABLE_NAME).updateColumn(VarcharColumnDef.newVarcharColumnDefBuilder()
+ .setLimit(10)
+ .setIsNullable(false)
+ .setColumnName(COLUMN_NAME)
+ .build()).build());
+ }
+}
.add(4013, "add index on 'issue_key' for table 'issue_changes'", AddIndexOnIssueKeyForIssueChangesTable.class)
.add(4014, "add index on 'kee' for table 'issue_changes'", AddIndexOnKeeForIssueChangesTable.class)
.add(4015, "add index on 'project_uuid' for table 'issue_changes'", AddIndexOnProjectUuidOnIssueChangesTable.class)
+ .add(4016, "Add 'type' column to 'plugins' table", AddTypeToPlugins.class)
+ .add(4017, "Populate 'type' column in 'plugins' table", PopulateTypeInPlugins.class)
+ .add(4018, "Alter 'type' column in 'plugins' to not nullable", AlterTypeInPluginNotNullable.class)
;
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.platform.db.migration.version.v85;
+
+import java.sql.SQLException;
+import org.sonar.db.Database;
+import org.sonar.server.platform.db.migration.step.DataChange;
+
+public class PopulateTypeInPlugins extends DataChange {
+ public PopulateTypeInPlugins(Database db) {
+ super(db);
+ }
+
+ @Override
+ protected void execute(Context context) throws SQLException {
+ context.prepareUpsert("update plugins set type = 'EXTERNAL' where type is null")
+ .execute()
+ .commit();
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.platform.db.migration.version.v85;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.MigrationStep;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AddTypeToPluginsTest {
+ private static final String TABLE_NAME = "plugins";
+
+ @Rule
+ public CoreDbTester db = CoreDbTester.createForSchema(AddTypeToPluginsTest.class, "schema.sql");
+
+ private MigrationStep underTest = new AddTypeToPlugins(db.database());
+
+ @Test
+ public void add_column() throws SQLException {
+ addPlugin("1");
+ addPlugin("2");
+ underTest.execute();
+ db.assertColumnDefinition(TABLE_NAME, "type", Types.VARCHAR, 10, true);
+ assertThat(db.countRowsOfTable(TABLE_NAME)).isEqualTo(2);
+ }
+
+ private void addPlugin(String id) {
+ db.executeInsert(TABLE_NAME,
+ "uuid", "uuid" + id,
+ "kee", "kee" + id,
+ "base_plugin_key", "base" + id,
+ "file_hash", "hash" + id,
+ "created_at", 1L,
+ "updated_at", 2L);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.platform.db.migration.version.v85;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.MigrationStep;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class AlterTypeInPluginNotNullableTest {
+ private static final String TABLE_NAME = "plugins";
+
+ @Rule
+ public CoreDbTester db = CoreDbTester.createForSchema(AlterTypeInPluginNotNullableTest.class, "schema.sql");
+
+ private MigrationStep underTest = new AlterTypeInPluginNotNullable(db.database());
+
+ @Test
+ public void add_column() throws SQLException {
+ addPlugin("1");
+ addPlugin("2");
+ underTest.execute();
+ db.assertColumnDefinition(TABLE_NAME, "type", Types.VARCHAR, 10, false);
+ assertThat(db.countRowsOfTable(TABLE_NAME)).isEqualTo(2);
+ }
+
+ private void addPlugin(String id) {
+ db.executeInsert(TABLE_NAME,
+ "uuid", "uuid" + id,
+ "kee", "kee" + id,
+ "base_plugin_key", "base" + id,
+ "file_hash", "hash" + id,
+ "type", "BUNDLED",
+ "created_at", 1L,
+ "updated_at", 2L);
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.platform.db.migration.version.v85;
+
+import java.sql.SQLException;
+import java.sql.Types;
+import javax.annotation.Nullable;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.db.CoreDbTester;
+import org.sonar.server.platform.db.migration.step.MigrationStep;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PopulateTypeInPluginsTest {
+ private static final String TABLE_NAME = "plugins";
+
+ @Rule
+ public CoreDbTester db = CoreDbTester.createForSchema(PopulateTypeInPluginsTest.class, "schema.sql");
+
+ private MigrationStep underTest = new PopulateTypeInPlugins(db.database());
+
+ @Test
+ public void add_column() throws SQLException {
+ addPlugin("1", null);
+ addPlugin("2", null);
+ addPlugin("3", "BUNDLED");
+
+ underTest.execute();
+ db.assertColumnDefinition(TABLE_NAME, "type", Types.VARCHAR, 10, true);
+ assertThat(db.countRowsOfTable(TABLE_NAME)).isEqualTo(3);
+ assertThat(db.select("select type as \"TYPE\" from plugins order by uuid").stream().map(r -> r.get("TYPE"))).containsExactly("EXTERNAL", "EXTERNAL", "BUNDLED");
+ }
+
+ private void addPlugin(String id, @Nullable String type) {
+ db.executeInsert(TABLE_NAME,
+ "uuid", "uuid" + id,
+ "kee", "kee" + id,
+ "base_plugin_key", "base" + id,
+ "file_hash", "hash" + id,
+ "type", type,
+ "created_at", 1L,
+ "updated_at", 2L);
+ }
+}
--- /dev/null
+CREATE TABLE "PLUGINS"(
+ "UUID" VARCHAR(40) NOT NULL,
+ "KEE" VARCHAR(200) NOT NULL,
+ "BASE_PLUGIN_KEY" VARCHAR(200),
+ "FILE_HASH" VARCHAR(200) NOT NULL,
+ "CREATED_AT" BIGINT NOT NULL,
+ "UPDATED_AT" BIGINT NOT NULL
+);
+ALTER TABLE "PLUGINS" ADD CONSTRAINT "PK_PLUGINS" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "PLUGINS_KEY" ON "PLUGINS"("KEE");
--- /dev/null
+CREATE TABLE "PLUGINS"(
+ "UUID" VARCHAR(40) NOT NULL,
+ "KEE" VARCHAR(200) NOT NULL,
+ "BASE_PLUGIN_KEY" VARCHAR(200),
+ "FILE_HASH" VARCHAR(200) NOT NULL,
+ "TYPE" VARCHAR(10),
+ "CREATED_AT" BIGINT NOT NULL,
+ "UPDATED_AT" BIGINT NOT NULL
+);
+ALTER TABLE "PLUGINS" ADD CONSTRAINT "PK_PLUGINS" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "PLUGINS_KEY" ON "PLUGINS"("KEE");
--- /dev/null
+CREATE TABLE "PLUGINS"(
+ "UUID" VARCHAR(40) NOT NULL,
+ "KEE" VARCHAR(200) NOT NULL,
+ "BASE_PLUGIN_KEY" VARCHAR(200),
+ "FILE_HASH" VARCHAR(200) NOT NULL,
+ "TYPE" VARCHAR(10),
+ "CREATED_AT" BIGINT NOT NULL,
+ "UPDATED_AT" BIGINT NOT NULL
+);
+ALTER TABLE "PLUGINS" ADD CONSTRAINT "PK_PLUGINS" PRIMARY KEY("UUID");
+CREATE UNIQUE INDEX "PLUGINS_KEY" ON "PLUGINS"("KEE");
return pluginsMap.get(key);
}
+ @Override
+ public Collection<Plugin> getPluginInstances() {
+ return pluginsMap.values();
+ }
+
@Override
public boolean hasPlugin(String key) {
return pluginsMap.containsKey(key);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2020 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;
- }
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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);
+ }
+}
*/
package org.sonar.server.plugins;
-import com.google.common.base.Optional;
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
+import java.util.Optional;
import org.apache.commons.io.FileUtils;
import org.picocontainer.Startable;
import org.sonar.api.utils.HttpDownloader;
}
}
- public List<String> getDownloadedPluginFilenames() {
- List<String> names = new ArrayList<>();
- for (File file : listPlugins(this.downloadDir)) {
- names.add(file.getName());
- }
- return names;
- }
-
/**
* @return the list of download plugins as {@link PluginInfo} instances
*/
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2020 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);
- }
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 static java.util.Objects.requireNonNull;
+
+@Immutable
+public class PluginFilesAndMd5 {
+ private final FileAndMd5 loadedJar;
+ @Nullable
+ private final FileAndMd5 compressedJar;
+
+ public PluginFilesAndMd5(FileAndMd5 loadedJar, @Nullable FileAndMd5 compressedJar) {
+ this.loadedJar = requireNonNull(loadedJar);
+ this.compressedJar = compressedJar;
+ }
+
+ public FileAndMd5 getLoadedJar() {
+ return loadedJar;
+ }
+
+ @Nullable
+ public FileAndMd5 getCompressedJar() {
+ return compressedJar;
+ }
+
+ @Immutable
+ public static 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;
+ }
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import org.apache.commons.io.FileUtils;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.server.platform.ServerFileSystem;
+import org.sonar.updatecenter.common.Version;
+
+import static java.lang.String.format;
+import static org.apache.commons.io.FileUtils.moveFile;
+import static org.sonar.core.util.FileUtils.deleteQuietly;
+import static org.sonar.server.plugins.PluginType.BUNDLED;
+import static org.sonar.server.plugins.PluginType.EXTERNAL;
+
+public class PluginJarLoader {
+ private static final Logger LOG = Loggers.get(PluginJarLoader.class);
+
+ // List of plugins that are silently removed if installed
+ private static final Set<String> DEFAULT_BLACKLISTED_PLUGINS = ImmutableSet.of("scmactivity", "issuesreport", "genericcoverage");
+ // List of plugins that should prevent the server to finish its startup
+ private static final Set<String> FORBIDDEN_COMPATIBLE_PLUGINS = ImmutableSet.of("sqale", "report", "views");
+
+ private final ServerFileSystem fs;
+ private final SonarRuntime runtime;
+ private final Set<String> blacklistedPluginKeys;
+
+ public PluginJarLoader(ServerFileSystem fs, SonarRuntime runtime) {
+ this(fs, runtime, DEFAULT_BLACKLISTED_PLUGINS);
+ }
+
+ PluginJarLoader(ServerFileSystem fs, SonarRuntime runtime, Set<String> blacklistedPluginKeys) {
+ this.fs = fs;
+ this.runtime = runtime;
+ this.blacklistedPluginKeys = blacklistedPluginKeys;
+ }
+
+ /**
+ * Load the plugins that are located in lib/extensions and extensions/plugins. Blacklisted plugins are deleted.
+ */
+ public Collection<ServerPluginInfo> loadPlugins() {
+ Map<String, ServerPluginInfo> bundledPluginsByKey = new LinkedHashMap<>();
+ for (ServerPluginInfo bundled : getBundledPluginsMetadata()) {
+ failIfContains(bundledPluginsByKey, bundled, plugin ->
+ MessageException.of(format("Found two versions of the plugin %s [%s] in the directory %s. Please remove one of %s or %s.",
+ bundled.getName(), bundled.getKey(), getRelativeDir(fs.getInstalledBundledPluginsDir()), bundled.getNonNullJarFile().getName(), plugin.getNonNullJarFile().getName())));
+ bundledPluginsByKey.put(bundled.getKey(), bundled);
+ }
+
+ Map<String, ServerPluginInfo> externalPluginsByKey = new LinkedHashMap<>();
+ for (ServerPluginInfo external : getExternalPluginsMetadata()) {
+ failIfContains(bundledPluginsByKey, external, plugin ->
+ MessageException.of(format("Found a plugin '%s' in the directory %s with the same key [%s] as a bundled plugin '%s'. Please remove %s.",
+ external.getName(), getRelativeDir(fs.getInstalledExternalPluginsDir()), external.getKey(), plugin.getName(), external.getNonNullJarFile().getName())));
+ failIfContains(externalPluginsByKey, external, plugin ->
+ MessageException.of(format("Found two versions of the plugin '%s' [%s] in the directory %s. Please remove %s or %s.", external.getName(), external.getKey(),
+ getRelativeDir(fs.getInstalledExternalPluginsDir()), external.getNonNullJarFile().getName(), plugin.getNonNullJarFile().getName())));
+ externalPluginsByKey.put(external.getKey(), external);
+ }
+
+ for (PluginInfo downloaded : getDownloadedPluginsMetadata()) {
+ failIfContains(bundledPluginsByKey, downloaded, plugin ->
+ MessageException.of(format("Fail to update plugin: %s. Bundled plugin with same key already exists: %s. Move or delete plugin from %s directory",
+ plugin.getName(), plugin.getKey(), getRelativeDir(fs.getDownloadedPluginsDir()))));
+
+ ServerPluginInfo installedPlugin;
+ if (externalPluginsByKey.containsKey(downloaded.getKey())) {
+ deleteQuietly(externalPluginsByKey.get(downloaded.getKey()).getNonNullJarFile());
+ installedPlugin = moveDownloadedPluginToExtensions(downloaded);
+ LOG.info("Plugin {} [{}] updated to version {}", installedPlugin.getName(), installedPlugin.getKey(), installedPlugin.getVersion());
+ } else {
+ installedPlugin = moveDownloadedPluginToExtensions(downloaded);
+ LOG.info("Plugin {} [{}] installed", installedPlugin.getName(), installedPlugin.getKey());
+ }
+
+ externalPluginsByKey.put(downloaded.getKey(), installedPlugin);
+ }
+
+ Map<String, ServerPluginInfo> plugins = new HashMap<>(externalPluginsByKey.size() + bundledPluginsByKey.size());
+ plugins.putAll(externalPluginsByKey);
+ plugins.putAll(bundledPluginsByKey);
+
+ unloadIncompatiblePlugins(plugins);
+
+ return plugins.values();
+ }
+
+ /**
+ * Removes the plugins that are not compatible with current environment.
+ */
+ private static void unloadIncompatiblePlugins(Map<String, ServerPluginInfo> pluginsByKey) {
+ // loop as long as the previous loop ignored some plugins. That allows to support dependencies
+ // on many levels, for example D extends C, which extends B, which requires A. If A is not installed,
+ // then B, C and D must be ignored. That's not possible to achieve this algorithm with a single iteration over plugins.
+ Set<String> removedKeys = new HashSet<>();
+ do {
+ removedKeys.clear();
+ for (PluginInfo plugin : pluginsByKey.values()) {
+ if (!isCompatible(plugin, pluginsByKey)) {
+ removedKeys.add(plugin.getKey());
+ }
+ }
+ for (String removedKey : removedKeys) {
+ pluginsByKey.remove(removedKey);
+ }
+ } while (!removedKeys.isEmpty());
+ }
+
+ @VisibleForTesting
+ static boolean isCompatible(PluginInfo plugin, Map<String, ServerPluginInfo> allPluginsByKeys) {
+ if (!Strings.isNullOrEmpty(plugin.getBasePlugin()) && !allPluginsByKeys.containsKey(plugin.getBasePlugin())) {
+ // it extends a plugin that is not installed
+ LOG.warn("Plugin {} [{}] is ignored because its base plugin [{}] is not installed", plugin.getName(), plugin.getKey(), plugin.getBasePlugin());
+ return false;
+ }
+
+ for (PluginInfo.RequiredPlugin requiredPlugin : plugin.getRequiredPlugins()) {
+ PluginInfo installedRequirement = allPluginsByKeys.get(requiredPlugin.getKey());
+ if (installedRequirement == null) {
+ // it requires a plugin that is not installed
+ LOG.warn("Plugin {} [{}] is ignored because the required plugin [{}] is not installed", plugin.getName(), plugin.getKey(), requiredPlugin.getKey());
+ return false;
+ }
+ Version installedRequirementVersion = installedRequirement.getVersion();
+ if (installedRequirementVersion != null && requiredPlugin.getMinimalVersion().compareToIgnoreQualifier(installedRequirementVersion) > 0) {
+ // it requires a more recent version
+ LOG.warn("Plugin {} [{}] is ignored because the version {} of required plugin [{}] is not installed", plugin.getName(), plugin.getKey(),
+ requiredPlugin.getMinimalVersion(), requiredPlugin.getKey());
+ return false;
+ }
+ }
+ return true;
+ }
+
+ private static String getRelativeDir(File dir) {
+ Path parent = dir.toPath().getParent().getParent();
+ return parent.relativize(dir.toPath()).toString();
+ }
+
+ private static void failIfContains(Map<String, ? extends PluginInfo> map, PluginInfo value, Function<PluginInfo, RuntimeException> msg) {
+ PluginInfo pluginInfo = map.get(value.getKey());
+ if (pluginInfo != null) {
+ throw msg.apply(pluginInfo);
+ }
+ }
+
+ private List<ServerPluginInfo> getBundledPluginsMetadata() {
+ return loadPluginsFromDir(fs.getInstalledBundledPluginsDir(), jar -> ServerPluginInfo.create(jar, BUNDLED));
+ }
+
+ private List<ServerPluginInfo> getExternalPluginsMetadata() {
+ return loadPluginsFromDir(fs.getInstalledExternalPluginsDir(), jar -> ServerPluginInfo.create(jar, EXTERNAL));
+ }
+
+ private List<PluginInfo> getDownloadedPluginsMetadata() {
+ return loadPluginsFromDir(fs.getDownloadedPluginsDir(), PluginInfo::create);
+ }
+
+ private ServerPluginInfo moveDownloadedPluginToExtensions(PluginInfo pluginInfo) {
+ File destDir = fs.getInstalledExternalPluginsDir();
+ File destFile = new File(destDir, pluginInfo.getNonNullJarFile().getName());
+ if (destFile.exists()) {
+ deleteQuietly(destFile);
+ }
+
+ movePlugin(pluginInfo.getNonNullJarFile(), destFile);
+ return ServerPluginInfo.create(destFile, EXTERNAL);
+ }
+
+ private static void movePlugin(File sourcePluginFile, File destPluginFile) {
+ try {
+ moveFile(sourcePluginFile, destPluginFile);
+ } catch (IOException e) {
+ throw new IllegalStateException(format("Fail to move plugin: %s to %s", sourcePluginFile.getAbsolutePath(), destPluginFile.getAbsolutePath()), e);
+ }
+ }
+
+ private <T extends PluginInfo> List<T> loadPluginsFromDir(File pluginsDir, Function<File, T> f) {
+ return listJarFiles(pluginsDir).stream()
+ .map(f)
+ .filter(this::checkPluginInfo)
+ .collect(Collectors.toList());
+ }
+
+ private boolean checkPluginInfo(PluginInfo info) {
+ String pluginKey = info.getKey();
+ if (blacklistedPluginKeys.contains(pluginKey)) {
+ LOG.warn("Plugin {} [{}] is blacklisted and is being uninstalled", info.getName(), pluginKey);
+ deleteQuietly(info.getNonNullJarFile());
+ return false;
+ }
+ if (FORBIDDEN_COMPATIBLE_PLUGINS.contains(pluginKey)) {
+ throw MessageException.of(String.format("Plugin '%s' is no longer compatible with this version of SonarQube", pluginKey));
+ }
+
+ if (Strings.isNullOrEmpty(info.getMainClass()) && Strings.isNullOrEmpty(info.getBasePlugin())) {
+ LOG.warn("Plugin {} [{}] is ignored because entry point class is not defined", info.getName(), info.getKey());
+ return false;
+ }
+
+ if (!info.isCompatibleWith(runtime.getApiVersion().toString())) {
+ throw MessageException.of(format("Plugin %s [%s] requires at least SonarQube %s", info.getName(), info.getKey(), info.getMinimalSqVersion()));
+ }
+ return true;
+ }
+
+ private static Collection<File> listJarFiles(File dir) {
+ if (dir.exists()) {
+ return FileUtils.listFiles(dir, new String[] {"jar"}, false);
+ }
+ return Collections.emptyList();
+ }
+
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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;
+
+public enum PluginType {
+ EXTERNAL, BUNDLED
+}
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
import org.apache.commons.io.FileUtils;
import org.picocontainer.Startable;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.util.stream.MoreCollectors;
import org.sonar.server.platform.ServerFileSystem;
import static java.lang.String.format;
import static org.apache.commons.io.FileUtils.forceMkdir;
+import static org.apache.commons.io.FileUtils.moveFileToDirectory;
+import static org.sonar.server.plugins.PluginType.EXTERNAL;
public class PluginUninstaller implements Startable {
+ private static final Logger LOG = Loggers.get(PluginUninstaller.class);
private static final String PLUGIN_EXTENSION = "jar";
- private final ServerPluginRepository serverPluginRepository;
- private final File uninstallDir;
- public PluginUninstaller(ServerPluginRepository serverPluginRepository, ServerFileSystem fs) {
- this.serverPluginRepository = serverPluginRepository;
- this.uninstallDir = fs.getUninstalledPluginsDir();
- }
+ private final ServerFileSystem fs;
+ private final ServerPluginRepository pluginRepository;
- private static Collection<File> listJarFiles(File dir) {
- if (dir.exists()) {
- return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false);
- }
- return Collections.emptyList();
+ public PluginUninstaller(ServerFileSystem fs, ServerPluginRepository pluginRepository) {
+ this.fs = fs;
+ this.pluginRepository = pluginRepository;
}
@Override
public void start() {
try {
- forceMkdir(uninstallDir);
+ forceMkdir(fs.getUninstalledPluginsDir());
} catch (IOException e) {
- throw new IllegalStateException("Fail to create the directory: " + uninstallDir, e);
+ throw new IllegalStateException("Fail to create the directory: " + fs.getUninstalledPluginsDir(), e);
}
}
// Nothing to do
}
+ /**
+ * Uninstall a plugin and its dependents
+ */
public void uninstall(String pluginKey) {
- ensurePluginIsInstalled(pluginKey);
- serverPluginRepository.uninstall(pluginKey, uninstallDir);
+ if (!pluginRepository.hasPlugin(pluginKey) || pluginRepository.getPlugin(pluginKey).getType() != EXTERNAL) {
+ throw new IllegalArgumentException(format("Plugin [%s] is not installed", pluginKey));
+ }
+
+ Set<String> uninstallKeys = new HashSet<>();
+ uninstallKeys.add(pluginKey);
+ appendDependentPluginKeys(pluginKey, uninstallKeys);
+
+ for (String uninstallKey : uninstallKeys) {
+ PluginInfo info = pluginRepository.getPluginInfo(uninstallKey);
+ // we don't check type because the dependent of an external plugin should never be a bundled plugin!
+ uninstall(info.getKey(), info.getName(), info.getNonNullJarFile().getName());
+ }
}
public void cancelUninstalls() {
- serverPluginRepository.cancelUninstalls(uninstallDir);
+ for (File file : listJarFiles(fs.getUninstalledPluginsDir())) {
+ try {
+ moveFileToDirectory(file, fs.getInstalledExternalPluginsDir(), false);
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to cancel plugin uninstalls", e);
+ }
+ }
}
/**
* @return the list of plugins to be uninstalled as {@link PluginInfo} instances
*/
public Collection<PluginInfo> getUninstalledPlugins() {
- return listJarFiles(uninstallDir)
- .stream()
+ return listJarFiles(fs.getUninstalledPluginsDir()).stream()
.map(PluginInfo::create)
.collect(MoreCollectors.toList());
}
- private void ensurePluginIsInstalled(String key) {
- if (!serverPluginRepository.hasPlugin(key)) {
- throw new IllegalArgumentException(format("Plugin [%s] is not installed", key));
+ private static Collection<File> listJarFiles(File dir) {
+ if (dir.exists()) {
+ return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false);
+ }
+ return Collections.emptyList();
+ }
+
+ private void uninstall(String key, String name, String fileName) {
+ try {
+ if (!getPluginFile(fileName).exists()) {
+ LOG.info("Plugin already uninstalled: {} [{}]", name, key);
+ return;
+ }
+
+ LOG.info("Uninstalling plugin {} [{}]", name, key);
+
+ File masterFile = getPluginFile(fileName);
+ moveFileToDirectory(masterFile, fs.getUninstalledPluginsDir(), true);
+ } catch (IOException e) {
+ throw new IllegalStateException(format("Fail to uninstall plugin %s [%s]", name, key), e);
+ }
+ }
+
+ private File getPluginFile(String fileName) {
+ // just to be sure that file is located in from extensions/plugins
+ return new File(fs.getInstalledExternalPluginsDir(), fileName);
+ }
+
+ private void appendDependentPluginKeys(String pluginKey, Set<String> appendTo) {
+ for (PluginInfo otherPlugin : pluginRepository.getPluginInfos()) {
+ if (otherPlugin.getKey().equals(pluginKey)) {
+ continue;
+ }
+
+ for (PluginInfo.RequiredPlugin requirement : otherPlugin.getRequiredPlugins()) {
+ if (requirement.getKey().equals(pluginKey)) {
+ appendTo.add(otherPlugin.getKey());
+ appendDependentPluginKeys(otherPlugin.getKey(), appendTo);
+ }
+ }
}
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 javax.annotation.CheckForNull;
+import javax.annotation.Nullable;
+import org.sonar.api.Plugin;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5;
+
+public class ServerPlugin {
+ private final PluginInfo pluginInfo;
+ 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, @Nullable FileAndMd5 compressed, ClassLoader classloader) {
+ this.pluginInfo = pluginInfo;
+ this.type = type;
+ this.instance = instance;
+ this.jar = jar;
+ this.compressed = compressed;
+ this.classloader = classloader;
+ }
+
+ public PluginInfo getPluginInfo() {
+ return pluginInfo;
+ }
+
+ public Plugin getInstance() {
+ return instance;
+ }
+
+ public PluginType getType() {
+ return type;
+ }
+
+ public FileAndMd5 getJar() {
+ return jar;
+ }
+
+ @CheckForNull
+ public FileAndMd5 getCompressed() {
+ return compressed;
+ }
+
+ public ClassLoader getClassloader() {
+ return classloader;
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.util.Objects;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.updatecenter.common.PluginManifest;
+
+public class ServerPluginInfo extends PluginInfo {
+ private PluginType type;
+
+ public ServerPluginInfo(String key) {
+ super(key);
+ }
+
+ public static ServerPluginInfo create(File jarFile, PluginType type) {
+ try {
+ PluginManifest manifest = new PluginManifest(jarFile);
+ ServerPluginInfo serverPluginInfo = new ServerPluginInfo(manifest.getKey());
+ serverPluginInfo.fillFields(jarFile, manifest, type);
+ return serverPluginInfo;
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to extract plugin metadata from file: " + jarFile, e);
+ }
+ }
+
+ private void fillFields(File jarFile, PluginManifest manifest, PluginType type) {
+ super.fillFields(jarFile, manifest);
+ setType(type);
+ }
+
+ public PluginType getType() {
+ return type;
+ }
+
+ public ServerPluginInfo setType(PluginType type) {
+ this.type = type;
+ return this;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ if (!super.equals(o)) {
+ return false;
+ }
+ ServerPluginInfo that = (ServerPluginInfo) o;
+ return type == that.type;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(super.hashCode(), type);
+ }
+}
import org.sonar.api.server.ServerSide;
import org.sonar.api.utils.ZipUtils;
import org.sonar.core.platform.ExplodedPlugin;
+import org.sonar.core.platform.PluginClassLoader;
import org.sonar.core.platform.PluginInfo;
import org.sonar.core.platform.PluginJarExploder;
import org.sonar.server.platform.ServerFileSystem;
@ServerSide
public class ServerPluginJarExploder extends PluginJarExploder {
private final ServerFileSystem fs;
- private final PluginFileSystem pluginFileSystem;
- public ServerPluginJarExploder(ServerFileSystem fs, PluginFileSystem pluginFileSystem) {
+ public ServerPluginJarExploder(ServerFileSystem fs) {
this.fs = fs;
- this.pluginFileSystem = pluginFileSystem;
}
/**
* JAR files of directory extensions/plugins can be moved when server is up and plugins are uninstalled.
* For this reason these files must not be locked by classloaders. They are copied to the directory
- * web/deploy/plugins in order to be loaded by {@link org.sonar.core.platform.PluginLoader}.
+ * web/deploy/plugins in order to be loaded by {@link PluginClassLoader}.
*/
@Override
- public ExplodedPlugin explode(PluginInfo pluginInfo) {
- File toDir = new File(fs.getDeployedPluginsDir(), pluginInfo.getKey());
+ public ExplodedPlugin explode(PluginInfo plugin) {
+ File toDir = new File(fs.getDeployedPluginsDir(), plugin.getKey());
try {
forceMkdir(toDir);
org.sonar.core.util.FileUtils.cleanDirectory(toDir);
- File jarSource = pluginInfo.getNonNullJarFile();
- File jarTarget = new File(toDir, jarSource.getName());
+ File jarTarget = new File(toDir, plugin.getNonNullJarFile().getName());
- FileUtils.copyFile(jarSource, jarTarget);
- ZipUtils.unzip(jarSource, toDir, newLibFilter());
- ExplodedPlugin explodedPlugin = explodeFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir);
- pluginFileSystem.addInstalledPlugin(pluginInfo, jarTarget);
- return explodedPlugin;
+ FileUtils.copyFile(plugin.getNonNullJarFile(), jarTarget);
+ ZipUtils.unzip(plugin.getNonNullJarFile(), toDir, newLibFilter());
+ return explodeFromUnzippedDir(plugin, jarTarget, toDir);
} catch (Exception e) {
throw new IllegalStateException(String.format(
- "Fail to unzip plugin [%s] %s to %s", pluginInfo.getKey(), pluginInfo.getNonNullJarFile().getAbsolutePath(), toDir.getAbsolutePath()), e);
+ "Fail to unzip plugin [%s] %s to %s", plugin.getKey(), plugin.getNonNullJarFile().getAbsolutePath(), toDir.getAbsolutePath()), e);
}
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.picocontainer.Startable;
+import org.sonar.api.Plugin;
+import org.sonar.api.utils.log.Logger;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.platform.ExplodedPlugin;
+import org.sonar.core.platform.PluginClassLoader;
+import org.sonar.core.platform.PluginJarExploder;
+
+/**
+ * Entry point to install and load plugins on server startup. It manages
+ * <ul>
+ * <li>installation of new plugins (effective after server startup)</li>
+ * <li>un-installation of plugins (effective after server startup)</li>
+ * <li>cancel pending installations/un-installations</li>
+ * <li>instantiation of plugin entry-points</li>
+ * </ul>
+ */
+public class ServerPluginManager implements Startable {
+ private static final Logger LOG = Loggers.get(ServerPluginManager.class);
+
+ 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) {
+ this.pluginClassLoader = pluginClassLoader;
+ this.pluginJarExploder = pluginJarExploder;
+ this.pluginJarLoader = pluginJarLoader;
+ this.pluginCompressor = pluginCompressor;
+ this.pluginRepository = pluginRepository;
+ }
+
+ @Override
+ public void start() {
+ Collection<ServerPluginInfo> loadedPlugins = pluginJarLoader.loadPlugins();
+ logInstalledPlugins(loadedPlugins);
+ 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);
+ pluginRepository.addPlugins(plugins);
+ }
+
+ private static Map<String, PluginType> getTypesByKey(Collection<ServerPluginInfo> loadedPlugins) {
+ return loadedPlugins.stream().collect(Collectors.toMap(ServerPluginInfo::getKey, ServerPluginInfo::getType));
+ }
+
+ @Override
+ public void stop() {
+ pluginClassLoader.unload(pluginRepository.getPluginInstances());
+ }
+
+ private static void logInstalledPlugins(Collection<ServerPluginInfo> plugins) {
+ plugins.stream().sorted().forEach(plugin -> LOG.info("Deploy plugin {} / {} / {}", plugin.getName(), plugin.getVersion(), plugin.getImplementationBuild()));
+ }
+
+ private Collection<ExplodedPlugin> extractPlugins(Collection<ServerPluginInfo> plugins) {
+ return plugins.stream().map(pluginJarExploder::explode).collect(Collectors.toList());
+ }
+
+ private List<ServerPlugin> compressAndCreateServerPlugins(Collection<ExplodedPlugin> explodedPlugins, Map<String, Plugin> instancesByKey, Map<String, PluginType> typseByKey) {
+ 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()));
+ }
+ return plugins;
+ }
+}
*/
package org.sonar.server.plugins;
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Joiner;
-import com.google.common.base.Strings;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.ImmutableSet;
-import com.google.common.collect.Ordering;
-import java.io.File;
-import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.Optional;
+import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
-import org.apache.commons.io.FileUtils;
-import org.picocontainer.Startable;
import org.sonar.api.Plugin;
-import org.sonar.api.SonarRuntime;
-import org.sonar.api.utils.MessageException;
-import org.sonar.api.utils.log.Logger;
-import org.sonar.api.utils.log.Loggers;
import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginLoader;
import org.sonar.core.platform.PluginRepository;
-import org.sonar.server.platform.ServerFileSystem;
-import org.sonar.updatecenter.common.Version;
import static com.google.common.base.Preconditions.checkArgument;
-import static com.google.common.base.Preconditions.checkState;
import static java.lang.String.format;
-import static org.apache.commons.io.FileUtils.moveFile;
-import static org.apache.commons.io.FileUtils.moveFileToDirectory;
-import static org.sonar.core.util.FileUtils.deleteQuietly;
-/**
- * Entry point to install and load plugins on server startup. It manages
- * <ul>
- * <li>installation of new plugins (effective after server startup)</li>
- * <li>un-installation of plugins (effective after server startup)</li>
- * <li>cancel pending installations/un-installations</li>
- * <li>instantiation of plugin entry-points</li>
- * </ul>
- */
-public class ServerPluginRepository implements PluginRepository, Startable {
-
- private static final Logger LOG = Loggers.get(ServerPluginRepository.class);
- private static final String[] JAR_FILE_EXTENSIONS = new String[] {"jar"};
- // List of plugins that are silently removed if installed
- private static final Set<String> DEFAULT_BLACKLISTED_PLUGINS = ImmutableSet.of("scmactivity", "issuesreport", "genericcoverage");
- // List of plugins that should prevent the server to finish its startup
- private static final Set<String> FORBIDDEN_COMPATIBLE_PLUGINS = ImmutableSet.of("sqale", "report", "views");
- private static final Joiner SLASH_JOINER = Joiner.on(" / ").skipNulls();
- private static final String NOT_STARTED_YET = "not started yet";
-
- private final SonarRuntime runtime;
- private final ServerFileSystem fs;
- private final PluginLoader loader;
- private final AtomicBoolean started = new AtomicBoolean(false);
- private Set<String> blacklistedPluginKeys = DEFAULT_BLACKLISTED_PLUGINS;
-
- // following fields are available after startup
- private final Map<String, PluginInfo> pluginInfosByKeys = new HashMap<>();
- private final Map<String, Plugin> pluginInstancesByKeys = new HashMap<>();
+public class ServerPluginRepository implements PluginRepository {
+ private final Map<String, ServerPlugin> pluginByKey = new HashMap<>();
private final Map<ClassLoader, String> keysByClassLoader = new HashMap<>();
- public ServerPluginRepository(SonarRuntime runtime, ServerFileSystem fs, PluginLoader loader) {
- this.runtime = runtime;
- this.fs = fs;
- this.loader = loader;
- }
-
- @VisibleForTesting
- void setBlacklistedPluginKeys(Set<String> keys) {
- this.blacklistedPluginKeys = keys;
- }
-
- @Override
- public void start() {
- loadPreInstalledPlugins();
- moveDownloadedPlugins();
- unloadIncompatiblePlugins();
- logInstalledPlugins();
- loadInstances();
- started.set(true);
+ public void addPlugins(List<ServerPlugin> plugins) {
+ pluginByKey.putAll(plugins.stream().collect(Collectors.toMap(p -> p.getPluginInfo().getKey(), p -> p)));
+ for (ServerPlugin p : plugins) {
+ keysByClassLoader.put(p.getClassloader(), p.getPluginInfo().getKey());
+ }
}
- @Override
- public void stop() {
- // close classloaders
- loader.unload(pluginInstancesByKeys.values());
- pluginInstancesByKeys.clear();
- pluginInfosByKeys.clear();
- keysByClassLoader.clear();
- started.set(true);
+ public void addPlugin(ServerPlugin plugin) {
+ pluginByKey.put(plugin.getPluginInfo().getKey(), plugin);
+ if (plugin.getInstance() != null) {
+ keysByClassLoader.put(plugin.getInstance().getClass().getClassLoader(), plugin.getPluginInfo().getKey());
+ }
}
- /**
- * Return the key of the plugin the extension (in the sense of {@link Plugin.Context#addExtension(Object)} is coming from.
- */
@CheckForNull
public String getPluginKey(Object extension) {
return keysByClassLoader.get(extension.getClass().getClassLoader());
}
- /**
- * Load the plugins that are located in lib/extensions and extensions/plugins. Blacklisted plugins are
- * deleted.
- */
- private void loadPreInstalledPlugins() {
- registerPluginsFromDir(fs.getInstalledBundledPluginsDir());
- registerPluginsFromDir(fs.getInstalledExternalPluginsDir());
- }
-
- private void registerPluginsFromDir(File pluginsDir) {
- for (File file : listJarFiles(pluginsDir)) {
- PluginInfo info = PluginInfo.create(file);
- registerPluginInfo(info);
- }
- }
-
- /**
- * Move the plugins recently downloaded to extensions/plugins.
- */
- private void moveDownloadedPlugins() {
- if (fs.getDownloadedPluginsDir().exists()) {
- for (File sourceFile : listJarFiles(fs.getDownloadedPluginsDir())) {
- overrideAndRegisterPlugin(sourceFile);
- }
- }
- }
-
- private void registerPluginInfo(PluginInfo info) {
- String pluginKey = info.getKey();
- if (blacklistedPluginKeys.contains(pluginKey)) {
- LOG.warn("Plugin {} [{}] is blacklisted and is being uninstalled", info.getName(), pluginKey);
- deleteQuietly(info.getNonNullJarFile());
- return;
- }
- if (FORBIDDEN_COMPATIBLE_PLUGINS.contains(pluginKey)) {
- throw MessageException.of(String.format("Plugin '%s' is no longer compatible with this version of SonarQube", pluginKey));
- }
- PluginInfo existing = pluginInfosByKeys.put(pluginKey, info);
- if (existing != null) {
- File existingPluginParentDir = existing.getNonNullJarFile().getParentFile();
- File currentPluginParentDir = info.getNonNullJarFile().getParentFile();
- if (existingPluginParentDir.equals(currentPluginParentDir)) {
- String directory = existingPluginParentDir.equals(fs.getInstalledBundledPluginsDir()) ? "lib/extensions" : "extensions/plugins";
- throw MessageException.of(format("Found two versions of the plugin %s [%s] in the directory %s. Please remove one of %s or %s.",
- info.getName(), pluginKey, directory, info.getNonNullJarFile().getName(), existing.getNonNullJarFile().getName()));
- } else {
- throw MessageException
- .of(format("Found two versions of the plugin %s [%s] in different directories lib/extensions and extension/plugins. Please remove the one from extension/plugins: %s.",
- info.getName(), pluginKey, info.getNonNullJarFile().getName()));
- }
- }
- }
-
- /**
- * Move or copy plugin to directory extensions/plugins. If a version of this plugin
- * already exists then it's deleted.
- */
- private void overrideAndRegisterPlugin(File sourceFile) {
- File destDir = fs.getInstalledExternalPluginsDir();
- File destFile = new File(destDir, sourceFile.getName());
- if (destFile.exists()) {
- // plugin with same filename already installed
- deleteQuietly(destFile);
- }
-
- movePlugin(sourceFile, destFile);
-
- PluginInfo info = PluginInfo.create(destFile);
- PluginInfo existing = pluginInfosByKeys.put(info.getKey(), info);
-
- if (existing != null) {
- File existingJarFile = existing.getNonNullJarFile();
-
- if (existingJarFile.getParentFile().equals(fs.getInstalledBundledPluginsDir())) {
- // move downloaded plugin back to origin location
- movePlugin(destFile, sourceFile);
- throw MessageException.of(format("Fail to update plugin: %s. Bundled plugin with same key already exists: %s. "
- + "Move or delete plugin from extensions/downloads directory",
- sourceFile.getName(), existing.getKey()));
- }
-
- if (!existingJarFile.getName().equals(destFile.getName())) {
- deleteQuietly(existingJarFile);
- }
- LOG.info("Plugin {} [{}] updated to version {}", info.getName(), info.getKey(), info.getVersion());
- } else {
- LOG.info("Plugin {} [{}] installed", info.getName(), info.getKey());
- }
- }
-
- private void movePlugin(File sourcePluginFile, File destPluginFile) {
- try {
- moveFile(sourcePluginFile, destPluginFile);
-
- } catch (IOException e) {
- throw new IllegalStateException(format("Fail to move plugin: %s to %s",
- sourcePluginFile.getAbsolutePath(), destPluginFile.getAbsolutePath()), e);
- }
- }
-
- /**
- * Removes the plugins that are not compatible with current environment.
- */
- private void unloadIncompatiblePlugins() {
- // loop as long as the previous loop ignored some plugins. That allows to support dependencies
- // on many levels, for example D extends C, which extends B, which requires A. If A is not installed,
- // then B, C and D must be ignored. That's not possible to achieve this algorithm with a single
- // iteration over plugins.
- Set<String> removedKeys = new HashSet<>();
- do {
- removedKeys.clear();
- for (PluginInfo plugin : pluginInfosByKeys.values()) {
- if (!isCompatible(plugin, runtime, pluginInfosByKeys)) {
- removedKeys.add(plugin.getKey());
- }
- }
- for (String removedKey : removedKeys) {
- pluginInfosByKeys.remove(removedKey);
- }
- } while (!removedKeys.isEmpty());
- }
-
- @VisibleForTesting
- static boolean isCompatible(PluginInfo plugin, SonarRuntime runtime, Map<String, PluginInfo> allPluginsByKeys) {
- if (Strings.isNullOrEmpty(plugin.getMainClass()) && Strings.isNullOrEmpty(plugin.getBasePlugin())) {
- LOG.warn("Plugin {} [{}] is ignored because entry point class is not defined", plugin.getName(), plugin.getKey());
- return false;
- }
-
- if (!plugin.isCompatibleWith(runtime.getApiVersion().toString())) {
- throw MessageException.of(format(
- "Plugin %s [%s] requires at least SonarQube %s", plugin.getName(), plugin.getKey(), plugin.getMinimalSqVersion()));
- }
-
- if (!Strings.isNullOrEmpty(plugin.getBasePlugin()) && !allPluginsByKeys.containsKey(plugin.getBasePlugin())) {
- // it extends a plugin that is not installed
- LOG.warn("Plugin {} [{}] is ignored because its base plugin [{}] is not installed", plugin.getName(), plugin.getKey(), plugin.getBasePlugin());
- return false;
- }
-
- for (PluginInfo.RequiredPlugin requiredPlugin : plugin.getRequiredPlugins()) {
- PluginInfo installedRequirement = allPluginsByKeys.get(requiredPlugin.getKey());
- if (installedRequirement == null) {
- // it requires a plugin that is not installed
- LOG.warn("Plugin {} [{}] is ignored because the required plugin [{}] is not installed", plugin.getName(), plugin.getKey(), requiredPlugin.getKey());
- return false;
- }
- Version installedRequirementVersion = installedRequirement.getVersion();
- if (installedRequirementVersion != null && requiredPlugin.getMinimalVersion().compareToIgnoreQualifier(installedRequirementVersion) > 0) {
- // it requires a more recent version
- LOG.warn("Plugin {} [{}] is ignored because the version {} of required plugin [{}] is not installed", plugin.getName(), plugin.getKey(),
- requiredPlugin.getMinimalVersion(), requiredPlugin.getKey());
- return false;
- }
- }
- return true;
- }
-
- private void logInstalledPlugins() {
- List<PluginInfo> orderedPlugins = Ordering.natural().sortedCopy(pluginInfosByKeys.values());
- for (PluginInfo plugin : orderedPlugins) {
- LOG.info("Deploy plugin {}", SLASH_JOINER.join(plugin.getName(), plugin.getVersion(), plugin.getImplementationBuild()));
- }
- }
-
- private void loadInstances() {
- pluginInstancesByKeys.putAll(loader.load(pluginInfosByKeys));
-
- for (Map.Entry<String, Plugin> e : pluginInstancesByKeys.entrySet()) {
- keysByClassLoader.put(e.getValue().getClass().getClassLoader(), e.getKey());
- }
- }
-
- /**
- * Uninstall a plugin and its dependents
- */
- public void uninstall(String pluginKey, File uninstallDir) {
- Set<String> uninstallKeys = new HashSet<>();
- uninstallKeys.add(pluginKey);
- appendDependentPluginKeys(pluginKey, uninstallKeys);
-
- for (String uninstallKey : uninstallKeys) {
- PluginInfo info = getPluginInfo(uninstallKey);
-
- try {
- if (!getPluginFile(info).exists()) {
- LOG.info("Plugin already uninstalled: {} [{}]", info.getName(), info.getKey());
- continue;
- }
-
- LOG.info("Uninstalling plugin {} [{}]", info.getName(), info.getKey());
-
- File masterFile = getPluginFile(info);
- moveFileToDirectory(masterFile, uninstallDir, true);
- } catch (IOException e) {
- throw new IllegalStateException(format("Fail to uninstall plugin %s [%s]", info.getName(), info.getKey()), e);
- }
- }
+ @Override
+ public Collection<PluginInfo> getPluginInfos() {
+ return Collections.unmodifiableCollection(pluginByKey.values().stream().map(ServerPlugin::getPluginInfo).collect(Collectors.toList()));
}
- public void cancelUninstalls(File uninstallDir) {
- for (File file : listJarFiles(uninstallDir)) {
- try {
- moveFileToDirectory(file, fs.getInstalledExternalPluginsDir(), false);
- } catch (IOException e) {
- throw new IllegalStateException("Fail to cancel plugin uninstalls", e);
- }
- }
+ @Override
+ public PluginInfo getPluginInfo(String key) {
+ return getPlugin(key).getPluginInfo();
}
- /**
- * Appends dependent plugins, only the ones that still exist in the plugins folder.
- */
- private void appendDependentPluginKeys(String pluginKey, Set<String> appendTo) {
- for (PluginInfo otherPlugin : getPluginInfos()) {
- if (!otherPlugin.getKey().equals(pluginKey)) {
- for (PluginInfo.RequiredPlugin requirement : otherPlugin.getRequiredPlugins()) {
- if (requirement.getKey().equals(pluginKey)) {
- appendTo.add(otherPlugin.getKey());
- appendDependentPluginKeys(otherPlugin.getKey(), appendTo);
- }
- }
- }
+ public ServerPlugin getPlugin(String key) {
+ ServerPlugin plugin = pluginByKey.get(key);
+ if (plugin == null) {
+ throw new IllegalArgumentException(format("Plugin [%s] does not exist", key));
}
+ return plugin;
}
- private File getPluginFile(PluginInfo info) {
- // we don't reuse info.getFile() just to be sure that file is located in from extensions/plugins
- return new File(fs.getInstalledExternalPluginsDir(), info.getNonNullJarFile().getName());
- }
-
- public Map<String, PluginInfo> getPluginInfosByKeys() {
- return pluginInfosByKeys;
- }
-
- @Override
- public Collection<PluginInfo> getPluginInfos() {
- checkState(started.get(), NOT_STARTED_YET);
- return ImmutableList.copyOf(pluginInfosByKeys.values());
+ public Collection<ServerPlugin> getPlugins() {
+ return Collections.unmodifiableCollection(pluginByKey.values());
}
- @Override
- public PluginInfo getPluginInfo(String key) {
- checkState(started.get(), NOT_STARTED_YET);
- PluginInfo info = pluginInfosByKeys.get(key);
- if (info == null) {
- throw new IllegalArgumentException(format("Plugin [%s] does not exist", key));
- }
- return info;
+ public Optional<ServerPlugin> findPlugin(String key) {
+ return Optional.ofNullable(pluginByKey.get(key));
}
@Override
public Plugin getPluginInstance(String key) {
- checkState(started.get(), NOT_STARTED_YET);
- Plugin plugin = pluginInstancesByKeys.get(key);
+ ServerPlugin plugin = pluginByKey.get(key);
checkArgument(plugin != null, "Plugin [%s] does not exist", key);
- return plugin;
+ return plugin.getInstance();
}
@Override
- public boolean hasPlugin(String key) {
- checkState(started.get(), NOT_STARTED_YET);
- return pluginInfosByKeys.containsKey(key);
+ public Collection<Plugin> getPluginInstances() {
+ return pluginByKey.values().stream()
+ .map(ServerPlugin::getInstance)
+ .collect(Collectors.toList());
}
- private static Collection<File> listJarFiles(File dir) {
- if (dir.exists()) {
- return FileUtils.listFiles(dir, JAR_FILE_EXTENSIONS, false);
- }
- return Collections.emptyList();
+ @Override
+ public boolean hasPlugin(String key) {
+ return pluginByKey.containsKey(key);
}
}
*/
package org.sonar.server.plugins;
-import com.google.common.base.Optional;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Date;
+import java.util.Optional;
import org.apache.commons.io.IOUtils;
import org.sonar.api.Properties;
import org.sonar.api.Property;
public Optional<UpdateCenter> getUpdateCenter(boolean forceRefresh) {
if (!isActivated) {
- return Optional.absent();
+ return Optional.empty();
}
if (pluginCenter == null || forceRefresh || needsRefresh()) {
pluginCenter = init();
lastRefreshDate = System.currentTimeMillis();
}
- return Optional.fromNullable(pluginCenter);
+ return Optional.ofNullable(pluginCenter);
}
public Date getLastRefreshDate() {
*/
package org.sonar.server.plugins;
-import com.google.common.base.Optional;
+import java.util.Optional;
import org.sonar.api.SonarRuntime;
import org.sonar.updatecenter.common.UpdateCenter;
import org.sonar.updatecenter.common.Version;
installedPluginReferentialFactory.getInstalledPluginReferential())
.setDate(centerClient.getLastRefreshDate()));
}
- return Optional.absent();
+ return Optional.empty();
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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);
+ // }
+
+}
*/
package org.sonar.server.plugins;
-import com.google.common.base.Optional;
import java.io.File;
import java.net.URI;
+import java.util.Optional;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
@Test
public void download_when_update_center_is_unavailable_with_no_exception_thrown() {
- when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.absent());
+ when(updateCenterMatrixFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.empty());
Plugin test = Plugin.factory("test");
Release test10 = new Release(test, "1.0").setDownloadUrl("http://server/test-1.0.jar");
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2020 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();
-
- @Before
- public void setUp() throws IOException {
- Path sourceFolder = temp.newFolder("source").toPath();
- Path targetFolder = temp.newFolder("target").toPath();
- Path 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);
- // }
-
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+public class PluginFilesAndMd5Test {
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Test
+ public void getters() throws IOException {
+ File jarFile = temp.newFile();
+ Files.write(jarFile.toPath(), "f1".getBytes(StandardCharsets.UTF_8));
+ File jarFileCompressed = temp.newFile();
+
+ Files.write(jarFileCompressed.toPath(), "f1compressed".getBytes(StandardCharsets.UTF_8));
+
+ PluginFilesAndMd5.FileAndMd5 jar = new PluginFilesAndMd5.FileAndMd5(jarFile);
+ PluginFilesAndMd5.FileAndMd5 jarCompressed = new PluginFilesAndMd5.FileAndMd5(jarFileCompressed);
+
+ PluginFilesAndMd5 underTest = new PluginFilesAndMd5(jar, jarCompressed);
+
+ assertThat(underTest.getCompressedJar().getFile()).isEqualTo(jarFileCompressed);
+ assertThat(underTest.getCompressedJar().getMd5()).isEqualTo("a0d076c0fc9f11ec68740fed5aa3ce38");
+
+ assertThat(underTest.getLoadedJar().getFile()).isEqualTo(jarFile);
+ assertThat(underTest.getLoadedJar().getMd5()).isEqualTo("bd19836ddb62c11c55ab251ccaca5645");
+ }
+
+ @Test
+ public void fail_if_cant_get_md5() throws IOException {
+ File jarFile = new File("nonexisting");
+ Assert.assertThrows("Fail to compute md5", IllegalStateException.class, () -> new PluginFilesAndMd5.FileAndMd5(jarFile));
+ }
+}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.FileOutputStream;
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.jar.JarOutputStream;
+import java.util.jar.Manifest;
+import javax.annotation.Nullable;
+import org.apache.commons.io.FileUtils;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.SonarRuntime;
+import org.sonar.api.utils.MessageException;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.server.platform.ServerFileSystem;
+import org.sonar.updatecenter.common.PluginManifest;
+
+import static java.util.jar.Attributes.Name.MANIFEST_VERSION;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class PluginJarLoaderTest {
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+ @Rule
+ public LogTester logs = new LogTester();
+
+ private ServerFileSystem fs = mock(ServerFileSystem.class);
+ private Set<String> blacklisted = new HashSet<>();
+ private SonarRuntime runtime = mock(SonarRuntime.class);
+ private PluginJarLoader underTest = new PluginJarLoader(fs, runtime, blacklisted);
+
+ @Before
+ public void setUp() throws IOException {
+ when(runtime.getApiVersion()).thenReturn(org.sonar.api.utils.Version.parse("5.2"));
+ when(fs.getDeployedPluginsDir()).thenReturn(temp.newFolder("deployed"));
+ when(fs.getDownloadedPluginsDir()).thenReturn(temp.newFolder("downloaded"));
+ when(fs.getHomeDir()).thenReturn(temp.newFolder("home"));
+ when(fs.getInstalledExternalPluginsDir()).thenReturn(temp.newFolder("external"));
+ when(fs.getInstalledBundledPluginsDir()).thenReturn(temp.newFolder("bundled"));
+ when(fs.getTempDir()).thenReturn(temp.newFolder("temp"));
+ }
+
+ @Test
+ public void load_installed_bundled_and_external_plugins() throws Exception {
+ copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
+ copyTestPluginTo("test-extend-plugin", fs.getInstalledBundledPluginsDir());
+
+ Collection<ServerPluginInfo> loadedPlugins = underTest.loadPlugins();
+
+ assertThat(loadedPlugins).extracting(PluginInfo::getKey).containsOnly("testbase", "testextend");
+ }
+
+ @Test
+ public void dont_fail_if_directories_dont_exist() {
+ FileUtils.deleteQuietly(fs.getInstalledExternalPluginsDir());
+ FileUtils.deleteQuietly(fs.getInstalledBundledPluginsDir());
+ FileUtils.deleteQuietly(fs.getDownloadedPluginsDir());
+ Collection<ServerPluginInfo> loadedPlugins = underTest.loadPlugins();
+ assertThat(loadedPlugins).extracting(PluginInfo::getKey).isEmpty();
+ }
+
+ @Test
+ public void update_downloaded_plugin() throws IOException {
+ File jar = createJar(fs.getDownloadedPluginsDir(), "plugin1", "main", null, "2.0");
+ createJar(fs.getInstalledExternalPluginsDir(), "plugin1", "main", null, "1.0");
+
+ underTest.loadPlugins();
+
+ assertThat(logs.logs()).contains("Plugin plugin1 [plugin1] updated to version 2.0");
+ assertThat(Files.list(fs.getInstalledExternalPluginsDir().toPath())).extracting(Path::getFileName).containsOnly(jar.toPath().getFileName());
+ }
+
+ @Test
+ public void move_downloaded_plugins_to_external() throws Exception {
+ copyTestPluginTo("test-base-plugin", fs.getDownloadedPluginsDir());
+ copyTestPluginTo("test-extend-plugin", fs.getInstalledExternalPluginsDir());
+ assertThat(Files.list(fs.getInstalledExternalPluginsDir().toPath())).hasSize(1);
+
+ Collection<ServerPluginInfo> loadedPlugins = underTest.loadPlugins();
+
+ assertThat(loadedPlugins).extracting(PluginInfo::getKey).containsOnly("testbase", "testextend");
+ assertThat(fs.getDownloadedPluginsDir()).isEmptyDirectory();
+ assertThat(Files.list(fs.getInstalledExternalPluginsDir().toPath())).hasSize(2);
+ }
+
+ @Test
+ public void no_plugins_at_startup() {
+ assertThat(underTest.loadPlugins()).isEmpty();
+ }
+
+ @Test
+ public void test_plugin_requirements_at_startup() throws Exception {
+ copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
+ copyTestPluginTo("test-require-plugin", fs.getInstalledExternalPluginsDir());
+
+ assertThat(underTest.loadPlugins()).extracting(PluginInfo::getKey).containsOnly("testbase", "testrequire");
+ }
+
+ @Test
+ public void plugin_is_ignored_if_required_plugin_is_missing_at_startup() throws Exception {
+ copyTestPluginTo("test-require-plugin", fs.getInstalledExternalPluginsDir());
+
+ // plugin is not installed as test-base-plugin is missing
+ assertThat(underTest.loadPlugins()).isEmpty();
+ assertThat(logs.logs()).contains("Plugin Test Require Plugin [testrequire] is ignored because the required plugin [testbase] is not installed");
+ }
+
+ @Test
+ public void install_plugin_and_its_extension_plugins_at_startup() throws Exception {
+ copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
+ copyTestPluginTo("test-extend-plugin", fs.getInstalledExternalPluginsDir());
+
+ // both plugins are installed
+ assertThat(underTest.loadPlugins()).extracting(PluginInfo::getKey).containsOnly("testbase", "testextend");
+ }
+
+ /**
+ * Some plugins can only extend the classloader of base plugin, without declaring new extensions.
+ */
+ @Test
+ public void plugin_is_compatible_if_no_entry_point_class_but_extend_other_plugin() throws IOException {
+ createJar(fs.getInstalledExternalPluginsDir(), "base", "org.bar.Bar", null);
+ createJar(fs.getInstalledExternalPluginsDir(), "foo", null, "base");
+
+ assertThat(underTest.loadPlugins()).extracting(PluginInfo::getKey).containsOnly("base", "foo");
+ }
+
+ @Test
+ public void extension_plugin_is_ignored_if_base_plugin_is_missing_at_startup() throws Exception {
+ copyTestPluginTo("test-extend-plugin", fs.getInstalledExternalPluginsDir());
+
+ assertThat(underTest.loadPlugins()).isEmpty();
+ assertThat(logs.logs()).contains("Plugin Test Extend Plugin [testextend] is ignored because its base plugin [testbase] is not installed");
+ }
+
+ @Test
+ public void plugin_is_ignored_if_required_plugin_is_too_old_at_startup() throws Exception {
+ copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
+ copyTestPluginTo("test-requirenew-plugin", fs.getInstalledExternalPluginsDir());
+
+ // the plugin "requirenew" is not installed as it requires base 0.2+ to be installed.
+ assertThat(underTest.loadPlugins()).extracting(PluginInfo::getKey).containsOnly("testbase");
+ assertThat(logs.logs()).contains("Plugin Test Require New Plugin [testrequire] is ignored because the version 0.2 of required plugin [testbase] is not installed");
+ }
+
+ @Test
+ public void blacklisted_plugin_is_automatically_deleted() throws Exception {
+ blacklisted.add("testbase");
+ blacklisted.add("issuesreport");
+
+ File jar = copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
+
+ Collection<ServerPluginInfo> loadedPlugins = underTest.loadPlugins();
+
+ // plugin is not installed and file is deleted
+ assertThat(loadedPlugins).isEmpty();
+ assertThat(jar).doesNotExist();
+ }
+
+ @Test
+ public void warn_if_plugin_has_no_entry_point_class() throws IOException {
+ createJar(fs.getInstalledExternalPluginsDir(), "test", null, null);
+ assertThat(underTest.loadPlugins()).isEmpty();
+ assertThat(logs.logs()).contains("Plugin test [test] is ignored because entry point class is not defined");
+ }
+
+ @Test
+ public void fail_if_external_plugin_has_same_key_has_bundled_plugin() throws IOException {
+ File jar = createJar(fs.getInstalledExternalPluginsDir(), "plugin1", "main", null);
+ createJar(fs.getInstalledBundledPluginsDir(), "plugin1", "main", null);
+
+ String dir = getDirName(fs.getInstalledExternalPluginsDir());
+ expectedException.expectMessage("Found a plugin 'plugin1' in the directory " + dir + " with the same key [plugin1] as a bundled plugin 'plugin1'. "
+ + "Please remove " + jar.getName());
+ expectedException.expect(MessageException.class);
+ underTest.loadPlugins();
+ }
+
+ @Test
+ public void fail_if_downloaded_plugin_has_same_key_has_bundled() throws IOException {
+ File downloaded = createJar(fs.getDownloadedPluginsDir(), "plugin1", "main", null);
+ createJar(fs.getInstalledBundledPluginsDir(), "plugin1", "main", null);
+ String dir = getDirName(fs.getDownloadedPluginsDir());
+ expectedException.expectMessage("Fail to update plugin: plugin1. Bundled plugin with same key already exists: plugin1. "
+ + "Move or delete plugin from " + dir + " directory");
+ expectedException.expect(MessageException.class);
+ underTest.loadPlugins();
+ }
+
+ @Test
+ public void fail_if_external_plugins_have_same_key() throws IOException {
+ File jar1 = createJar(fs.getInstalledExternalPluginsDir(), "plugin1", "main", null);
+ File jar2 = createJar(fs.getInstalledExternalPluginsDir(), "plugin1", "main", null);
+
+ String dir = getDirName(fs.getInstalledExternalPluginsDir());
+ expectedException.expectMessage("Found two versions of the plugin 'plugin1' [plugin1] in the directory " + dir + ". Please remove ");
+ expectedException.expectMessage(jar2.getName());
+ expectedException.expectMessage(jar1.getName());
+ expectedException.expect(MessageException.class);
+ underTest.loadPlugins();
+ }
+
+ @Test
+ public void fail_if_bundled_plugins_have_same_key() throws IOException {
+ File jar1 = createJar(fs.getInstalledBundledPluginsDir(), "plugin1", "main", null);
+ File jar2 = createJar(fs.getInstalledBundledPluginsDir(), "plugin1", "main", null);
+ String dir = getDirName(fs.getInstalledBundledPluginsDir());
+ expectedException.expectMessage("Found two versions of the plugin plugin1 [plugin1] in the directory " + dir + ". Please remove one of ");
+ expectedException.expectMessage(jar1.getName());
+ expectedException.expectMessage(jar2.getName());
+ expectedException.expect(MessageException.class);
+ underTest.loadPlugins();
+ }
+
+ @Test
+ public void fail_when_sqale_plugin_is_installed() throws Exception {
+ copyTestPluginTo("fake-sqale-plugin", fs.getInstalledExternalPluginsDir());
+
+ expectedException.expect(MessageException.class);
+ expectedException.expectMessage("Plugin 'sqale' is no longer compatible with this version of SonarQube");
+ underTest.loadPlugins();
+ }
+
+ @Test
+ public void fail_when_report_is_installed() throws Exception {
+ copyTestPluginTo("fake-report-plugin", fs.getInstalledExternalPluginsDir());
+
+ expectedException.expect(MessageException.class);
+ expectedException.expectMessage("Plugin 'report' is no longer compatible with this version of SonarQube");
+ underTest.loadPlugins();
+ }
+
+ @Test
+ public void fail_when_views_is_installed() throws Exception {
+ copyTestPluginTo("fake-views-plugin", fs.getInstalledExternalPluginsDir());
+
+ expectedException.expect(MessageException.class);
+ expectedException.expectMessage("Plugin 'views' is no longer compatible with this version of SonarQube");
+ underTest.loadPlugins();
+ }
+
+ @Test
+ public void fail_if_plugin_does_not_support_sq_version() throws Exception {
+ when(runtime.getApiVersion()).thenReturn(org.sonar.api.utils.Version.parse("1.0"));
+ copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
+
+ expectedException.expectMessage("Plugin Base Plugin [testbase] requires at least SonarQube 4.5.4");
+ underTest.loadPlugins();
+ }
+
+ private static File copyTestPluginTo(String testPluginName, File toDir) throws IOException {
+ File jar = TestProjectUtils.jarOf(testPluginName);
+ // file is copied because it's supposed to be moved by the test
+ FileUtils.copyFileToDirectory(jar, toDir);
+ return new File(toDir, jar.getName());
+ }
+
+ private static String getDirName(File dir) {
+ Path path = dir.toPath();
+ return new File(path.getName(path.getNameCount() - 2).toString(), path.getName(path.getNameCount() - 1).toString()).toString();
+ }
+
+ private static File createJar(File dir, String key, @Nullable String mainClass, @Nullable String basePlugin) throws IOException {
+ return createJar(dir, key, mainClass, basePlugin, null);
+ }
+
+ private static File createJar(File dir, String key, @Nullable String mainClass, @Nullable String basePlugin, @Nullable String version) throws IOException {
+ Manifest manifest = new Manifest();
+ manifest.getMainAttributes().putValue(PluginManifest.KEY, key);
+ manifest.getMainAttributes().putValue(PluginManifest.NAME, key);
+ if (version != null) {
+ manifest.getMainAttributes().putValue(PluginManifest.VERSION, version);
+ }
+ if (mainClass != null) {
+ manifest.getMainAttributes().putValue(PluginManifest.MAIN_CLASS, mainClass);
+ }
+ if (basePlugin != null) {
+ manifest.getMainAttributes().putValue(PluginManifest.BASE_PLUGIN, basePlugin);
+ }
+ manifest.getMainAttributes().putValue(MANIFEST_VERSION.toString(), "1.0");
+ File jarFile = File.createTempFile(key, ".jar", dir);
+ try (JarOutputStream jar = new JarOutputStream(new FileOutputStream(jarFile), manifest)) {
+ // nothing else to add
+ }
+ return jarFile;
+ }
+}
import java.io.File;
import java.io.IOException;
+import java.nio.file.Files;
+import java.util.Arrays;
+import java.util.Collections;
import org.apache.commons.io.FileUtils;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
+import org.sonar.api.Plugin;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.core.platform.PluginInfo;
import org.sonar.server.platform.ServerFileSystem;
+import org.sonar.updatecenter.common.Version;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
+import static org.sonar.server.plugins.PluginType.BUNDLED;
+import static org.sonar.server.plugins.PluginType.EXTERNAL;
public class PluginUninstallerTest {
@Rule
public TemporaryFolder testFolder = new TemporaryFolder();
-
@Rule
public ExpectedException exception = ExpectedException.none();
+ @Rule
+ public LogTester logs = new LogTester();
private File uninstallDir;
- private PluginUninstaller underTest;
- private ServerPluginRepository serverPluginRepository;
- private ServerFileSystem fs;
+ private ServerFileSystem fs = mock(ServerFileSystem.class);
+ private ServerPluginRepository serverPluginRepository = new ServerPluginRepository();
+ private PluginUninstaller underTest = new PluginUninstaller(fs, serverPluginRepository);
@Before
public void setUp() throws IOException {
- serverPluginRepository = mock(ServerPluginRepository.class);
uninstallDir = testFolder.newFolder("uninstall");
- fs = mock(ServerFileSystem.class);
when(fs.getUninstalledPluginsDir()).thenReturn(uninstallDir);
- underTest = new PluginUninstaller(serverPluginRepository, fs);
+ when(fs.getInstalledExternalPluginsDir()).thenReturn(testFolder.newFolder("external"));
}
@Test
- public void uninstall() {
- when(serverPluginRepository.hasPlugin("plugin")).thenReturn(true);
- underTest.uninstall("plugin");
- verify(serverPluginRepository).uninstall("plugin", uninstallDir);
+ public void create_uninstall_dir() {
+ File dir = new File(testFolder.getRoot(), "dir");
+ when(fs.getUninstalledPluginsDir()).thenReturn(dir);
+
+ assertThat(dir).doesNotExist();
+ underTest.start();
+ assertThat(dir).isDirectory();
}
@Test
- public void fail_uninstall_if_plugin_not_installed() {
- when(serverPluginRepository.hasPlugin("plugin")).thenReturn(false);
- exception.expect(IllegalArgumentException.class);
- exception.expectMessage("Plugin [plugin] is not installed");
- underTest.uninstall("plugin");
- verifyZeroInteractions(serverPluginRepository);
+ public void fail_uninstall_if_plugin_doesnt_exist() {
+ underTest.start();
+ assertThatThrownBy(() -> underTest.uninstall("plugin"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Plugin [plugin] is not installed");
}
@Test
- public void create_uninstall_dir() {
- File dir = new File(testFolder.getRoot(), "dir");
- when(fs.getUninstalledPluginsDir()).thenReturn(dir);
- underTest = new PluginUninstaller(serverPluginRepository, fs);
+ public void fail_uninstall_if_plugin_is_bundled() {
underTest.start();
- assertThat(dir).isDirectory();
+ serverPluginRepository.addPlugin(newPlugin("plugin", BUNDLED, "plugin.jar"));
+ assertThatThrownBy(() -> underTest.uninstall("plugin"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("Plugin [plugin] is not installed");
+ }
+
+ @Test
+ public void uninstall() throws Exception {
+ File installedJar = copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
+ serverPluginRepository.addPlugin(newPlugin("testbase", EXTERNAL, installedJar.getName()));
+
+ underTest.start();
+ assertThat(installedJar).exists();
+
+ underTest.uninstall("testbase");
+
+ assertThat(installedJar).doesNotExist();
+ assertThat(uninstallDir.list()).containsOnly(installedJar.getName());
}
@Test
- public void cancel() {
+ public void uninstall_ignores_non_existing_files() {
+ underTest.start();
+ serverPluginRepository.addPlugin(newPlugin("test", EXTERNAL, "nonexisting.jar"));
+ underTest.uninstall("test");
+ assertThat(uninstallDir).isEmptyDirectory();
+ assertThat(logs.logs()).contains("Plugin already uninstalled: test [test]");
+ }
+
+ @Test
+ public void uninstall_dependents() throws IOException {
+ File baseJar = copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
+ File requirejar = copyTestPluginTo("test-require-plugin", fs.getInstalledExternalPluginsDir());
+
+ ServerPlugin base = newPlugin("test-base-plugin", EXTERNAL, baseJar.getName());
+ ServerPlugin extension = newPlugin("test-require-plugin", EXTERNAL, requirejar.getName(), new PluginInfo.RequiredPlugin("test-base-plugin", Version.create("1.0")));
+
+ serverPluginRepository.addPlugins(Arrays.asList(base, extension));
+
+ underTest.start();
+ underTest.uninstall("test-base-plugin");
+ assertThat(Files.list(uninstallDir.toPath())).extracting(p -> p.getFileName().toString()).containsOnly(baseJar.getName(), requirejar.getName());
+ assertThat(fs.getInstalledExternalPluginsDir()).isEmptyDirectory();
+ }
+
+ @Test
+ public void cancel() throws IOException {
+ File file = copyTestPluginTo("test-base-plugin", uninstallDir);
+ assertThat(Files.list(uninstallDir.toPath())).extracting(p -> p.getFileName().toString()).containsOnly(file.getName());
underTest.cancelUninstalls();
- verify(serverPluginRepository).cancelUninstalls(uninstallDir);
- verifyNoMoreInteractions(serverPluginRepository);
}
@Test
assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase");
}
+ private static ServerPlugin newPlugin(String key, PluginType type, String jarFile, PluginInfo.RequiredPlugin requiredPlugin) {
+ ServerPluginInfo pluginInfo = newPluginInfo(key, type, jarFile);
+ when(pluginInfo.getRequiredPlugins()).thenReturn(Collections.singleton(requiredPlugin));
+ return newPlugin(pluginInfo);
+ }
+
+ private static ServerPlugin newPlugin(String key, PluginType type, String jarFile) {
+ return newPlugin(newPluginInfo(key, type, jarFile));
+ }
+
+ private static ServerPluginInfo newPluginInfo(String key, PluginType type, String jarFile) {
+ ServerPluginInfo pluginInfo = mock(ServerPluginInfo.class);
+ when(pluginInfo.getKey()).thenReturn(key);
+ when(pluginInfo.getName()).thenReturn(key);
+ when(pluginInfo.getType()).thenReturn(type);
+ when(pluginInfo.getNonNullJarFile()).thenReturn(new File(jarFile));
+ return pluginInfo;
+ }
+
+ 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));
+ }
+
private File copyTestPluginTo(String testPluginName, File toDir) throws IOException {
File jar = TestProjectUtils.jarOf(testPluginName);
// file is copied because it's supposed to be moved by the test
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.sonar.server.plugins.PluginType.BUNDLED;
+import static org.sonar.server.plugins.PluginType.EXTERNAL;
+
+public class ServerPluginInfoTest {
+ @Test
+ public void equals_returns_false_with_different_types() {
+ ServerPluginInfo info1 = new ServerPluginInfo("key1").setType(EXTERNAL);
+ ServerPluginInfo info2 = new ServerPluginInfo("key1").setType(PluginType.BUNDLED);
+ ServerPluginInfo info3 = new ServerPluginInfo("key1").setType(EXTERNAL);
+
+ assertThat(info1).isNotEqualTo(info2)
+ .isEqualTo(info3)
+ .hasSameHashCodeAs(info3.hashCode());
+ assertThat(info1.hashCode()).isNotEqualTo(info2.hashCode());
+ }
+
+ @Test
+ public void set_and_get_type() {
+ ServerPluginInfo info = new ServerPluginInfo("key1").setType(EXTERNAL);
+ assertThat(info.getType()).isEqualTo(EXTERNAL);
+
+ info.setType(BUNDLED);
+ assertThat(info.getType()).isEqualTo(BUNDLED);
+ }
+}
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();
private ServerFileSystem fs = mock(ServerFileSystem.class);
- private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class);
- private ServerPluginJarExploder underTest = new ServerPluginJarExploder(fs, pluginFileSystem);
+ private ServerPluginJarExploder underTest = new ServerPluginJarExploder(fs);
@Test
public void copy_all_classloader_files_to_dedicated_directory() throws Exception {
assertThat(lib.getCanonicalPath()).startsWith(pluginDeployDir.getCanonicalPath());
}
File targetJar = new File(fs.getDeployedPluginsDir(), "testlibs/test-libs-plugin-0.1-SNAPSHOT.jar");
- verify(pluginFileSystem).addInstalledPlugin(info, targetJar);
}
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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 com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import org.junit.After;
+import org.junit.Rule;
+import org.junit.Test;
+import org.sonar.api.Plugin;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.core.platform.ExplodedPlugin;
+import org.sonar.core.platform.PluginClassLoader;
+import org.sonar.core.platform.PluginJarExploder;
+import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.tuple;
+import static org.mockito.ArgumentMatchers.anyList;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.server.plugins.PluginType.EXTERNAL;
+
+public class ServerPluginManagerTest {
+
+ @Rule
+ public LogTester logs = new LogTester();
+
+ 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);
+
+ @After
+ public void tearDown() {
+ underTest.stop();
+ }
+
+ @Test
+ public void load_plugins() {
+ ServerPluginInfo p1 = newPluginInfo("p1");
+ ServerPluginInfo p2 = newPluginInfo("p2");
+ when(jarLoader.loadPlugins()).thenReturn(Arrays.asList(p1, p2));
+ when(jarExploder.explode(p1)).thenReturn(new ExplodedPlugin(p1, "p1", new File("p1Exploded.jar"), Collections.singletonList(new File("libP1.jar"))));
+ when(jarExploder.explode(p2)).thenReturn(new ExplodedPlugin(p2, "p2", new File("p2Exploded.jar"), Collections.singletonList(new File("libP2.jar"))));
+
+ 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")));
+ }
+
+ private static ServerPluginInfo newPluginInfo(String key) {
+ ServerPluginInfo pluginInfo = mock(ServerPluginInfo.class);
+ when(pluginInfo.getKey()).thenReturn(key);
+ when(pluginInfo.getType()).thenReturn(EXTERNAL);
+ when(pluginInfo.getNonNullJarFile()).thenReturn(new File(key + ".jar"));
+ 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");
+
+ return new PluginFilesAndMd5(jar, compressed);
+ }
+}
*/
package org.sonar.server.plugins;
-import com.google.common.collect.ImmutableMap;
-import com.google.common.collect.ImmutableSet;
-import java.io.File;
-import java.io.IOException;
+import java.util.Arrays;
import java.util.Collections;
-import java.util.Map;
-import org.apache.commons.io.FileUtils;
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Rule;
+import org.junit.Assert;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.rules.TemporaryFolder;
-import org.mockito.Mockito;
-import org.sonar.api.SonarRuntime;
-import org.sonar.api.utils.MessageException;
-import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.Plugin;
import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginLoader;
-import org.sonar.server.platform.ServerFileSystem;
-import org.sonar.updatecenter.common.Version;
+import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatThrownBy;
-import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
+import static org.sonar.server.plugins.PluginType.EXTERNAL;
public class ServerPluginRepositoryTest {
-
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
-
- @Rule
- public LogTester logs = new LogTester();
-
- private SonarRuntime runtime = mock(SonarRuntime.class);
- private ServerFileSystem fs = mock(ServerFileSystem.class, Mockito.RETURNS_DEEP_STUBS);
- private PluginLoader pluginLoader = mock(PluginLoader.class);
- private ServerPluginRepository underTest = new ServerPluginRepository(runtime, fs, pluginLoader);
-
- @Before
- public void setUp() throws IOException {
- when(fs.getDeployedPluginsDir()).thenReturn(temp.newFolder());
- when(fs.getDownloadedPluginsDir()).thenReturn(temp.newFolder());
- when(fs.getHomeDir()).thenReturn(temp.newFolder());
- when(fs.getInstalledExternalPluginsDir()).thenReturn(temp.newFolder());
- when(fs.getInstalledBundledPluginsDir()).thenReturn(temp.newFolder());
- when(fs.getTempDir()).thenReturn(temp.newFolder());
- when(runtime.getApiVersion()).thenReturn(org.sonar.api.utils.Version.parse("5.2"));
- }
-
- @After
- public void tearDown() {
- underTest.stop();
- }
+ private ServerPluginRepository repository = new ServerPluginRepository();
@Test
- public void standard_startup_loads_installed_bundled_and_external_plugins() throws Exception {
- copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
- copyTestPluginTo("test-extend-plugin", fs.getInstalledBundledPluginsDir());
+ public void get_plugin_data() {
+ ServerPlugin plugin1 = newPlugin("plugin1");
+ ServerPlugin plugin2 = newPlugin("plugin2");
- underTest.start();
-
- assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase", "testextend");
- }
-
- @Test
- public void no_plugins_at_all_on_startup() {
- underTest.start();
-
- assertThat(underTest.getPluginInfos()).isEmpty();
- assertThat(underTest.getPluginInfosByKeys()).isEmpty();
- assertThat(underTest.hasPlugin("testbase")).isFalse();
- }
-
- @Test
- public void fail_if_multiple_jars_for_same_installed_external_plugin_on_startup() throws Exception {
- copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
- copyTestPluginTo("test-base-plugin-v2", fs.getInstalledExternalPluginsDir());
+ repository.addPlugins(Collections.singletonList(plugin1));
+ repository.addPlugin(plugin2);
+ assertThat(repository.getPluginInfos()).containsOnly(plugin1.getPluginInfo(), plugin2.getPluginInfo());
+ assertThat(repository.getPluginInstance("plugin1")).isEqualTo(plugin1.getInstance());
+ assertThat(repository.getPluginInstances()).containsOnly(plugin1.getInstance(), plugin2.getInstance());
+ assertThat(repository.getPlugins()).containsOnly(plugin1, plugin2);
+ assertThat(repository.getPlugin("plugin2")).isEqualTo(plugin2);
+ assertThat(repository.findPlugin("plugin2")).contains(plugin2);
+ assertThat(repository.hasPlugin("plugin2")).isTrue();
- try {
- underTest.start();
- fail();
- } catch (MessageException e) {
- assertThat(e)
- .hasMessageStartingWith("Found two versions of the plugin Base Plugin [testbase] in the directory extensions/plugins. Please remove one of ")
- // order is not guaranteed, so assertion is split
- .hasMessageContaining("test-base-plugin-0.1-SNAPSHOT.jar")
- .hasMessageContaining("test-base-plugin-0.2-SNAPSHOT.jar");
- }
+ assertThat(repository.findPlugin("nonexisting")).isEmpty();
+ assertThat(repository.hasPlugin("nonexisting")).isFalse();
}
@Test
- public void fail_if_multiple_jars_for_same_installed_bundled_plugin_on_startup() throws Exception {
- copyTestPluginTo("test-base-plugin", fs.getInstalledBundledPluginsDir());
- copyTestPluginTo("test-base-plugin-v2", fs.getInstalledBundledPluginsDir());
+ public void fail_getPluginInstance_if_plugin_doesnt_exist() {
+ ServerPlugin plugin1 = newPlugin("plugin1");
+ ServerPlugin plugin2 = newPlugin("plugin2");
- try {
- underTest.start();
- fail();
- } catch (MessageException e) {
- assertThat(e)
- .hasMessageStartingWith("Found two versions of the plugin Base Plugin [testbase] in the directory lib/extensions. Please remove one of ")
- // order is not guaranteed, so assertion is split
- .hasMessageContaining("test-base-plugin-0.1-SNAPSHOT.jar")
- .hasMessageContaining("test-base-plugin-0.2-SNAPSHOT.jar");
- }
+ repository.addPlugins(Arrays.asList(plugin1, plugin2));
+ Assert.assertThrows("asd", IllegalArgumentException.class, () -> repository.getPluginInstance("plugin3"));
}
@Test
- public void fail_if_multiple_jars_for_same_installed_external_bundled_plugin_on_startup() throws Exception {
- copyTestPluginTo("test-base-plugin", fs.getInstalledBundledPluginsDir());
- copyTestPluginTo("test-base-plugin-v2", fs.getInstalledExternalPluginsDir());
-
- try {
- underTest.start();
- fail();
- } catch (MessageException e) {
- assertThat(e)
- .hasMessageStartingWith(
- "Found two versions of the plugin Base Plugin [testbase] in different directories lib/extensions and extension/plugins. Please remove the one from extension/plugins: ")
- .hasMessageContaining("test-base-plugin-0.2-SNAPSHOT.jar");
- }
- }
-
- @Test
- public void install_downloaded_plugins_on_startup() throws Exception {
- File downloadedJar = copyTestPluginTo("test-base-plugin", fs.getDownloadedPluginsDir());
-
- underTest.start();
-
- // plugin is moved to extensions/plugins then loaded
- assertThat(downloadedJar).doesNotExist();
- assertThat(new File(fs.getInstalledExternalPluginsDir(), downloadedJar.getName())).isFile().exists();
- assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
- }
-
- @Test
- public void downloaded_file_overrides_existing_installed_file_on_startup() throws Exception {
- File installedV1 = copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
- File downloadedV2 = copyTestPluginTo("test-base-plugin-v2", fs.getDownloadedPluginsDir());
-
- underTest.start();
-
- // plugin is moved to extensions/plugins and replaces v1
- assertThat(downloadedV2).doesNotExist();
- assertThat(installedV1).doesNotExist();
- assertThat(new File(fs.getInstalledExternalPluginsDir(), downloadedV2.getName())).exists();
- assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
- assertThat(underTest.getPluginInfo("testbase").getVersion()).isEqualTo(Version.create("0.2-SNAPSHOT"));
- }
-
- @Test
- public void downloaded_file_does_not_override_existing_bundled_file_on_startup() throws Exception {
- File installedV1 = copyTestPluginTo("test-base-plugin", fs.getInstalledBundledPluginsDir());
- File downloadedV2 = copyTestPluginTo("test-base-plugin-v2", fs.getDownloadedPluginsDir());
-
- assertThatThrownBy(() -> underTest.start())
- .isInstanceOf(MessageException.class)
- .hasMessage("Fail to update plugin: test-base-plugin-0.2-SNAPSHOT.jar. Bundled plugin with same key already exists: testbase. "
- + "Move or delete plugin from extensions/downloads directory");
+ public void fail_getPluginInfo_if_plugin_doesnt_exist() {
+ ServerPlugin plugin1 = newPlugin("plugin1");
+ ServerPlugin plugin2 = newPlugin("plugin2");
- // downloaded plugin stays in origin location
- assertThat(downloadedV2).exists();
- // installed plugin has not been deleted
- assertThat(installedV1).exists();
- assertThat(new File(fs.getInstalledExternalPluginsDir(), downloadedV2.getName())).doesNotExist();
- assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
+ repository.addPlugins(Arrays.asList(plugin1, plugin2));
+ Assert.assertThrows("asd", IllegalArgumentException.class, () -> repository.getPluginInfo("plugin3"));
}
- @Test
- public void blacklisted_plugin_is_automatically_uninstalled_on_startup() throws Exception {
- underTest.setBlacklistedPluginKeys(ImmutableSet.of("testbase", "issuesreport"));
- File jar = copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
-
- underTest.start();
-
- // plugin is not installed and file is deleted
- assertThat(underTest.getPluginInfos()).isEmpty();
- assertThat(jar).doesNotExist();
- }
-
- @Test
- public void test_plugin_requirements_at_startup() throws Exception {
- copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
- copyTestPluginTo("test-require-plugin", fs.getInstalledExternalPluginsDir());
-
- underTest.start();
-
- // both plugins are installed
- assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase", "testrequire");
- }
-
- @Test
- public void plugin_is_ignored_if_required_plugin_is_missing_at_startup() throws Exception {
- copyTestPluginTo("test-require-plugin", fs.getInstalledExternalPluginsDir());
-
- underTest.start();
-
- // plugin is not installed as test-base-plugin is missing
- assertThat(underTest.getPluginInfosByKeys()).isEmpty();
- }
-
- @Test
- public void plugin_is_ignored_if_required_plugin_is_too_old_at_startup() throws Exception {
- copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
- copyTestPluginTo("test-requirenew-plugin", fs.getInstalledExternalPluginsDir());
-
- underTest.start();
-
- assertThat(logs.logs()).contains("Plugin Test Require New Plugin [testrequire] is ignored because the version 0.2 of required plugin [testbase] is not installed");
- // the plugin "requirenew" is not installed as it requires base 0.2+ to be installed.
- assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
- }
-
- @Test
- public void fail_if_plugin_does_not_support_sq_version() throws Exception {
- when(runtime.getApiVersion()).thenReturn(org.sonar.api.utils.Version.parse("1.0"));
- copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
-
- try {
- underTest.start();
- fail();
- } catch (MessageException e) {
- assertThat(e).hasMessage("Plugin Base Plugin [testbase] requires at least SonarQube 4.5.4");
- }
- }
-
- @Test
- public void uninstall() throws Exception {
- File installedJar = copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
- File uninstallDir = temp.newFolder("uninstallDir");
-
- underTest.start();
- assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
- underTest.uninstall("testbase", uninstallDir);
-
- assertThat(installedJar).doesNotExist();
- // still up. Will be dropped after next startup
- assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
- assertThat(uninstallDir.list()).containsOnly(installedJar.getName());
- }
-
- @Test
- public void uninstall_dependents() throws Exception {
- File base = copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
- File extension = copyTestPluginTo("test-require-plugin", fs.getInstalledExternalPluginsDir());
- File uninstallDir = temp.newFolder("uninstallDir");
-
- underTest.start();
- assertThat(underTest.getPluginInfos()).hasSize(2);
- underTest.uninstall("testbase", uninstallDir);
- assertThat(base).doesNotExist();
- assertThat(extension).doesNotExist();
- assertThat(uninstallDir.list()).containsOnly(base.getName(), extension.getName());
- }
-
- @Test
- public void dont_uninstall_non_existing_dependents() throws IOException {
- File base = copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
- File extension = copyTestPluginTo("test-require-plugin", fs.getInstalledExternalPluginsDir());
- File uninstallDir = temp.newFolder("uninstallDir");
-
- underTest.start();
- assertThat(underTest.getPluginInfos()).hasSize(2);
- underTest.uninstall("testrequire", uninstallDir);
- assertThat(underTest.getPluginInfos()).hasSize(2);
-
- underTest.uninstall("testbase", uninstallDir);
- assertThat(base).doesNotExist();
- assertThat(extension).doesNotExist();
- assertThat(uninstallDir.list()).containsOnly(base.getName(), extension.getName());
- }
-
- @Test
- public void dont_uninstall_non_existing_files() throws IOException {
- File base = copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
- File extension = copyTestPluginTo("test-require-plugin", fs.getInstalledExternalPluginsDir());
- File uninstallDir = temp.newFolder("uninstallDir");
-
- underTest.start();
- assertThat(underTest.getPluginInfos()).hasSize(2);
- underTest.uninstall("testbase", uninstallDir);
- assertThat(underTest.getPluginInfos()).hasSize(2);
-
- underTest.uninstall("testbase", uninstallDir);
- assertThat(base).doesNotExist();
- assertThat(extension).doesNotExist();
- assertThat(uninstallDir.list()).containsOnly(base.getName(), extension.getName());
- }
-
- @Test
- public void install_plugin_and_its_extension_plugins_at_startup() throws Exception {
- copyTestPluginTo("test-base-plugin", fs.getInstalledExternalPluginsDir());
- copyTestPluginTo("test-extend-plugin", fs.getInstalledExternalPluginsDir());
-
- underTest.start();
-
- // both plugins are installed
- assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase", "testextend");
- }
-
- @Test
- public void extension_plugin_is_ignored_if_base_plugin_is_missing_at_startup() throws Exception {
- copyTestPluginTo("test-extend-plugin", fs.getInstalledExternalPluginsDir());
-
- underTest.start();
-
- // plugin is not installed as its base plugin is not installed
- assertThat(underTest.getPluginInfos()).isEmpty();
- }
-
- @Test
- public void fail_to_get_missing_plugins() {
- underTest.start();
- try {
- underTest.getPluginInfo("unknown");
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessage("Plugin [unknown] does not exist");
- }
-
- try {
- underTest.getPluginInstance("unknown");
- fail();
- } catch (IllegalArgumentException e) {
- assertThat(e).hasMessage("Plugin [unknown] does not exist");
- }
- }
-
- @Test
- public void plugin_is_incompatible_if_no_entry_point_class() {
- PluginInfo plugin = new PluginInfo("foo").setName("Foo");
- assertThat(ServerPluginRepository.isCompatible(plugin, runtime, Collections.emptyMap())).isFalse();
- assertThat(logs.logs()).contains("Plugin Foo [foo] is ignored because entry point class is not defined");
- }
-
- @Test
- public void fail_when_views_is_installed() throws Exception {
- copyTestPluginTo("fake-views-plugin", fs.getInstalledExternalPluginsDir());
-
- expectedException.expect(MessageException.class);
- expectedException.expectMessage("Plugin 'views' is no longer compatible with this version of SonarQube");
- underTest.start();
- }
-
- @Test
- public void fail_when_sqale_plugin_is_installed() throws Exception {
- copyTestPluginTo("fake-sqale-plugin", fs.getInstalledExternalPluginsDir());
-
- expectedException.expect(MessageException.class);
- expectedException.expectMessage("Plugin 'sqale' is no longer compatible with this version of SonarQube");
- underTest.start();
- }
-
- @Test
- public void fail_when_report_is_installed() throws Exception {
- copyTestPluginTo("fake-report-plugin", fs.getInstalledExternalPluginsDir());
-
- expectedException.expect(MessageException.class);
- expectedException.expectMessage("Plugin 'report' is no longer compatible with this version of SonarQube");
- underTest.start();
- }
-
- /**
- * Some plugins can only extend the classloader of base plugin, without declaring new extensions.
- */
- @Test
- public void plugin_is_compatible_if_no_entry_point_class_but_extend_other_plugin() {
- PluginInfo basePlugin = new PluginInfo("base").setMainClass("org.bar.Bar");
- PluginInfo plugin = new PluginInfo("foo").setBasePlugin("base");
- Map<String, PluginInfo> plugins = ImmutableMap.of("base", basePlugin, "foo", plugin);
-
- assertThat(ServerPluginRepository.isCompatible(plugin, runtime, plugins)).isTrue();
- }
-
- @Test
- public void getPluginInstance_throws_ISE_if_repo_is_not_started() {
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("not started yet");
-
- underTest.getPluginInstance("foo");
- }
-
- @Test
- public void getPluginInfo_throws_ISE_if_repo_is_not_started() {
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("not started yet");
-
- underTest.getPluginInfo("foo");
- }
-
- @Test
- public void hasPlugin_throws_ISE_if_repo_is_not_started() {
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("not started yet");
-
- underTest.hasPlugin("foo");
- }
-
- @Test
- public void getPluginInfos_throws_ISE_if_repo_is_not_started() {
- expectedException.expect(IllegalStateException.class);
- expectedException.expectMessage("not started yet");
-
- underTest.getPluginInfos();
+ private PluginInfo newPluginInfo(String key) {
+ PluginInfo pluginInfo = mock(PluginInfo.class);
+ when(pluginInfo.getKey()).thenReturn(key);
+ return pluginInfo;
}
- private File copyTestPluginTo(String testPluginName, File toDir) throws IOException {
- File jar = TestProjectUtils.jarOf(testPluginName);
- // file is copied because it's supposed to be moved by the test
- FileUtils.copyFileToDirectory(jar, toDir);
- return new File(toDir, jar.getName());
+ private ServerPlugin newPlugin(String key) {
+ return new ServerPlugin(newPluginInfo(key), EXTERNAL, mock(Plugin.class), mock(FileAndMd5.class), mock(FileAndMd5.class), mock(ClassLoader.class));
}
}
import org.sonar.updatecenter.common.Version;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.guava.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
@Test
public void ignore_connection_errors() {
when(reader.readString(any(URI.class), eq(StandardCharsets.UTF_8))).thenThrow(new SonarException());
- assertThat(underTest.getUpdateCenter()).isAbsent();
+ assertThat(underTest.getUpdateCenter()).isEmpty();
}
@Test
public void update_center_is_null_when_property_is_false() {
settings.setProperty(ProcessProperties.Property.SONAR_UPDATECENTER_ACTIVATE.getKey(), false);
- assertThat(underTest.getUpdateCenter()).isAbsent();
+ assertThat(underTest.getUpdateCenter()).isEmpty();
}
}
*/
package org.sonar.server.plugins;
-import com.google.common.base.Optional;
+import java.util.Optional;
import org.junit.Test;
import org.sonar.api.SonarRuntime;
import org.sonar.updatecenter.common.UpdateCenter;
-import static org.assertj.guava.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.anyBoolean;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@Test
public void return_absent_update_center() {
UpdateCenterClient updateCenterClient = mock(UpdateCenterClient.class);
- when(updateCenterClient.getUpdateCenter(anyBoolean())).thenReturn(Optional.absent());
+ when(updateCenterClient.getUpdateCenter(anyBoolean())).thenReturn(Optional.empty());
underTest = new UpdateCenterMatrixFactory(updateCenterClient, mock(SonarRuntime.class), mock(InstalledPluginReferentialFactory.class));
Optional<UpdateCenter> updateCenter = underTest.getUpdateCenter(false);
- assertThat(updateCenter).isAbsent();
+ assertThat(updateCenter).isEmpty();
}
}
import org.sonar.api.utils.log.Loggers;
import org.sonar.api.utils.log.Profiler;
import org.sonar.server.platform.ServerFileSystem;
-import org.sonar.server.plugins.InstalledPlugin;
-import org.sonar.server.plugins.PluginFileSystem;
+import org.sonar.server.plugins.ServerPlugin;
+import org.sonar.server.plugins.ServerPluginRepository;
/**
* The file deploy/plugins/index.txt is required for old versions of SonarLint.
private static final Logger LOG = Loggers.get(GeneratePluginIndex.class);
private final ServerFileSystem serverFs;
- private final PluginFileSystem pluginFs;
+ private final ServerPluginRepository serverPluginRepository;
- public GeneratePluginIndex(ServerFileSystem serverFs, PluginFileSystem pluginFs) {
+ public GeneratePluginIndex(ServerFileSystem serverFs, ServerPluginRepository serverPluginRepository) {
this.serverFs = serverFs;
- this.pluginFs = pluginFs;
+ this.serverPluginRepository = serverPluginRepository;
}
@Override
try {
FileUtils.forceMkdir(indexFile.getParentFile());
try (Writer writer = new OutputStreamWriter(new FileOutputStream(indexFile), StandardCharsets.UTF_8)) {
- for (InstalledPlugin plugin : pluginFs.getInstalledFiles()) {
+ for (ServerPlugin plugin : serverPluginRepository.getPlugins()) {
writer.append(toRow(plugin));
writer.append(CharUtils.LF);
}
}
}
- private static String toRow(InstalledPlugin file) {
- StringBuilder sb = new StringBuilder();
- sb.append(file.getPluginInfo().getKey())
+ private static String toRow(ServerPlugin file) {
+ return new StringBuilder().append(file.getPluginInfo().getKey())
.append(",")
.append(file.getPluginInfo().isSonarLintSupported())
.append(",")
- .append(file.getLoadedJar().getFile().getName())
+ .append(file.getJar().getFile().getName())
.append("|")
- .append(file.getLoadedJar().getMd5());
- return sb.toString();
+ .append(file.getJar().getMd5())
+ .toString();
}
}
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.plugin.PluginDto;
-import org.sonar.server.plugins.InstalledPlugin;
-import org.sonar.server.plugins.PluginFileSystem;
+import org.sonar.server.plugins.PluginType;
+import org.sonar.server.plugins.ServerPlugin;
+import org.sonar.server.plugins.ServerPluginRepository;
import static java.util.function.Function.identity;
private static final Logger LOG = Loggers.get(RegisterPlugins.class);
- private final PluginFileSystem pluginFileSystem;
+ private final ServerPluginRepository serverPluginRepository;
private final DbClient dbClient;
private final UuidFactory uuidFactory;
private final System2 system;
- public RegisterPlugins(PluginFileSystem pluginFileSystem, DbClient dbClient, UuidFactory uuidFactory, System2 system) {
- this.pluginFileSystem = pluginFileSystem;
+ public RegisterPlugins(ServerPluginRepository serverPluginRepository, DbClient dbClient, UuidFactory uuidFactory, System2 system) {
+ this.serverPluginRepository = serverPluginRepository;
this.dbClient = dbClient;
this.uuidFactory = uuidFactory;
this.system = system;
try (DbSession dbSession = dbClient.openSession(false)) {
Map<String, PluginDto> allPreviousPluginsByKey = dbClient.pluginDao().selectAll(dbSession).stream()
.collect(Collectors.toMap(PluginDto::getKee, identity()));
- for (InstalledPlugin installed : pluginFileSystem.getInstalledFiles()) {
+ for (ServerPlugin installed : serverPluginRepository.getPlugins()) {
PluginInfo info = installed.getPluginInfo();
PluginDto previousDto = allPreviousPluginsByKey.get(info.getKey());
if (previousDto == null) {
.setUuid(uuidFactory.create())
.setKee(info.getKey())
.setBasePluginKey(info.getBasePlugin())
- .setFileHash(installed.getLoadedJar().getMd5())
+ .setFileHash(installed.getJar().getMd5())
+ .setType(toTypeDto(installed.getType()))
.setCreatedAt(now)
.setUpdatedAt(now);
dbClient.pluginDao().insert(dbSession, pluginDto);
- } else if (!previousDto.getFileHash().equals(installed.getLoadedJar().getMd5())) {
+ } else if (!previousDto.getFileHash().equals(installed.getJar().getMd5()) || !previousDto.getType().equals(toTypeDto(installed.getType()))) {
LOG.debug("Update plugin {}", info.getKey());
previousDto
.setBasePluginKey(info.getBasePlugin())
- .setFileHash(installed.getLoadedJar().getMd5())
+ .setFileHash(installed.getJar().getMd5())
+ .setType(toTypeDto(installed.getType()))
.setUpdatedAt(now);
dbClient.pluginDao().update(dbSession, previousDto);
}
}
}
+ private static PluginDto.Type toTypeDto(PluginType type) {
+ switch (type) {
+ case EXTERNAL:
+ return PluginDto.Type.EXTERNAL;
+ case BUNDLED:
+ return PluginDto.Type.BUNDLED;
+ default:
+ throw new IllegalStateException("Unknown type: " + type);
+ }
+ }
+
}
import org.junit.rules.TemporaryFolder;
import org.sonar.core.platform.PluginInfo;
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 org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5;
+import org.sonar.server.plugins.ServerPlugin;
+import org.sonar.server.plugins.ServerPluginRepository;
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;
+import static org.sonar.server.plugins.PluginType.BUNDLED;
public class GeneratePluginIndexTest {
public TemporaryFolder temp = new TemporaryFolder();
private ServerFileSystem serverFileSystem = mock(ServerFileSystem.class);
- private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class);
+ private ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
private File index;
@Before
@Test
public void shouldWriteIndex() throws IOException {
- InstalledPlugin javaPlugin = newInstalledPlugin("java", true);
- InstalledPlugin gitPlugin = newInstalledPlugin("scmgit", false);
- when(pluginFileSystem.getInstalledFiles()).thenReturn(asList(javaPlugin, gitPlugin));
+ ServerPlugin javaPlugin = newInstalledPlugin("java", true);
+ ServerPlugin gitPlugin = newInstalledPlugin("scmgit", false);
+ when(serverPluginRepository.getPlugins()).thenReturn(asList(javaPlugin, gitPlugin));
- GeneratePluginIndex underTest = new GeneratePluginIndex(serverFileSystem, pluginFileSystem);
+ GeneratePluginIndex underTest = new GeneratePluginIndex(serverFileSystem, serverPluginRepository);
underTest.start();
List<String> lines = FileUtils.readLines(index);
assertThat(lines).containsExactly(
- "java,true," + javaPlugin.getLoadedJar().getFile().getName() + "|" + javaPlugin.getLoadedJar().getMd5(),
- "scmgit,false," + gitPlugin.getLoadedJar().getFile().getName() + "|" + gitPlugin.getLoadedJar().getMd5());
+ "java,true," + javaPlugin.getJar().getFile().getName() + "|" + javaPlugin.getJar().getMd5(),
+ "scmgit,false," + gitPlugin.getJar().getFile().getName() + "|" + gitPlugin.getJar().getMd5());
underTest.stop();
}
File wrongIndex = new File(wrongParent, "index.txt");
when(serverFileSystem.getPluginIndex()).thenReturn(wrongIndex);
- new GeneratePluginIndex(serverFileSystem, pluginFileSystem).start();
+ new GeneratePluginIndex(serverFileSystem, serverPluginRepository).start();
}
- private InstalledPlugin newInstalledPlugin(String key, boolean supportSonarLint) throws IOException {
+ 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 InstalledPlugin(pluginInfo, jar, null);
+ return new ServerPlugin(pluginInfo, BUNDLED, null, jar, null, null);
}
}
import org.sonar.db.DbClient;
import org.sonar.db.DbTester;
import org.sonar.db.plugin.PluginDto;
-import org.sonar.server.plugins.InstalledPlugin;
-import org.sonar.server.plugins.PluginFileSystem;
+import org.sonar.db.plugin.PluginDto.Type;
+import org.sonar.server.plugins.PluginFilesAndMd5;
+import org.sonar.server.plugins.PluginType;
+import org.sonar.server.plugins.ServerPlugin;
+import org.sonar.server.plugins.ServerPluginRepository;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
public DbTester dbTester = DbTester.create(System2.INSTANCE);
private final long now = 12345L;
- private DbClient dbClient = dbTester.getDbClient();
- private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class);
- private UuidFactory uuidFactory = mock(UuidFactory.class);
- private System2 system2 = mock(System2.class);
+ private final DbClient dbClient = dbTester.getDbClient();
+ private final ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
+ private final UuidFactory uuidFactory = mock(UuidFactory.class);
+ private final System2 system2 = mock(System2.class);
@Before
public void setUp() {
FileUtils.write(fakeJavaJar, "fakejava", StandardCharsets.UTF_8);
File fakeJavaCustomJar = temp.newFile();
FileUtils.write(fakeJavaCustomJar, "fakejavacustom", StandardCharsets.UTF_8);
- when(pluginFileSystem.getInstalledFiles()).thenReturn(asList(
+ when(serverPluginRepository.getPlugins()).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(pluginFileSystem, dbClient, uuidFactory, system2);
+ RegisterPlugins register = new RegisterPlugins(serverPluginRepository, dbClient, uuidFactory, system2);
register.start();
Map<String, PluginDto> pluginsByKey = selectAllPlugins();
assertThat(pluginsByKey).hasSize(2);
- verify(pluginsByKey.get("java"), null, "bd451e47a1aa76e73da0359cef63dd63", now, now);
- verify(pluginsByKey.get("javacustom"), "java", "de9b2de3ddc0680904939686c0dba5be", now, now);
+ verify(pluginsByKey.get("java"), Type.BUNDLED, null, "bd451e47a1aa76e73da0359cef63dd63", now, now);
+ verify(pluginsByKey.get("javacustom"), Type.BUNDLED, "java", "de9b2de3ddc0680904939686c0dba5be", now, now);
register.stop();
}
.setKee("java")
.setBasePluginKey(null)
.setFileHash("bd451e47a1aa76e73da0359cef63dd63")
+ .setType(Type.BUNDLED)
.setCreatedAt(1L)
.setUpdatedAt(1L));
dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
.setKee("javacustom")
.setBasePluginKey("java")
.setFileHash("de9b2de3ddc0680904939686c0dba5be")
+ .setType(Type.BUNDLED)
.setCreatedAt(1L)
.setUpdatedAt(1L));
+ dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
+ .setUuid("c")
+ .setKee("csharp")
+ .setBasePluginKey(null)
+ .setFileHash("a4813b6d879c4ec852747c175cdd6141")
+ .setType(Type.EXTERNAL)
+ .setCreatedAt(1L)
+ .setUpdatedAt(1L));
+ dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
+ .setUuid("d")
+ .setKee("new-measures")
+ .setBasePluginKey(null)
+ .setFileHash("6d24712cf701c41ce5eaa948e0bd6d22")
+ .setType(Type.EXTERNAL)
+ .setCreatedAt(1L)
+ .setUpdatedAt(1L));
+
dbTester.commit();
File fakeJavaCustomJar = temp.newFile();
FileUtils.write(fakeJavaCustomJar, "fakejavacustomchanged", StandardCharsets.UTF_8);
- when(pluginFileSystem.getInstalledFiles()).thenReturn(asList(
- newPlugin("javacustom", fakeJavaCustomJar, "java2")));
- new RegisterPlugins(pluginFileSystem, dbClient, uuidFactory, system2).start();
+ File fakeCSharpJar = temp.newFile();
+ FileUtils.write(fakeCSharpJar, "fakecsharp", StandardCharsets.UTF_8);
+
+ when(serverPluginRepository.getPlugins()).thenReturn(asList(
+ newPlugin("javacustom", PluginType.BUNDLED, fakeJavaCustomJar, "java2"),
+ // csharp plugin type changed
+ newPlugin("csharp", PluginType.BUNDLED, fakeCSharpJar, null)));
+
+ new RegisterPlugins(serverPluginRepository, dbClient, uuidFactory, system2).start();
Map<String, PluginDto> pluginsByKey = selectAllPlugins();
- assertThat(pluginsByKey).hasSize(2);
- verify(pluginsByKey.get("java"), null, "bd451e47a1aa76e73da0359cef63dd63", 1L, 1L);
- verify(pluginsByKey.get("javacustom"), "java2", "d22091cff5155e892cfe2f9dab51f811", 1L, now);
+ assertThat(pluginsByKey).hasSize(4);
+ verify(pluginsByKey.get("java"), Type.BUNDLED, null, "bd451e47a1aa76e73da0359cef63dd63", 1L, 1L);
+ verify(pluginsByKey.get("javacustom"), Type.BUNDLED, "java2", "d22091cff5155e892cfe2f9dab51f811", 1L, now);
+ verify(pluginsByKey.get("csharp"), Type.BUNDLED, null, "a4813b6d879c4ec852747c175cdd6141", 1L, now);
+ verify(pluginsByKey.get("new-measures"), Type.EXTERNAL, null, "6d24712cf701c41ce5eaa948e0bd6d22", 1L, 1L);
+ }
+
+ private static ServerPlugin newPlugin(String key, File file, @Nullable String basePlugin) {
+ return newPlugin(key, PluginType.BUNDLED, file, basePlugin);
}
- private static InstalledPlugin newPlugin(String key, File file, @Nullable String basePlugin) {
- InstalledPlugin.FileAndMd5 jar = new InstalledPlugin.FileAndMd5(file);
+ private static ServerPlugin newPlugin(String key, PluginType type, File file, @Nullable String basePlugin) {
+ PluginFilesAndMd5.FileAndMd5 jar = new PluginFilesAndMd5.FileAndMd5(file);
PluginInfo info = new PluginInfo(key)
.setBasePlugin(basePlugin)
.setJarFile(file);
- return new InstalledPlugin(info, jar, null);
+ return new ServerPlugin(info, PluginType.BUNDLED, null, jar, null, null);
}
private Map<String, PluginDto> selectAllPlugins() {
- return dbTester.getDbClient().pluginDao().selectAll(dbTester.getSession())
- .stream()
+ return dbTester.getDbClient().pluginDao().selectAll(dbTester.getSession()).stream()
.collect(uniqueIndex(PluginDto::getKee));
}
- private void verify(PluginDto java, @Nullable String basePluginKey, String fileHash, @Nullable Long createdAt, long updatedAt) {
+ private void verify(PluginDto java, Type type, @Nullable String basePluginKey, String fileHash, @Nullable Long createdAt, long updatedAt) {
assertThat(java.getBasePluginKey()).isEqualTo(basePluginKey);
+ assertThat(java.getType()).isEqualTo(type);
assertThat(java.getFileHash()).isEqualTo(fileHash);
assertThat(java.getCreatedAt()).isEqualTo(createdAt);
assertThat(java.getUpdatedAt()).isEqualTo(updatedAt);
*/
package org.sonar.server.platform.ws;
-import com.google.common.base.Optional;
import com.google.common.io.Resources;
import java.util.List;
+import java.util.Optional;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
*/
package org.sonar.server.plugins.ws;
-import com.google.common.base.Optional;
-import com.google.common.collect.ImmutableSortedSet;
import com.google.common.io.Resources;
import java.util.Collection;
+import java.util.Optional;
+import java.util.stream.Collectors;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
}
private static Collection<PluginUpdate> retrieveAvailablePlugins(UpdateCenter updateCenter) {
- return ImmutableSortedSet.copyOf(
- NAME_KEY_PLUGIN_UPDATE_ORDERING,
- updateCenter.findAvailablePlugins()
- );
+ return updateCenter.findAvailablePlugins().stream().sorted(NAME_KEY_PLUGIN_UPDATE_ORDERING).collect(Collectors.toList());
}
}
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.server.exceptions.NotFoundException;
-import org.sonar.server.plugins.InstalledPlugin;
-import org.sonar.server.plugins.InstalledPlugin.FileAndMd5;
-import org.sonar.server.plugins.PluginFileSystem;
+import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5;
+import org.sonar.server.plugins.ServerPlugin;
+import org.sonar.server.plugins.ServerPluginRepository;
public class DownloadAction implements PluginsWsAction {
private static final String ACCEPT_COMPRESSIONS_PARAM = "acceptCompressions";
private static final String PLUGIN_PARAM = "plugin";
- private final PluginFileSystem pluginFileSystem;
+ private final ServerPluginRepository pluginRepository;
- public DownloadAction(PluginFileSystem pluginFileSystem) {
- this.pluginFileSystem = pluginFileSystem;
+ public DownloadAction(ServerPluginRepository pluginRepository) {
+ this.pluginRepository = pluginRepository;
}
@Override
public void handle(Request request, Response response) throws Exception {
String pluginKey = request.mandatoryParam(PLUGIN_PARAM);
- Optional<InstalledPlugin> file = pluginFileSystem.getInstalledPlugin(pluginKey);
+ Optional<ServerPlugin> file = pluginRepository.findPlugin(pluginKey);
if (!file.isPresent()) {
throw new NotFoundException("Plugin " + pluginKey + " not found");
}
FileAndMd5 downloadedFile;
- FileAndMd5 compressedJar = file.get().getCompressedJar();
+ 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().getLoadedJar().getMd5());
+ response.setHeader("Sonar-UncompressedMD5", file.get().getJar().getMd5());
downloadedFile = compressedJar;
} else {
response.stream().setMediaType("application/java-archive");
- downloadedFile = file.get().getLoadedJar();
+ downloadedFile = file.get().getJar();
}
response.setHeader("Sonar-MD5", downloadedFile.getMd5());
try (InputStream input = FileUtils.openInputStream(downloadedFile.getFile())) {
*/
package org.sonar.server.plugins.ws;
-import com.google.common.base.Optional;
import java.util.Objects;
+import java.util.Optional;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
package org.sonar.server.plugins.ws;
import com.google.common.io.Resources;
-import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.SortedSet;
import java.util.function.Function;
+import java.util.stream.Collectors;
+import javax.annotation.Nullable;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.plugin.PluginDto;
-import org.sonar.server.plugins.InstalledPlugin;
-import org.sonar.server.plugins.PluginFileSystem;
+import org.sonar.db.plugin.PluginDto.Type;
+import org.sonar.server.plugins.PluginType;
+import org.sonar.server.plugins.ServerPlugin;
+import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.updatecenter.common.Plugin;
public class InstalledAction implements PluginsWsAction {
private static final String ARRAY_PLUGINS = "plugins";
private static final String FIELD_CATEGORY = "category";
+ private static final String PARAM_TYPE = "type";
- private final PluginFileSystem pluginFileSystem;
+ private final ServerPluginRepository serverPluginRepository;
private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
private final DbClient dbClient;
- public InstalledAction(PluginFileSystem pluginFileSystem,
- UpdateCenterMatrixFactory updateCenterMatrixFactory, DbClient dbClient) {
- this.pluginFileSystem = pluginFileSystem;
+ public InstalledAction(ServerPluginRepository serverPluginRepository, UpdateCenterMatrixFactory updateCenterMatrixFactory, DbClient dbClient) {
+ this.serverPluginRepository = serverPluginRepository;
this.updateCenterMatrixFactory = updateCenterMatrixFactory;
this.dbClient = dbClient;
}
.setDescription("Get the list of all the plugins installed on the SonarQube instance, sorted by plugin name.")
.setSince("5.2")
.setChangelog(
+ new Change("8.0", "The 'documentationPath' field is added"),
+ new Change("7.0", "The fields 'compressedHash' and 'compressedFilename' are added"),
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("7.0", "The fields 'compressedHash' and 'compressedFilename' are added"),
- new Change("8.0", "The 'documentationPath' field is added"))
+ new Change("6.6", "The 'updatedAt' field is added"))
.setHandler(this)
.setResponseExample(Resources.getResource(this.getClass(), "example-installed_plugins.json"));
"<li>%s - category as defined in the Update Center. A connection to the Update Center is needed</li>" +
"</lu>", FIELD_CATEGORY))
.setSince("5.6");
+
+ action.createParam(PARAM_TYPE)
+ .setInternal(true)
+ .setSince("8.5")
+ .setPossibleValues(Type.values())
+ .setDescription("Allows to filter plugins by type");
}
@Override
public void handle(Request request, Response response) throws Exception {
- Collection<InstalledPlugin> installedPlugins = loadInstalledPlugins();
+ String typeParam = request.param(PARAM_TYPE);
+ SortedSet<ServerPlugin> installedPlugins = loadInstalledPlugins(typeParam);
Map<String, PluginDto> dtosByKey;
try (DbSession dbSession = dbClient.openSession(false)) {
dtosByKey = dbClient.pluginDao().selectAll(dbSession).stream().collect(toMap(PluginDto::getKee, Function.identity()));
json.name(ARRAY_PLUGINS);
json.beginArray();
- for (InstalledPlugin installedPlugin : copyOf(NAME_KEY_COMPARATOR, installedPlugins)) {
+ for (ServerPlugin installedPlugin : 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());
json.close();
}
- private SortedSet<InstalledPlugin> loadInstalledPlugins() {
- return copyOf(NAME_KEY_COMPARATOR, pluginFileSystem.getInstalledFiles());
+ private SortedSet<ServerPlugin> loadInstalledPlugins(@Nullable String typeParam) {
+ if (typeParam != null) {
+ return copyOf(NAME_KEY_COMPARATOR, serverPluginRepository.getPlugins().stream()
+ .filter(serverPlugin -> serverPlugin.getType().equals(PluginType.valueOf(typeParam)))
+ .collect(Collectors.toSet()));
+ }
+ return copyOf(NAME_KEY_COMPARATOR, serverPluginRepository.getPlugins());
}
}
*/
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;
import java.util.Set;
-import javax.annotation.Nonnull;
+import java.util.stream.Collectors;
import org.sonar.api.server.ws.Change;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.server.user.UserSession;
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;
private final UserSession userSession;
private final PluginDownloader pluginDownloader;
- private final ServerPluginRepository installer;
+ private final ServerPluginRepository serverPluginRepository;
private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
private final PluginUninstaller pluginUninstaller;
public PendingAction(UserSession userSession, PluginDownloader pluginDownloader,
- ServerPluginRepository installer, PluginUninstaller pluginUninstaller, UpdateCenterMatrixFactory updateCenterMatrixFactory) {
+ ServerPluginRepository serverPluginRepository, PluginUninstaller pluginUninstaller, UpdateCenterMatrixFactory updateCenterMatrixFactory) {
this.userSession = userSession;
this.pluginDownloader = pluginDownloader;
- this.installer = installer;
+ this.serverPluginRepository = serverPluginRepository;
this.pluginUninstaller = pluginUninstaller;
this.updateCenterMatrixFactory = updateCenterMatrixFactory;
}
private void writePlugins(JsonWriter json, Map<String, Plugin> compatiblePluginsByKey) {
Collection<PluginInfo> uninstalledPlugins = pluginUninstaller.getUninstalledPlugins();
Collection<PluginInfo> downloadedPlugins = pluginDownloader.getDownloadedPlugins();
- Collection<PluginInfo> installedPlugins = installer.getPluginInfos();
- MatchPluginKeys matchPluginKeys = new MatchPluginKeys(from(installedPlugins).transform(PluginInfoToKey.INSTANCE).toSet());
+ Collection<PluginInfo> installedPlugins = serverPluginRepository.getPluginInfos();
+ Set<String> installedPluginKeys = installedPlugins.stream().map(PluginInfo::getKey).collect(Collectors.toSet());
Collection<PluginInfo> newPlugins = new ArrayList<>();
Collection<PluginInfo> updatedPlugins = new ArrayList<>();
for (PluginInfo pluginInfo : downloadedPlugins) {
- if (matchPluginKeys.apply(pluginInfo)) {
+ if (installedPluginKeys.contains(pluginInfo.getKey())) {
updatedPlugins.add(pluginInfo);
} else {
newPlugins.add(pluginInfo);
}
json.endArray();
}
-
- private enum PluginInfoToKey implements Function<PluginInfo, String> {
- INSTANCE;
-
- @Override
- public String apply(@Nonnull PluginInfo input) {
- return input.getKey();
- }
- }
-
- private static class MatchPluginKeys implements Predicate<PluginInfo> {
- private final Set<String> pluginKeys;
-
- private MatchPluginKeys(Collection<String> pluginKeys) {
- this.pluginKeys = copyOf(pluginKeys);
- }
-
- @Override
- public boolean apply(@Nonnull PluginInfo input) {
- return pluginKeys.contains(input.getKey());
- }
- }
}
package org.sonar.server.plugins.ws;
import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Optional;
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.Optional;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.core.platform.PluginInfo;
import org.sonar.db.plugin.PluginDto;
-import org.sonar.server.plugins.InstalledPlugin;
+import org.sonar.server.plugins.ServerPlugin;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.server.plugins.edition.EditionBundledPlugins;
import org.sonar.updatecenter.common.Artifact;
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())
+ public static final Comparator<ServerPlugin> NAME_KEY_COMPARATOR = Comparator
+ .comparing((java.util.function.Function<ServerPlugin, 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(Plugin::getName)
// prevent instantiation
}
- public static void writePluginInfo(JsonWriter json, PluginInfo pluginInfo, @Nullable String category, @Nullable PluginDto pluginDto, @Nullable InstalledPlugin installedFile) {
+ public static void writePluginInfo(JsonWriter json, PluginInfo pluginInfo, @Nullable String category, @Nullable PluginDto pluginDto, @Nullable ServerPlugin installedFile) {
json.beginObject();
json.prop(PROPERTY_KEY, pluginInfo.getKey());
json.prop(PROPERTY_NAME, pluginInfo.getName());
json.prop(PROPERTY_UPDATED_AT, pluginDto.getUpdatedAt());
}
if (installedFile != null) {
- json.prop(PROPERTY_FILENAME, installedFile.getLoadedJar().getFile().getName());
+ json.prop(PROPERTY_FILENAME, installedFile.getJar().getFile().getName());
json.prop(PROPERTY_SONARLINT_SUPPORTED, installedFile.getPluginInfo().isSonarLintSupported());
- json.prop(PROPERTY_HASH, installedFile.getLoadedJar().getMd5());
+ json.prop(PROPERTY_HASH, installedFile.getJar().getMd5());
}
json.endObject();
}
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
-import org.sonar.core.platform.PluginInfo;
import org.sonar.server.plugins.PluginUninstaller;
-import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.user.UserSession;
-import static java.lang.String.format;
-import static org.sonar.server.plugins.edition.EditionBundledPlugins.isEditionBundled;
-
/**
* Implementation of the {@code uninstall} action for the Plugins WebService.
*/
public class UninstallAction implements PluginsWsAction {
private static final String PARAM_KEY = "key";
- private final ServerPluginRepository serverPluginRepository;
private final PluginUninstaller pluginUninstaller;
private final UserSession userSession;
- public UninstallAction(ServerPluginRepository serverPluginRepository, PluginUninstaller pluginUninstaller, UserSession userSession) {
- this.serverPluginRepository = serverPluginRepository;
+ public UninstallAction(PluginUninstaller pluginUninstaller, UserSession userSession) {
this.pluginUninstaller = pluginUninstaller;
this.userSession = userSession;
}
@Override
public void handle(Request request, Response response) throws Exception {
userSession.checkIsSystemAdministrator();
-
- String key = request.mandatoryParam(PARAM_KEY);
- PluginInfo pluginInfo = serverPluginRepository.getPluginInfo(key);
- if (pluginInfo != null) {
- if (isEditionBundled(pluginInfo)) {
- throw new IllegalArgumentException(format(
- "SonarSource commercial plugin with key '%s' can only be uninstalled as part of a SonarSource edition",
- pluginInfo.getKey()));
- }
- pluginUninstaller.uninstall(key);
- }
+ pluginUninstaller.uninstall(request.mandatoryParam(PARAM_KEY));
response.noContent();
}
-
}
*/
package org.sonar.server.plugins.ws;
-import com.google.common.base.Optional;
-import com.google.common.base.Predicate;
-import com.google.common.collect.Iterables;
+import java.util.Optional;
import javax.annotation.Nonnull;
-import javax.annotation.Nullable;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
PluginUpdate pluginUpdate = MISSING_PLUGIN;
if (updateCenter.isPresent()) {
- pluginUpdate = Iterables.find(
- updateCenter.get().findPluginUpdates(),
- new PluginKeyPredicate(key),
- MISSING_PLUGIN
- );
+ pluginUpdate = updateCenter.get().findPluginUpdates().stream()
+ .filter(update -> update != null && key.equals(update.getPlugin().getKey()))
+ .findFirst().orElse(MISSING_PLUGIN);
}
if (pluginUpdate == MISSING_PLUGIN) {
}
return pluginUpdate;
}
-
- private static class PluginKeyPredicate implements Predicate<PluginUpdate> {
- private final String key;
-
- public PluginKeyPredicate(String key) {
- this.key = key;
- }
-
- @Override
- public boolean apply(@Nullable PluginUpdate input) {
- return input != null && key.equals(input.getPlugin().getKey());
- }
- }
}
*/
package org.sonar.server.plugins.ws;
-import com.google.common.base.Optional;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.collect.Ordering;
import com.google.common.io.Resources;
import java.util.Collection;
import java.util.List;
+import java.util.Optional;
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.resources.Language;
import org.sonar.server.plugins.ServerPluginRepository;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
when(repo.getPluginKey(lang1)).thenReturn("plugin1");
when(repo.getPluginKey(lang2)).thenReturn("plugin2");
- exception.expect(IllegalStateException.class);
- exception.expectMessage("There are two languages declared with the same key 'key' declared by the plugins 'plugin1' and 'plugin2'. "
- + "Please uninstall one of the conflicting plugins.");
LanguageValidation languageValidation = new LanguageValidation(repo, lang1, lang2);
- languageValidation.start();
+ assertThatThrownBy(languageValidation::start)
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessage("There are two languages declared with the same key 'key' declared by the plugins 'plugin1' and 'plugin2'. "
+ + "Please uninstall one of the conflicting plugins.");
}
@Test
when(lang2.getKey()).thenReturn("key2");
ServerPluginRepository repo = mock(ServerPluginRepository.class);
+
when(repo.getPluginKey(lang1)).thenReturn("plugin1");
when(repo.getPluginKey(lang2)).thenReturn("plugin2");
*/
package org.sonar.server.platform.ws;
-import com.google.common.base.Optional;
+import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.sonar.api.server.ws.WebService;
@Test
public void empty_array_is_returned_when_update_center_is_unavailable() {
- when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.absent());
+ when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.empty());
TestResponse response = tester.newRequest().execute();
*/
package org.sonar.server.plugins.ws;
-import com.google.common.base.Optional;
+import java.util.Optional;
import org.junit.Before;
import org.sonar.api.utils.DateUtils;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
*/
package org.sonar.server.plugins.ws;
-import com.google.common.base.Optional;
+import java.util.Optional;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
@Test
public void empty_array_is_returned_when_update_center_is_not_accessible() {
logInAsSystemAdministrator();
- when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.absent());
+ when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.empty());
TestResponse response = tester.newRequest().execute();
import org.sonar.api.server.ws.WebService;
import org.sonar.core.platform.PluginInfo;
import org.sonar.server.exceptions.NotFoundException;
-import org.sonar.server.plugins.InstalledPlugin;
-import org.sonar.server.plugins.InstalledPlugin.FileAndMd5;
-import org.sonar.server.plugins.PluginFileSystem;
+import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5;
+import org.sonar.server.plugins.PluginType;
+import org.sonar.server.plugins.ServerPlugin;
+import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsAction;
import org.sonar.server.ws.WsActionTester;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@Rule
public ExpectedException expectedException = ExpectedException.none();
- private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class);
- private WsAction underTest = new DownloadAction(pluginFileSystem);
- private WsActionTester tester = new WsActionTester(underTest);
+ private final ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
+ private final WsAction underTest = new DownloadAction(serverPluginRepository);
+ private final WsActionTester tester = new WsActionTester(underTest);
@Test
public void test_definition() {
@Test
public void return_404_if_plugin_not_found() {
- when(pluginFileSystem.getInstalledPlugin("foo")).thenReturn(Optional.empty());
+ when(serverPluginRepository.findPlugin("foo")).thenReturn(Optional.empty());
- expectedException.expect(NotFoundException.class);
- expectedException.expectMessage("Plugin foo not found");
-
- tester.newRequest()
- .setParam("plugin", "foo")
- .execute();
+ TestRequest request = tester.newRequest().setParam("plugin", "foo");
+ assertThatThrownBy(request::execute)
+ .isInstanceOf(NotFoundException.class)
+ .hasMessage("Plugin foo not found");
}
@Test
public void return_jar_if_plugin_exists() throws Exception {
- InstalledPlugin plugin = newPlugin();
- when(pluginFileSystem.getInstalledPlugin(plugin.getPluginInfo().getKey())).thenReturn(Optional.of(plugin));
-
+ ServerPlugin plugin = newPlugin();
+ 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.getLoadedJar().getMd5());
+ assertThat(response.getHeader("Sonar-MD5")).isEqualTo(plugin.getJar().getMd5());
assertThat(response.getHeader("Sonar-Compression")).isNull();
assertThat(response.getMediaType()).isEqualTo("application/java-archive");
- verifySameContent(response, plugin.getLoadedJar().getFile());
+ verifySameContent(response, plugin.getJar().getFile());
}
@Test
public void return_uncompressed_jar_if_client_does_not_accept_compression() throws Exception {
- InstalledPlugin plugin = newCompressedPlugin();
- when(pluginFileSystem.getInstalledPlugin(plugin.getPluginInfo().getKey())).thenReturn(Optional.of(plugin));
+ 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.getLoadedJar().getMd5());
+ 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.getLoadedJar().getFile());
+ verifySameContent(response, plugin.getJar().getFile());
}
@Test
public void return_uncompressed_jar_if_client_requests_unsupported_compression() throws Exception {
- InstalledPlugin plugin = newCompressedPlugin();
- when(pluginFileSystem.getInstalledPlugin(plugin.getPluginInfo().getKey())).thenReturn(Optional.of(plugin));
+ 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.getLoadedJar().getMd5());
+ 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.getLoadedJar().getFile());
+ verifySameContent(response, plugin.getJar().getFile());
}
@Test
public void return_compressed_jar_if_client_accepts_pack200() throws Exception {
- InstalledPlugin plugin = newCompressedPlugin();
- when(pluginFileSystem.getInstalledPlugin(plugin.getPluginInfo().getKey())).thenReturn(Optional.of(plugin));
+ 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.getCompressedJar().getMd5());
- assertThat(response.getHeader("Sonar-UncompressedMD5")).isEqualTo(plugin.getLoadedJar().getMd5());
+ 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.getCompressedJar().getFile());
+ verifySameContent(response, plugin.getCompressed().getFile());
}
- private InstalledPlugin newPlugin() throws IOException {
+ private ServerPlugin newPlugin() throws IOException {
FileAndMd5 jar = new FileAndMd5(temp.newFile());
- return new InstalledPlugin(new PluginInfo("foo"), jar, null);
+ return new ServerPlugin(new PluginInfo("foo"), PluginType.BUNDLED, null, jar, null, null);
}
- private InstalledPlugin newCompressedPlugin() throws IOException {
+ private ServerPlugin newCompressedPlugin() throws IOException {
FileAndMd5 jar = new FileAndMd5(temp.newFile());
FileAndMd5 compressedJar = new FileAndMd5(temp.newFile());
- return new InstalledPlugin(new PluginInfo("foo"), jar, compressedJar);
+ return new ServerPlugin(new PluginInfo("foo"), PluginType.BUNDLED, null, jar, compressedJar, null);
}
private static void verifySameContent(TestResponse response, File file) throws IOException {
*/
package org.sonar.server.plugins.ws;
-import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
import com.tngtech.java.junit.dataprovider.UseDataProvider;
+import java.util.Optional;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@RunWith(DataProviderRunner.class)
public class InstallActionTest {
- private static final String DUMMY_CONTROLLER_KEY = "dummy";
private static final String ACTION_KEY = "install";
private static final String KEY_PARAM = "key";
private static final String PLUGIN_KEY = "pluginKey";
@Test
public void IAE_is_raised_when_update_center_is_unavailable() {
logInAsSystemAdministrator();
- when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.absent());
+ when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.empty());
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("No plugin with key 'pluginKey'");
*/
package org.sonar.server.plugins.ws;
-import com.google.common.base.Optional;
import com.hazelcast.internal.json.Json;
+import com.hazelcast.internal.json.JsonArray;
import com.hazelcast.internal.json.JsonObject;
+import com.hazelcast.internal.json.JsonValue;
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.io.IOException;
+import java.util.Arrays;
+import java.util.Optional;
import java.util.Random;
import org.junit.Rule;
import org.junit.Test;
import org.sonar.api.utils.System2;
import org.sonar.core.platform.PluginInfo;
import org.sonar.db.DbTester;
-import org.sonar.server.plugins.InstalledPlugin;
-import org.sonar.server.plugins.InstalledPlugin.FileAndMd5;
-import org.sonar.server.plugins.PluginFileSystem;
+import org.sonar.db.plugin.PluginDto.Type;
+import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5;
+import org.sonar.server.plugins.PluginType;
+import org.sonar.server.plugins.ServerPlugin;
+import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.server.ws.WsActionTester;
import org.sonar.updatecenter.common.Plugin;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import static org.sonar.test.JsonAssert.assertJson;
public DbTester db = DbTester.create(System2.INSTANCE);
private UpdateCenterMatrixFactory updateCenterMatrixFactory = mock(UpdateCenterMatrixFactory.class, RETURNS_DEEP_STUBS);
- private PluginFileSystem pluginFileSystem = mock(PluginFileSystem.class);
- private InstalledAction underTest = new InstalledAction(pluginFileSystem, updateCenterMatrixFactory, db.getDbClient());
+ private ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
+ private InstalledAction underTest = new InstalledAction(serverPluginRepository, updateCenterMatrixFactory, db.getDbClient());
private WsActionTester tester = new WsActionTester(underTest);
@DataProvider
@Test
public void empty_array_when_update_center_is_unavailable() {
- when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.absent());
+ when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.empty());
String response = tester.newRequest().execute().getInput();
assertJson(response).withStrictArrayOrder().isSimilarTo(JSON_EMPTY_PLUGIN_LIST);
}
+ @Test
+ public void filter_by_plugin_type() throws IOException {
+ when(serverPluginRepository.getPlugins()).thenReturn(
+ Arrays.asList(
+ newInstalledPlugin(new PluginInfo("foo-external-1")
+ .setName("foo-external-1"),
+ PluginType.EXTERNAL),
+ newInstalledPlugin(new PluginInfo("foo-bundled-1")
+ .setName("foo-bundled-1"),
+ PluginType.BUNDLED),
+ newInstalledPlugin(new PluginInfo("foo-external-2")
+ .setName("foo-external-2"),
+ PluginType.EXTERNAL)));
+ db.pluginDbTester().insertPlugin(
+ p -> p.setKee("foo-external-1"),
+ p -> p.setType(Type.EXTERNAL),
+ p -> p.setUpdatedAt(100L));
+ db.pluginDbTester().insertPlugin(
+ p -> p.setKee("foo-bundled-1"),
+ p -> p.setType(Type.BUNDLED),
+ p -> p.setUpdatedAt(101L));
+ db.pluginDbTester().insertPlugin(
+ p -> p.setKee("foo-external-2"),
+ p -> p.setType(Type.EXTERNAL),
+ p -> p.setUpdatedAt(102L));
+
+ // no type param
+ String response = tester.newRequest().execute().getInput();
+
+ JsonArray jsonArray = Json.parse(response).asObject().get("plugins").asArray();
+ assertThat(jsonArray).hasSize(3);
+ assertThat(jsonArray).extracting(JsonValue::asObject)
+ .extracting(members -> members.get("key").asString())
+ .containsExactlyInAnyOrder("foo-external-1", "foo-bundled-1", "foo-external-2");
+
+ // type param == BUNDLED
+ response = tester.newRequest().setParam("type", "BUNDLED").execute().getInput();
+
+ jsonArray = Json.parse(response).asObject().get("plugins").asArray();
+ assertThat(jsonArray).hasSize(1);
+ assertThat(jsonArray).extracting(JsonValue::asObject)
+ .extracting(members -> members.get("key").asString())
+ .containsExactlyInAnyOrder("foo-bundled-1");
+
+ // type param == EXTERNAL
+ response = tester.newRequest().setParam("type", "EXTERNAL").execute().getInput();
+
+ jsonArray = Json.parse(response).asObject().get("plugins").asArray();
+ assertThat(jsonArray).hasSize(2);
+ assertThat(jsonArray).extracting(JsonValue::asObject)
+ .extracting(members -> members.get("key").asString())
+ .containsExactlyInAnyOrder("foo-external-1", "foo-external-2");
+ }
+
@Test
public void empty_fields_are_not_serialized_to_json() throws IOException {
- when(pluginFileSystem.getInstalledFiles()).thenReturn(
+ when(serverPluginRepository.getPlugins()).thenReturn(
singletonList(newInstalledPlugin(new PluginInfo("foo")
.setName("")
.setDescription("")
.setIssueTrackerUrl(""))));
db.pluginDbTester().insertPlugin(
p -> p.setKee("foo"),
+ p -> p.setType(Type.EXTERNAL),
p -> p.setUpdatedAt(100L));
String response = tester.newRequest().execute().getInput();
assertThat(json.get("organizationUrl")).isNull();
assertThat(json.get("homepageUrl")).isNull();
assertThat(json.get("issueTrackerUrl")).isNull();
+ }
+ private ServerPlugin newInstalledPlugin(PluginInfo plugin) throws IOException {
+ return newInstalledPlugin(plugin, PluginType.BUNDLED);
}
- private InstalledPlugin newInstalledPlugin(PluginInfo plugin) throws IOException {
+ private ServerPlugin newInstalledPlugin(PluginInfo plugin, PluginType type) throws IOException {
FileAndMd5 jar = new FileAndMd5(temp.newFile());
- return new InstalledPlugin(plugin.setJarFile(jar.getFile()), jar, null);
+ return new ServerPlugin(plugin, type, null, jar, null, null);
}
- private InstalledPlugin newInstalledPluginWithCompression(PluginInfo plugin) throws IOException {
+ private ServerPlugin 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);
+ return new ServerPlugin(plugin, PluginType.BUNDLED, null, jar, compressedJar, null);
}
@Test
public void return_default_fields() throws Exception {
- InstalledPlugin plugin = newInstalledPlugin(new PluginInfo("foo")
+ ServerPlugin plugin = newInstalledPlugin(new PluginInfo("foo")
.setName("plugName")
.setDescription("desc_it")
.setVersion(Version.create("1.0"))
.setIssueTrackerUrl("issueTracker_url")
.setImplementationBuild("sou_rev_sha1")
.setSonarLintSupported(true));
- when(pluginFileSystem.getInstalledFiles()).thenReturn(singletonList(plugin));
+ when(serverPluginRepository.getPlugins()).thenReturn(singletonList(plugin));
db.pluginDbTester().insertPlugin(
p -> p.setKee(plugin.getPluginInfo().getKey()),
+ p -> p.setType(Type.valueOf(plugin.getType().name())),
p -> p.setUpdatedAt(100L));
String response = tester.newRequest().execute().getInput();
- verifyZeroInteractions(updateCenterMatrixFactory);
+ verifyNoMoreInteractions(updateCenterMatrixFactory);
assertJson(response).isSimilarTo(
"{" +
" \"plugins\":" +
" \"issueTrackerUrl\": \"issueTracker_url\"," +
" \"implementationBuild\": \"sou_rev_sha1\"," +
" \"sonarLintSupported\": true," +
- " \"filename\": \"" + plugin.getLoadedJar().getFile().getName() + "\"," +
- " \"hash\": \"" + plugin.getLoadedJar().getMd5() + "\"," +
+ " \"filename\": \"" + plugin.getJar().getFile().getName() + "\"," +
+ " \"hash\": \"" + plugin.getJar().getMd5() + "\"," +
" \"updatedAt\": 100" +
" }" +
" ]" +
@Test
public void return_compression_fields_if_available() throws Exception {
- InstalledPlugin plugin = newInstalledPluginWithCompression(new PluginInfo("foo")
+ ServerPlugin plugin = newInstalledPluginWithCompression(new PluginInfo("foo")
.setName("plugName")
.setDescription("desc_it")
.setVersion(Version.create("1.0"))
.setImplementationBuild("sou_rev_sha1")
.setDocumentationPath("static/documentation.md")
.setSonarLintSupported(true));
- when(pluginFileSystem.getInstalledFiles()).thenReturn(singletonList(plugin));
+ 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();
" \"implementationBuild\": \"sou_rev_sha1\"," +
" \"sonarLintSupported\": true," +
" \"documentationPath\": \"static/documentation.md\"," +
- " \"filename\": \"" + plugin.getLoadedJar().getFile().getName() + "\"," +
- " \"hash\": \"" + plugin.getLoadedJar().getMd5() + "\"," +
+ " \"filename\": \"" + plugin.getJar().getFile().getName() + "\"," +
+ " \"hash\": \"" + plugin.getJar().getMd5() + "\"," +
" \"updatedAt\": 100" +
" }" +
" ]" +
public void category_is_returned_when_in_additional_fields() throws Exception {
String jarFilename = getClass().getSimpleName() + "/" + "some.jar";
File jar = new File(getClass().getResource(jarFilename).toURI());
- when(pluginFileSystem.getInstalledFiles()).thenReturn(asList(
- new InstalledPlugin(new PluginInfo("plugKey")
+ when(serverPluginRepository.getPlugins()).thenReturn(asList(
+ newInstalledPlugin(new PluginInfo("plugKey")
.setName("plugName")
.setDescription("desc_it")
.setVersion(Version.create("1.0"))
.setOrganizationUrl("org_url")
.setHomepageUrl("homepage_url")
.setIssueTrackerUrl("issueTracker_url")
- .setImplementationBuild("sou_rev_sha1")
- .setJarFile(jar), new FileAndMd5(jar), null)));
+ .setImplementationBuild("sou_rev_sha1"))));
+ // .setJarFile(jar), new FileAndMd5(jar), null
UpdateCenter updateCenter = mock(UpdateCenter.class);
when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.of(updateCenter));
when(updateCenter.findAllCompatiblePlugins()).thenReturn(
db.pluginDbTester().insertPlugin(
p -> p.setKee("plugKey"),
+ p -> p.setType(Type.EXTERNAL),
p -> p.setFileHash("abcdplugKey"),
p -> p.setUpdatedAt(111111L));
@Test
public void plugins_are_sorted_by_name_then_key_and_only_one_plugin_can_have_a_specific_name() throws IOException {
- when(pluginFileSystem.getInstalledFiles()).thenReturn(
+ when(serverPluginRepository.getPlugins()).thenReturn(
asList(
plugin("A", "name2"),
plugin("B", "name1"),
db.pluginDbTester().insertPlugin(
p -> p.setKee("A"),
+ p -> p.setType(Type.EXTERNAL),
p -> p.setFileHash("abcdA"),
p -> p.setUpdatedAt(111111L));
db.pluginDbTester().insertPlugin(
p -> p.setKee("B"),
+ p -> p.setType(Type.EXTERNAL),
p -> p.setFileHash("abcdB"),
p -> p.setUpdatedAt(222222L));
db.pluginDbTester().insertPlugin(
p -> p.setKee("C"),
+ p -> p.setType(Type.EXTERNAL),
p -> p.setFileHash("abcdC"),
p -> p.setUpdatedAt(333333L));
db.pluginDbTester().insertPlugin(
p -> p.setKee("D"),
+ p -> p.setType(Type.EXTERNAL),
p -> p.setFileHash("abcdD"),
p -> p.setUpdatedAt(444444L));
String organization = random.nextBoolean() ? "SonarSource" : "SONARSOURCE";
String pluginKey = "plugKey";
File jar = new File(getClass().getResource(jarFilename).toURI());
- when(pluginFileSystem.getInstalledFiles()).thenReturn(asList(
- new InstalledPlugin(new PluginInfo(pluginKey)
+ when(serverPluginRepository.getPlugins()).thenReturn(asList(
+ new ServerPlugin(new PluginInfo(pluginKey)
.setName("plugName")
.setVersion(Version.create("1.0"))
.setLicense(license)
.setOrganizationName(organization)
- .setImplementationBuild("sou_rev_sha1")
- .setJarFile(jar), new FileAndMd5(jar), null)));
+ .setImplementationBuild("sou_rev_sha1"),
+ PluginType.BUNDLED,
+ null,
+ new FileAndMd5(jar), new FileAndMd5(jar), null)));
db.pluginDbTester().insertPlugin(
p -> p.setKee(pluginKey),
+ p -> p.setType(Type.BUNDLED),
p -> p.setFileHash("abcdplugKey"),
p -> p.setUpdatedAt(111111L));
// ensure flag editionBundled is computed from jar info by enabling datacenter with other organization and license values
@Test
public void only_one_plugin_can_have_a_specific_name_and_key() throws IOException {
- when(pluginFileSystem.getInstalledFiles()).thenReturn(
+ when(serverPluginRepository.getPlugins()).thenReturn(
asList(
plugin("A", "name2"),
plugin("A", "name2")));
db.pluginDbTester().insertPlugin(
p -> p.setKee("A"),
+ p -> p.setType(Type.EXTERNAL),
p -> p.setFileHash("abcdA"),
p -> p.setUpdatedAt(111111L));
assertThat(response).containsOnlyOnce("name2");
}
- private InstalledPlugin plugin(String key, String name) throws IOException {
+ private ServerPlugin 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);
+ return new ServerPlugin(info, PluginType.BUNDLED, null, new FileAndMd5(file), null, null);
}
}
*/
package org.sonar.server.plugins.ws;
-import com.google.common.base.Optional;
import java.util.ArrayList;
import java.util.List;
+import java.util.Optional;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.sonar.test.JsonAssert.assertJson;
public class PendingActionTest {
-
- private static final String DUMMY_CONTROLLER_KEY = "dummy";
-
@Rule
public UserSessionRule userSession = UserSessionRule.standalone();
@Rule
@Test
public void empty_arrays_are_returned_when_update_center_is_unavailable() {
logInAsSystemAdministrator();
- when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.absent());
+ when(updateCenterMatrixFactory.getUpdateCenter(false)).thenReturn(Optional.empty());
TestResponse response = tester.newRequest().execute();
import org.sonar.api.utils.text.JsonWriter;
import org.sonar.core.platform.PluginInfo;
import org.sonar.db.plugin.PluginDto;
-import org.sonar.server.plugins.InstalledPlugin;
-import org.sonar.server.plugins.InstalledPlugin.FileAndMd5;
+import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5;
+import org.sonar.server.plugins.ServerPlugin;
import org.sonar.server.ws.DumbResponse;
import org.sonar.updatecenter.common.Plugin;
import org.sonar.updatecenter.common.PluginUpdate;
import static org.assertj.core.api.Assertions.assertThat;
import static org.sonar.api.utils.DateUtils.parseDate;
+import static org.sonar.server.plugins.PluginType.BUNDLED;
import static org.sonar.server.plugins.ws.PluginWSCommons.toJSon;
import static org.sonar.test.JsonAssert.assertJson;
import static org.sonar.updatecenter.common.PluginUpdate.Status.COMPATIBLE;
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);
+ ServerPlugin serverPlugin = new ServerPlugin(gitPluginInfo(), BUNDLED, null, loadedJar, compressedJar, null);
- PluginWSCommons.writePluginInfo(jsonWriter, gitPluginInfo(), null, dto, installedFile);
+ PluginWSCommons.writePluginInfo(jsonWriter, gitPluginInfo(), null, dto, serverPlugin);
jsonWriter.close();
assertJson(response.outputAsString()).withStrictArrayOrder().isSimilarTo("{" +
- " \"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() + "\"" +
+ " \"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 status_COMPATIBLE_is_displayed_COMPATIBLE_in_JSON() {
assertThat(toJSon(COMPATIBLE)).isEqualTo("COMPATIBLE");
*/
package org.sonar.server.plugins.ws;
-import com.tngtech.java.junit.dataprovider.DataProvider;
import com.tngtech.java.junit.dataprovider.DataProviderRunner;
-import com.tngtech.java.junit.dataprovider.UseDataProvider;
import org.junit.Rule;
import org.junit.Test;
-import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.sonar.api.server.ws.WebService;
-import org.sonar.core.platform.PluginInfo;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.plugins.PluginUninstaller;
-import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.tester.UserSessionRule;
+import org.sonar.server.ws.TestRequest;
import org.sonar.server.ws.TestResponse;
import org.sonar.server.ws.WsActionTester;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
@RunWith(DataProviderRunner.class)
public class UninstallActionTest {
- private static final String CONTROLLER_KEY = "api/plugins";
private static final String ACTION_KEY = "uninstall";
private static final String KEY_PARAM = "key";
private static final String PLUGIN_KEY = "findbugs";
@Rule
public UserSessionRule userSessionRule = UserSessionRule.standalone();
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
- private ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
private PluginUninstaller pluginUninstaller = mock(PluginUninstaller.class);
- private UninstallAction underTest = new UninstallAction(serverPluginRepository, pluginUninstaller, userSessionRule);
+ private UninstallAction underTest = new UninstallAction(pluginUninstaller, userSessionRule);
private WsActionTester tester = new WsActionTester(underTest);
@Test
public void request_fails_with_ForbiddenException_when_user_is_not_logged_in() {
- expectedException.expect(ForbiddenException.class);
- expectedException.expectMessage("Insufficient privileges");
+ TestRequest request = tester.newRequest();
- tester.newRequest().execute();
+ assertThatThrownBy(request::execute)
+ .isInstanceOf(ForbiddenException.class)
+ .hasMessage("Insufficient privileges");
}
@Test
public void request_fails_with_ForbiddenException_when_user_is_not_system_administrator() {
userSessionRule.logIn().setNonSystemAdministrator();
- expectedException.expect(ForbiddenException.class);
- expectedException.expectMessage("Insufficient privileges");
+ TestRequest request = tester.newRequest();
- tester.newRequest().execute();
+ assertThatThrownBy(request::execute)
+ .isInstanceOf(ForbiddenException.class)
+ .hasMessage("Insufficient privileges");
}
@Test
public void IAE_is_raised_when_key_param_is_not_provided() {
logInAsSystemAdministrator();
- expectedException.expect(IllegalArgumentException.class);
-
- tester.newRequest().execute();
- }
-
- @Test
- public void do_not_attempt_uninstall_if_no_plugin_in_repository_for_specified_key() {
- logInAsSystemAdministrator();
- when(serverPluginRepository.getPluginInfo(PLUGIN_KEY)).thenReturn(null);
-
- tester.newRequest()
- .setParam(KEY_PARAM, PLUGIN_KEY)
- .execute();
-
- verifyZeroInteractions(pluginUninstaller);
- }
-
- @Test
- @UseDataProvider("editionBundledOrganizationAndLicense")
- public void IAE_is_raised_when_plugin_is_installed_and_is_edition_bundled(String organization, String license) {
- logInAsSystemAdministrator();
- when(serverPluginRepository.getPluginInfo(PLUGIN_KEY))
- .thenReturn(new PluginInfo(PLUGIN_KEY)
- .setOrganizationName(organization)
- .setLicense(license));
-
- expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("SonarSource commercial plugin with key '" + PLUGIN_KEY + "' can only be uninstalled as part of a SonarSource edition");
-
- tester.newRequest()
- .setParam(KEY_PARAM, PLUGIN_KEY)
- .execute();
- }
-
- @DataProvider
- public static Object[][] editionBundledOrganizationAndLicense() {
- return new Object[][] {
- {"SonarSource", "SonarSource"},
- {"SonarSource", "Commercial"},
- {"sonarsource", "SOnArSOURCE"}
- };
+ TestRequest request = tester.newRequest();
+ assertThatThrownBy(request::execute)
+ .isInstanceOf(IllegalArgumentException.class);
}
@Test
- public void if_plugin_is_installed_uninstallation_is_triggered() {
+ public void uninstaller_is_called() {
logInAsSystemAdministrator();
- when(serverPluginRepository.getPluginInfo(PLUGIN_KEY)).thenReturn(new PluginInfo(PLUGIN_KEY));
TestResponse response = tester.newRequest()
.setParam(KEY_PARAM, PLUGIN_KEY)
*/
package org.sonar.server.plugins.ws;
-import com.google.common.base.Optional;
import com.google.common.collect.ImmutableList;
+import java.util.Optional;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@Test
public void IAE_is_raised_when_update_center_is_unavailable() {
logInAsSystemAdministrator();
- when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.absent());
+ when(updateCenterFactory.getUpdateCenter(anyBoolean())).thenReturn(Optional.empty());
expectedException.expect(IllegalArgumentException.class);
expectedException.expectMessage("No plugin with key 'pluginKey'");
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2020 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.updatecenter.ws;
-
-import org.junit.Test;
-import org.sonar.api.server.ws.WebService;
-import org.sonar.core.platform.PluginInfo;
-import org.sonar.server.plugins.ServerPluginRepository;
-import org.sonar.server.ws.WsActionTester;
-import org.sonar.updatecenter.common.Version;
-
-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.mock;
-import static org.mockito.Mockito.when;
-import static org.sonar.test.JsonAssert.assertJson;
-
-public class InstalledPluginsActionTest {
-
- private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
-
- private WsActionTester ws = new WsActionTester(new InstalledPluginsAction(pluginRepository));
-
- @Test
- public void return_plugins() {
- when(pluginRepository.getPluginInfos()).thenReturn(asList(
- new PluginInfo("java").setName("Java").setVersion(Version.create("3.14")),
- new PluginInfo("xoo").setName("Xoo").setVersion(Version.create("1.0"))));
-
- String result = ws.newRequest().execute().getInput();
-
- assertJson(result).isSimilarTo("[" +
- " {" +
- " \"key\": \"java\",\n" +
- " \"name\": \"Java\",\n" +
- " \"version\": \"3.14\"\n" +
- " }," +
- " {" +
- " \"key\": \"xoo\",\n" +
- " \"name\": \"Xoo\",\n" +
- " \"version\": \"1.0\"\n" +
- " }" +
- "]");
- }
-
- @Test
- public void return_plugins_with_plugin_having_no_version() {
- when(pluginRepository.getPluginInfos()).thenReturn(singletonList(
- new PluginInfo("java").setName("Java")));
-
- String result = ws.newRequest().execute().getInput();
-
- assertJson(result).isSimilarTo("[" +
- " {" +
- " \"key\": \"java\",\n" +
- " \"name\": \"Java\"\n" +
- " }" +
- "]");
- }
-
- @Test
- public void test_example() {
- when(pluginRepository.getPluginInfos()).thenReturn(asList(
- new PluginInfo("findbugs").setName("Findbugs").setVersion(Version.create("2.1")),
- new PluginInfo("l10nfr").setName("French Pack").setVersion(Version.create("1.10")),
- new PluginInfo("jira").setName("JIRA").setVersion(Version.create("1.2"))));
-
- String result = ws.newRequest().execute().getInput();
-
- assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString());
- }
-
- @Test
- public void test_definition() {
- WebService.Action action = ws.getDef();
- assertThat(action).isNotNull();
- assertThat(action.isInternal()).isTrue();
- assertThat(action.isPost()).isFalse();
- assertThat(action.responseExampleAsString()).isNotEmpty();
- assertThat(action.params()).hasSize(1);
- }
-
-}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.updatecenter.ws;
+
+import org.junit.Test;
+import org.sonar.api.server.ws.WebService;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.ws.WsActionTester;
+import org.sonar.updatecenter.common.Version;
+
+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.mock;
+import static org.mockito.Mockito.when;
+import static org.sonar.test.JsonAssert.assertJson;
+
+public class PluginsActionTestFilesAndMD5 {
+
+ private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
+
+ private WsActionTester ws = new WsActionTester(new InstalledPluginsAction(pluginRepository));
+
+ @Test
+ public void return_plugins() {
+ when(pluginRepository.getPluginInfos()).thenReturn(asList(
+ new PluginInfo("java").setName("Java").setVersion(Version.create("3.14")),
+ new PluginInfo("xoo").setName("Xoo").setVersion(Version.create("1.0"))));
+
+ String result = ws.newRequest().execute().getInput();
+
+ assertJson(result).isSimilarTo("[" +
+ " {" +
+ " \"key\": \"java\",\n" +
+ " \"name\": \"Java\",\n" +
+ " \"version\": \"3.14\"\n" +
+ " }," +
+ " {" +
+ " \"key\": \"xoo\",\n" +
+ " \"name\": \"Xoo\",\n" +
+ " \"version\": \"1.0\"\n" +
+ " }" +
+ "]");
+ }
+
+ @Test
+ public void return_plugins_with_plugin_having_no_version() {
+ when(pluginRepository.getPluginInfos()).thenReturn(singletonList(
+ new PluginInfo("java").setName("Java")));
+
+ String result = ws.newRequest().execute().getInput();
+
+ assertJson(result).isSimilarTo("[" +
+ " {" +
+ " \"key\": \"java\",\n" +
+ " \"name\": \"Java\"\n" +
+ " }" +
+ "]");
+ }
+
+ @Test
+ public void test_example() {
+ when(pluginRepository.getPluginInfos()).thenReturn(asList(
+ new PluginInfo("findbugs").setName("Findbugs").setVersion(Version.create("2.1")),
+ new PluginInfo("l10nfr").setName("French Pack").setVersion(Version.create("1.10")),
+ new PluginInfo("jira").setName("JIRA").setVersion(Version.create("1.2"))));
+
+ String result = ws.newRequest().execute().getInput();
+
+ assertJson(result).isSimilarTo(ws.getDef().responseExampleAsString());
+ }
+
+ @Test
+ public void test_definition() {
+ WebService.Action action = ws.getDef();
+ assertThat(action).isNotNull();
+ assertThat(action.isInternal()).isTrue();
+ assertThat(action.isPost()).isFalse();
+ assertThat(action.responseExampleAsString()).isNotEmpty();
+ assertThat(action.params()).hasSize(1);
+ }
+
+}
import org.sonar.api.utils.Durations;
import org.sonar.core.extension.CoreExtensionsInstaller;
import org.sonar.core.platform.ComponentContainer;
+import org.sonar.core.platform.PluginClassLoader;
import org.sonar.core.platform.PluginClassloaderFactory;
-import org.sonar.core.platform.PluginLoader;
import org.sonar.server.es.MigrationEsClientImpl;
import org.sonar.server.l18n.ServerI18n;
import org.sonar.server.platform.DatabaseServerCompatibility;
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.PluginFileSystem;
+import org.sonar.server.plugins.PluginCompressor;
+import org.sonar.server.plugins.PluginJarLoader;
+import org.sonar.server.plugins.PluginUninstaller;
import org.sonar.server.plugins.ServerPluginJarExploder;
+import org.sonar.server.plugins.ServerPluginManager;
import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.plugins.WebServerExtensionInstaller;
WebPagesCache.class,
// plugins
+ PluginJarLoader.class,
ServerPluginRepository.class,
+ ServerPluginManager.class,
ServerPluginJarExploder.class,
- PluginLoader.class,
- PluginFileSystem.class,
+ PluginClassLoader.class,
+ PluginCompressor.class,
PluginClassloaderFactory.class,
InstalledPluginReferentialFactory.class,
WebServerExtensionInstaller.class,
ClusterVerification.class,
LogServerId.class,
LogOAuthWarning.class,
- PluginDownloader.class,
PluginUninstaller.class,
+ PluginDownloader.class,
PageRepository.class,
ResourceTypes.class,
DefaultResourceTypes.get(),
import java.util.Collection;
public class ExplodedPlugin {
-
+ private final PluginInfo pluginInfo;
private final String key;
private final File main;
private final Collection<File> libs;
- public ExplodedPlugin(String key, File main, Collection<File> libs) {
+ public ExplodedPlugin(PluginInfo pluginInfo, String key, File main, Collection<File> libs) {
+ this.pluginInfo = pluginInfo;
this.key = key;
this.main = main;
this.libs = libs;
public Collection<File> getLibs() {
return libs;
}
+
+ public PluginInfo getPluginInfo() {
+ return pluginInfo;
+ }
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.core.platform;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Strings;
+import java.io.Closeable;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.apache.commons.lang.SystemUtils;
+import org.sonar.api.Plugin;
+import org.sonar.api.utils.log.Loggers;
+import org.sonar.updatecenter.common.Version;
+
+import static java.util.Collections.singleton;
+
+/**
+ * Loads the plugin JAR files by creating the appropriate classloaders and by instantiating
+ * the entry point classes as defined in manifests. It assumes that JAR files are compatible with current
+ * environment (minimal sonarqube version, compatibility between plugins, ...):
+ * <ul>
+ * <li>server verifies compatibility of JARs before deploying them at startup (see ServerPluginRepository)</li>
+ * <li>batch loads only the plugins deployed on server (see BatchPluginRepository)</li>
+ * </ul>
+ * <p/>
+ * Plugins have their own isolated classloader, inheriting only from API classes.
+ * Some plugins can extend a "base" plugin, sharing the same classloader.
+ * <p/>
+ * This class is stateless. It does not keep pointers to classloaders and {@link org.sonar.api.Plugin}.
+ */
+public class PluginClassLoader {
+ private static final String[] DEFAULT_SHARED_RESOURCES = {"org/sonar/plugins", "com/sonar/plugins", "com/sonarsource/plugins"};
+ private static final Version COMPATIBILITY_MODE_MAX_VERSION = Version.create("5.2");
+
+ private final PluginClassloaderFactory classloaderFactory;
+
+ public PluginClassLoader(PluginClassloaderFactory classloaderFactory) {
+ this.classloaderFactory = classloaderFactory;
+ }
+
+ public Map<String, Plugin> load(Collection<ExplodedPlugin> plugins) {
+ return load(plugins.stream().collect(Collectors.toMap(ExplodedPlugin::getKey, x -> x)));
+ }
+
+ public Map<String, Plugin> load(Map<String, ExplodedPlugin> pluginsByKey) {
+ Collection<PluginClassLoaderDef> defs = defineClassloaders(pluginsByKey);
+ Map<PluginClassLoaderDef, ClassLoader> classloaders = classloaderFactory.create(defs);
+ return instantiatePluginClasses(classloaders);
+ }
+
+ /**
+ * Defines the different classloaders to be created. Number of classloaders can be
+ * different than number of plugins.
+ */
+ @VisibleForTesting
+ Collection<PluginClassLoaderDef> defineClassloaders(Map<String, ExplodedPlugin> pluginsByKey) {
+ Map<String, PluginClassLoaderDef> classloadersByBasePlugin = new HashMap<>();
+
+ for (ExplodedPlugin plugin : pluginsByKey.values()) {
+ PluginInfo info = plugin.getPluginInfo();
+ String baseKey = basePluginKey(info, pluginsByKey);
+ PluginClassLoaderDef def = classloadersByBasePlugin.get(baseKey);
+ if (def == null) {
+ def = new PluginClassLoaderDef(baseKey);
+ classloadersByBasePlugin.put(baseKey, def);
+ }
+ def.addFiles(singleton(plugin.getMain()));
+ def.addFiles(plugin.getLibs());
+ def.addMainClass(info.getKey(), info.getMainClass());
+
+ for (String defaultSharedResource : DEFAULT_SHARED_RESOURCES) {
+ def.getExportMask().addInclusion(String.format("%s/%s/api/", defaultSharedResource, info.getKey()));
+ }
+
+ // The plugins that extend other plugins can only add some files to classloader.
+ // They can't change metadata like ordering strategy or compatibility mode.
+ if (Strings.isNullOrEmpty(info.getBasePlugin())) {
+ if (info.isUseChildFirstClassLoader()) {
+ Loggers.get(getClass()).warn("Plugin {} [{}] uses a child first classloader which is deprecated", info.getName(), info.getKey());
+ }
+ def.setSelfFirstStrategy(info.isUseChildFirstClassLoader());
+ Version minSqVersion = info.getMinimalSqVersion();
+ boolean compatibilityMode = minSqVersion != null && minSqVersion.compareToIgnoreQualifier(COMPATIBILITY_MODE_MAX_VERSION) < 0;
+ if (compatibilityMode) {
+ Loggers.get(getClass()).warn("API compatibility mode is no longer supported. In case of error, plugin {} [{}] should package its dependencies.",
+ info.getName(), info.getKey());
+ }
+ }
+ }
+ return classloadersByBasePlugin.values();
+
+ }
+
+ /**
+ * Instantiates collection of {@link org.sonar.api.Plugin} according to given metadata and classloaders
+ *
+ * @return the instances grouped by plugin key
+ * @throws IllegalStateException if at least one plugin can't be correctly loaded
+ */
+ private static Map<String, Plugin> instantiatePluginClasses(Map<PluginClassLoaderDef, ClassLoader> classloaders) {
+ // instantiate plugins
+ Map<String, Plugin> instancesByPluginKey = new HashMap<>();
+ for (Map.Entry<PluginClassLoaderDef, ClassLoader> entry : classloaders.entrySet()) {
+ PluginClassLoaderDef def = entry.getKey();
+ ClassLoader classLoader = entry.getValue();
+
+ // the same classloader can be used by multiple plugins
+ for (Map.Entry<String, String> mainClassEntry : def.getMainClassesByPluginKey().entrySet()) {
+ String pluginKey = mainClassEntry.getKey();
+ String mainClass = mainClassEntry.getValue();
+ try {
+ instancesByPluginKey.put(pluginKey, (Plugin) classLoader.loadClass(mainClass).getDeclaredConstructor().newInstance());
+ } catch (UnsupportedClassVersionError e) {
+ throw new IllegalStateException(String.format("The plugin [%s] does not support Java %s", pluginKey, SystemUtils.JAVA_VERSION_TRIMMED), e);
+ } catch (Throwable e) {
+ throw new IllegalStateException(String.format("Fail to instantiate class [%s] of plugin [%s]", mainClass, pluginKey), e);
+ }
+ }
+ }
+ return instancesByPluginKey;
+ }
+
+ public void unload(Collection<Plugin> plugins) {
+ for (Plugin plugin : plugins) {
+ ClassLoader classLoader = plugin.getClass().getClassLoader();
+ if (classLoader instanceof Closeable && classLoader != classloaderFactory.baseClassLoader()) {
+ try {
+ ((Closeable) classLoader).close();
+ } catch (Exception e) {
+ Loggers.get(getClass()).error("Fail to close classloader " + classLoader.toString(), e);
+ }
+ }
+ }
+ }
+
+ /**
+ * Get the root key of a tree of plugins. For example if plugin C depends on B, which depends on A, then
+ * B and C must be attached to the classloader of A. The method returns A in the three cases.
+ */
+ private static String basePluginKey(PluginInfo plugin, Map<String, ExplodedPlugin> pluginsByKey) {
+ String base = plugin.getKey();
+ String parentKey = plugin.getBasePlugin();
+ while (!Strings.isNullOrEmpty(parentKey)) {
+ PluginInfo parentPlugin = pluginsByKey.get(parentKey).getPluginInfo();
+ base = parentPlugin.getKey();
+ parentKey = parentPlugin.getBasePlugin();
+ }
+ return base;
+ }
+}
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
+import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.jar.JarFile;
private static final Joiner SLASH_JOINER = Joiner.on(" / ").skipNulls();
- public static class RequiredPlugin {
-
- private static final Pattern PARSER = Pattern.compile("\\w+:.+");
-
- private final String key;
- private final Version minimalVersion;
-
- public RequiredPlugin(String key, Version minimalVersion) {
- this.key = key;
- this.minimalVersion = minimalVersion;
- }
-
- public String getKey() {
- return key;
- }
-
- public Version getMinimalVersion() {
- return minimalVersion;
- }
-
- public static RequiredPlugin parse(String s) {
- if (!PARSER.matcher(s).matches()) {
- throw new IllegalArgumentException("Manifest field does not have correct format: " + s);
- }
- String[] fields = StringUtils.split(s, ':');
- return new RequiredPlugin(fields[0], Version.create(fields[1]).removeQualifier());
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
- RequiredPlugin that = (RequiredPlugin) o;
- return key.equals(that.key);
- }
-
- @Override
- public int hashCode() {
- return key.hashCode();
- }
-
- @Override
- public String toString() {
- return new StringBuilder().append(key).append(':').append(minimalVersion.getName()).toString();
- }
- }
-
private final String key;
private String name;
return false;
}
PluginInfo info = (PluginInfo) o;
- if (!key.equals(info.key)) {
- return false;
- }
- return !(version != null ? !version.equals(info.version) : info.version != null);
+ return Objects.equals(key, info.key) && Objects.equals(version, info.version);
}
@Override
public int hashCode() {
- int result = key.hashCode();
- result = 31 * result + (version != null ? version.hashCode() : 0);
- return result;
+ return Objects.hash(key, version);
}
@Override
@VisibleForTesting
static PluginInfo create(File jarFile, PluginManifest manifest) {
+ validateManifest(jarFile, manifest);
+ PluginInfo info = new PluginInfo(manifest.getKey());
+ info.fillFields(jarFile, manifest);
+ return info;
+ }
+
+ private static void validateManifest(File jarFile, PluginManifest manifest) {
if (StringUtils.isBlank(manifest.getKey())) {
throw MessageException.of(String.format("File is not a plugin. Please delete it and restart: %s", jarFile.getAbsolutePath()));
}
- PluginInfo info = new PluginInfo(manifest.getKey());
+ }
- info.setJarFile(jarFile);
- info.setName(manifest.getName());
- info.setMainClass(manifest.getMainClass());
- info.setVersion(Version.create(manifest.getVersion()));
- info.setDocumentationPath(getDocumentationPath(jarFile));
+ protected void fillFields(File jarFile, PluginManifest manifest) {
+ setJarFile(jarFile);
+ setName(manifest.getName());
+ setMainClass(manifest.getMainClass());
+ setVersion(Version.create(manifest.getVersion()));
+ setDocumentationPath(getDocumentationPath(jarFile));
// optional fields
- info.setDescription(manifest.getDescription());
- info.setLicense(manifest.getLicense());
- info.setOrganizationName(manifest.getOrganization());
- info.setOrganizationUrl(manifest.getOrganizationUrl());
- info.setDisplayVersion(manifest.getDisplayVersion());
+ setDescription(manifest.getDescription());
+ setLicense(manifest.getLicense());
+ setOrganizationName(manifest.getOrganization());
+ setOrganizationUrl(manifest.getOrganizationUrl());
+ setDisplayVersion(manifest.getDisplayVersion());
String minSqVersion = manifest.getSonarVersion();
if (minSqVersion != null) {
- info.setMinimalSqVersion(Version.create(minSqVersion));
+ setMinimalSqVersion(Version.create(minSqVersion));
}
- info.setHomepageUrl(manifest.getHomepage());
- info.setIssueTrackerUrl(manifest.getIssueTrackerUrl());
- info.setUseChildFirstClassLoader(manifest.isUseChildFirstClassLoader());
- info.setSonarLintSupported(manifest.isSonarLintSupported());
- info.setBasePlugin(manifest.getBasePlugin());
- info.setImplementationBuild(manifest.getImplementationBuild());
- String[] requiredPlugins = manifest.getRequirePlugins();
- if (requiredPlugins != null) {
- Arrays.stream(requiredPlugins)
+ setHomepageUrl(manifest.getHomepage());
+ setIssueTrackerUrl(manifest.getIssueTrackerUrl());
+ setUseChildFirstClassLoader(manifest.isUseChildFirstClassLoader());
+ setSonarLintSupported(manifest.isSonarLintSupported());
+ setBasePlugin(manifest.getBasePlugin());
+ setImplementationBuild(manifest.getImplementationBuild());
+ String[] requiredPluginsFromManifest = manifest.getRequirePlugins();
+ if (requiredPluginsFromManifest != null) {
+ Arrays.stream(requiredPluginsFromManifest)
.map(RequiredPlugin::parse)
.filter(t -> !"license".equals(t.key))
- .forEach(info::addRequiredPlugin);
+ .forEach(this::addRequiredPlugin);
}
- return info;
}
private static String getDocumentationPath(File file) {
return null;
}
+ public static class RequiredPlugin {
+
+ private static final Pattern PARSER = Pattern.compile("\\w+:.+");
+
+ private final String key;
+ private final Version minimalVersion;
+
+ public RequiredPlugin(String key, Version minimalVersion) {
+ this.key = key;
+ this.minimalVersion = minimalVersion;
+ }
+
+ public String getKey() {
+ return key;
+ }
+
+ public Version getMinimalVersion() {
+ return minimalVersion;
+ }
+
+ public static RequiredPlugin parse(String s) {
+ if (!PARSER.matcher(s).matches()) {
+ throw new IllegalArgumentException("Manifest field does not have correct format: " + s);
+ }
+ String[] fields = StringUtils.split(s, ':');
+ return new RequiredPlugin(fields[0], Version.create(fields[1]).removeQualifier());
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+ RequiredPlugin that = (RequiredPlugin) o;
+ return key.equals(that.key);
+ }
+
+ @Override
+ public int hashCode() {
+ return key.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return key + ':' + minimalVersion.getName();
+ }
+ }
}
protected static final String LIB_RELATIVE_PATH_IN_JAR = "META-INF/lib";
- public abstract ExplodedPlugin explode(PluginInfo info);
+ public abstract ExplodedPlugin explode(PluginInfo plugin);
protected Predicate<ZipEntry> newLibFilter() {
return ze -> ze.getName().startsWith(LIB_RELATIVE_PATH_IN_JAR);
}
- protected ExplodedPlugin explodeFromUnzippedDir(String pluginKey, File jarFile, File unzippedDir) {
+ protected ExplodedPlugin explodeFromUnzippedDir(PluginInfo pluginInfo, File jarFile, File unzippedDir) {
File libDir = new File(unzippedDir, PluginJarExploder.LIB_RELATIVE_PATH_IN_JAR);
Collection<File> libs;
if (libDir.isDirectory() && libDir.exists()) {
} else {
libs = Collections.emptyList();
}
- return new ExplodedPlugin(pluginKey, jarFile, libs);
+ return new ExplodedPlugin(pluginInfo, pluginInfo.getKey(), jarFile, libs);
}
}
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2020 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.core.platform;
-
-import com.google.common.annotations.VisibleForTesting;
-import com.google.common.base.Strings;
-import java.io.Closeable;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Map;
-import org.apache.commons.lang.SystemUtils;
-import org.sonar.api.Plugin;
-import org.sonar.api.utils.log.Loggers;
-import org.sonar.updatecenter.common.Version;
-
-import static java.util.Arrays.asList;
-
-/**
- * Loads the plugin JAR files by creating the appropriate classloaders and by instantiating
- * the entry point classes as defined in manifests. It assumes that JAR files are compatible with current
- * environment (minimal sonarqube version, compatibility between plugins, ...):
- * <ul>
- * <li>server verifies compatibility of JARs before deploying them at startup (see ServerPluginRepository)</li>
- * <li>batch loads only the plugins deployed on server (see BatchPluginRepository)</li>
- * </ul>
- * <p/>
- * Plugins have their own isolated classloader, inheriting only from API classes.
- * Some plugins can extend a "base" plugin, sharing the same classloader.
- * <p/>
- * This class is stateless. It does not keep pointers to classloaders and {@link org.sonar.api.Plugin}.
- */
-public class PluginLoader {
-
- private static final String[] DEFAULT_SHARED_RESOURCES = {"org/sonar/plugins", "com/sonar/plugins", "com/sonarsource/plugins"};
-
- private static final Version COMPATIBILITY_MODE_MAX_VERSION = Version.create("5.2");
-
- private final PluginJarExploder jarExploder;
- private final PluginClassloaderFactory classloaderFactory;
-
- public PluginLoader(PluginJarExploder jarExploder, PluginClassloaderFactory classloaderFactory) {
- this.jarExploder = jarExploder;
- this.classloaderFactory = classloaderFactory;
- }
-
- public Map<String, Plugin> load(Map<String, PluginInfo> infoByKeys) {
- Collection<PluginClassLoaderDef> defs = defineClassloaders(infoByKeys);
- Map<PluginClassLoaderDef, ClassLoader> classloaders = classloaderFactory.create(defs);
- return instantiatePluginClasses(classloaders);
- }
-
- /**
- * Defines the different classloaders to be created. Number of classloaders can be
- * different than number of plugins.
- */
- @VisibleForTesting
- Collection<PluginClassLoaderDef> defineClassloaders(Map<String, PluginInfo> infoByKeys) {
- Map<String, PluginClassLoaderDef> classloadersByBasePlugin = new HashMap<>();
-
- for (PluginInfo info : infoByKeys.values()) {
- String baseKey = basePluginKey(info, infoByKeys);
- PluginClassLoaderDef def = classloadersByBasePlugin.get(baseKey);
- if (def == null) {
- def = new PluginClassLoaderDef(baseKey);
- classloadersByBasePlugin.put(baseKey, def);
- }
- ExplodedPlugin explodedPlugin = jarExploder.explode(info);
- def.addFiles(asList(explodedPlugin.getMain()));
- def.addFiles(explodedPlugin.getLibs());
- def.addMainClass(info.getKey(), info.getMainClass());
-
- for (String defaultSharedResource : DEFAULT_SHARED_RESOURCES) {
- def.getExportMask().addInclusion(String.format("%s/%s/api/", defaultSharedResource, info.getKey()));
- }
-
- // The plugins that extend other plugins can only add some files to classloader.
- // They can't change metadata like ordering strategy or compatibility mode.
- if (Strings.isNullOrEmpty(info.getBasePlugin())) {
- if (info.isUseChildFirstClassLoader()) {
- Loggers.get(getClass()).warn("Plugin {} [{}] uses a child first classloader which is deprecated", info.getName(), info.getKey());
- }
- def.setSelfFirstStrategy(info.isUseChildFirstClassLoader());
- Version minSqVersion = info.getMinimalSqVersion();
- boolean compatibilityMode = minSqVersion != null && minSqVersion.compareToIgnoreQualifier(COMPATIBILITY_MODE_MAX_VERSION) < 0;
- if (compatibilityMode) {
- Loggers.get(getClass()).warn("API compatibility mode is no longer supported. In case of error, plugin {} [{}] should package its dependencies.",
- info.getName(), info.getKey());
- }
- }
- }
- return classloadersByBasePlugin.values();
-
- }
-
- /**
- * Instantiates collection of {@link org.sonar.api.Plugin} according to given metadata and classloaders
- *
- * @return the instances grouped by plugin key
- * @throws IllegalStateException if at least one plugin can't be correctly loaded
- */
- @VisibleForTesting
- Map<String, Plugin> instantiatePluginClasses(Map<PluginClassLoaderDef, ClassLoader> classloaders) {
- // instantiate plugins
- Map<String, Plugin> instancesByPluginKey = new HashMap<>();
- for (Map.Entry<PluginClassLoaderDef, ClassLoader> entry : classloaders.entrySet()) {
- PluginClassLoaderDef def = entry.getKey();
- ClassLoader classLoader = entry.getValue();
-
- // the same classloader can be used by multiple plugins
- for (Map.Entry<String, String> mainClassEntry : def.getMainClassesByPluginKey().entrySet()) {
- String pluginKey = mainClassEntry.getKey();
- String mainClass = mainClassEntry.getValue();
- try {
- instancesByPluginKey.put(pluginKey, (Plugin) classLoader.loadClass(mainClass).newInstance());
- } catch (UnsupportedClassVersionError e) {
- throw new IllegalStateException(String.format("The plugin [%s] does not support Java %s",
- pluginKey, SystemUtils.JAVA_VERSION_TRIMMED), e);
- } catch (Throwable e) {
- throw new IllegalStateException(String.format(
- "Fail to instantiate class [%s] of plugin [%s]", mainClass, pluginKey), e);
- }
- }
- }
- return instancesByPluginKey;
- }
-
- public void unload(Collection<Plugin> plugins) {
- for (Plugin plugin : plugins) {
- ClassLoader classLoader = plugin.getClass().getClassLoader();
- if (classLoader instanceof Closeable && classLoader != classloaderFactory.baseClassLoader()) {
- try {
- ((Closeable) classLoader).close();
- } catch (Exception e) {
- Loggers.get(getClass()).error("Fail to close classloader " + classLoader.toString(), e);
- }
- }
- }
- }
-
- /**
- * Get the root key of a tree of plugins. For example if plugin C depends on B, which depends on A, then
- * B and C must be attached to the classloader of A. The method returns A in the three cases.
- */
- static String basePluginKey(PluginInfo plugin, Map<String, PluginInfo> allPluginsPerKey) {
- String base = plugin.getKey();
- String parentKey = plugin.getBasePlugin();
- while (!Strings.isNullOrEmpty(parentKey)) {
- PluginInfo parentPlugin = allPluginsPerKey.get(parentKey);
- base = parentPlugin.getKey();
- parentKey = parentPlugin.getBasePlugin();
- }
- return base;
- }
-}
import java.util.Collection;
import org.sonar.api.Plugin;
-import org.sonar.api.scanner.ScannerSide;
import org.sonar.api.ce.ComputeEngineSide;
+import org.sonar.api.scanner.ScannerSide;
import org.sonar.api.server.ServerSide;
/**
*/
Plugin getPluginInstance(String key);
+ Collection<Plugin> getPluginInstances();
+
boolean hasPlugin(String key);
}
--- /dev/null
+/*
+ * SonarQube
+ * Copyright (C) 2009-2020 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.core.platform;
+
+import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.io.IOException;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import org.assertj.core.data.MapEntry;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.api.utils.log.LogTester;
+import org.sonar.api.utils.log.LoggerLevel;
+import org.sonar.updatecenter.common.Version;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.entry;
+import static org.mockito.Mockito.mock;
+
+public class PluginClassLoaderTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ @Rule
+ public LogTester logTester = new LogTester();
+
+ private PluginClassloaderFactory classloaderFactory = mock(PluginClassloaderFactory.class);
+ private PluginClassLoader underTest = new PluginClassLoader(classloaderFactory);
+
+ @Test
+ public void define_classloader() throws Exception {
+ File jarFile = temp.newFile();
+ PluginInfo plugin = new PluginInfo("foo")
+ .setJarFile(jarFile)
+ .setMainClass("org.foo.FooPlugin")
+ .setMinimalSqVersion(Version.create("5.2"));
+
+ ExplodedPlugin explodedPlugin = createExplodedPlugin(plugin);
+ Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(
+ ImmutableMap.of("foo", explodedPlugin));
+
+ assertThat(defs).hasSize(1);
+ PluginClassLoaderDef def = defs.iterator().next();
+ assertThat(def.getBasePluginKey()).isEqualTo("foo");
+ assertThat(def.isSelfFirstStrategy()).isFalse();
+ assertThat(def.getFiles()).containsAll(explodedPlugin.getLibs());
+ assertThat(def.getMainClassesByPluginKey()).containsOnly(MapEntry.entry("foo", "org.foo.FooPlugin"));
+ }
+
+ /**
+ * A plugin (the "base" plugin) can be extended by other plugins. In this case they share the same classloader.
+ */
+ @Test
+ public void test_plugins_sharing_the_same_classloader() throws Exception {
+ File baseJarFile = temp.newFile();
+ File extensionJar1 = temp.newFile();
+ File extensionJar2 = temp.newFile();
+ PluginInfo base = new PluginInfo("foo")
+ .setJarFile(baseJarFile)
+ .setMainClass("org.foo.FooPlugin")
+ .setUseChildFirstClassLoader(false);
+
+ PluginInfo extension1 = new PluginInfo("fooExtension1")
+ .setJarFile(extensionJar1)
+ .setMainClass("org.foo.Extension1Plugin")
+ .setBasePlugin("foo");
+
+ // This extension tries to change the classloader-ordering strategy of base plugin
+ // (see setUseChildFirstClassLoader(true)).
+ // That is not allowed and should be ignored -> strategy is still the one
+ // defined on base plugin (parent-first in this example)
+ PluginInfo extension2 = new PluginInfo("fooExtension2")
+ .setJarFile(extensionJar2)
+ .setMainClass("org.foo.Extension2Plugin")
+ .setBasePlugin("foo")
+ .setUseChildFirstClassLoader(true);
+
+ ExplodedPlugin baseExplodedPlugin = createExplodedPlugin(base);
+ ExplodedPlugin extension1ExplodedPlugin = createExplodedPlugin(extension1);
+ ExplodedPlugin extension2ExplodedPlugin = createExplodedPlugin(extension2);
+ Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(ImmutableMap.of(
+ base.getKey(), baseExplodedPlugin,
+ extension1.getKey(), extension1ExplodedPlugin,
+ extension2.getKey(), extension2ExplodedPlugin));
+
+ assertThat(defs).hasSize(1);
+ PluginClassLoaderDef def = defs.iterator().next();
+ assertThat(def.getBasePluginKey()).isEqualTo("foo");
+ assertThat(def.isSelfFirstStrategy()).isFalse();
+
+ assertThat(def.getFiles())
+ .containsAll(baseExplodedPlugin.getLibs())
+ .containsAll(extension1ExplodedPlugin.getLibs())
+ .containsAll(extension2ExplodedPlugin.getLibs());
+ assertThat(def.getMainClassesByPluginKey()).containsOnly(
+ entry("foo", "org.foo.FooPlugin"),
+ entry("fooExtension1", "org.foo.Extension1Plugin"),
+ entry("fooExtension2", "org.foo.Extension2Plugin"));
+ }
+
+ @Test
+ public void log_warning_if_plugin_uses_child_first_classloader() throws IOException {
+ File jarFile = temp.newFile();
+ PluginInfo info = new PluginInfo("foo")
+ .setJarFile(jarFile)
+ .setUseChildFirstClassLoader(true)
+ .setMainClass("org.foo.FooPlugin");
+
+ Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(
+ ImmutableMap.of("foo", createExplodedPlugin(info)));
+ assertThat(defs).extracting(PluginClassLoaderDef::getBasePluginKey).containsExactly("foo");
+
+ List<String> warnings = logTester.logs(LoggerLevel.WARN);
+ assertThat(warnings).contains("Plugin foo [foo] uses a child first classloader which is deprecated");
+ }
+
+ @Test
+ public void log_warning_if_plugin_is_built_with_api_5_2_or_lower() throws Exception {
+ File jarFile = temp.newFile();
+ PluginInfo info = new PluginInfo("foo")
+ .setJarFile(jarFile)
+ .setMainClass("org.foo.FooPlugin")
+ .setMinimalSqVersion(Version.create("4.5.2"));
+
+ Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(
+ ImmutableMap.of("foo", createExplodedPlugin(info)));
+ assertThat(defs).extracting(PluginClassLoaderDef::getBasePluginKey).containsExactly("foo");
+
+ List<String> warnings = logTester.logs(LoggerLevel.WARN);
+ assertThat(warnings).contains("API compatibility mode is no longer supported. In case of error, plugin foo [foo] should package its dependencies.");
+ }
+
+ private ExplodedPlugin createExplodedPlugin(PluginInfo plugin) {
+ return new ExplodedPlugin(plugin, plugin.getKey(), new File(plugin.getKey() + ".jar"), Collections
+ .singleton(new File(plugin.getKey() + "-lib.jar")));
+ }
+}
import com.tngtech.java.junit.dataprovider.UseDataProvider;
import java.io.File;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
import org.assertj.core.api.Assertions;
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.utils.MessageException;
import static com.google.common.collect.Ordering.natural;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.Assert.fail;
@RunWith(DataProviderRunner.class)
@Rule
public TemporaryFolder temp = new TemporaryFolder();
- @Rule
- public ExpectedException expectedException = ExpectedException.none();
-
@Test
public void test_RequiredPlugin() {
PluginInfo.RequiredPlugin plugin = PluginInfo.RequiredPlugin.parse("java:1.1");
assertThat(plugin.getKey()).isEqualTo("java");
assertThat(plugin.getMinimalVersion().getName()).isEqualTo("1.1");
- assertThat(plugin.toString()).isEqualTo("java:1.1");
- assertThat(plugin.equals(PluginInfo.RequiredPlugin.parse("java:1.2"))).isTrue();
- assertThat(plugin.equals(PluginInfo.RequiredPlugin.parse("php:1.2"))).isFalse();
+ assertThat(plugin).hasToString("java:1.1")
+ .isEqualTo(PluginInfo.RequiredPlugin.parse("java:1.2"))
+ .isNotEqualTo(PluginInfo.RequiredPlugin.parse("php:1.2"));
try {
PluginInfo.RequiredPlugin.parse("java");
manifest.setOrganization("SonarSource");
manifest.setOrganizationUrl("http://sonarsource.com");
manifest.setIssueTrackerUrl("http://jira.com");
- manifest.setRequirePlugins(new String[]{"java:2.0", "pmd:1.3"});
+ manifest.setRequirePlugins(new String[] {"java:2.0", "pmd:1.3"});
manifest.setSonarLintSupported(true);
File jarFile = temp.newFile();
manifest.setVersion("1.0");
manifest.setName("Java");
manifest.setMainClass("org.foo.FooPlugin");
- manifest.setRequirePlugins(new String[]{"license:" + version});
+ manifest.setRequirePlugins(new String[] {"license:" + version});
File jarFile = temp.newFile();
PluginInfo pluginInfo = PluginInfo.create(jarFile, manifest);
manifest.setVersion("1.0");
manifest.setName("Java");
manifest.setMainClass("org.foo.FooPlugin");
- manifest.setRequirePlugins(new String[]{"java:2.0", "license:" + version, "pmd:1.3"});
+ manifest.setRequirePlugins(new String[] {"java:2.0", "license:" + version, "pmd:1.3"});
File jarFile = temp.newFile();
PluginInfo pluginInfo = PluginInfo.create(jarFile, manifest);
@DataProvider
public static Object[][] licenseVersions() {
- return new Object[][]{
+ return new Object[][] {
{"0.3"},
{"7.2.0.1253"}
};
assertThat(pluginInfo.toString()).isEqualTo("[java / 1.1]");
pluginInfo.setImplementationBuild("SHA1");
- assertThat(pluginInfo.toString()).isEqualTo("[java / 1.1 / SHA1]");
+ assertThat(pluginInfo).hasToString("[java / 1.1 / SHA1]");
}
/**
public void fail_when_jar_is_not_a_plugin() throws IOException {
// this JAR has a manifest but is not a plugin
File jarRootDir = temp.newFolder();
- FileUtils.write(new File(jarRootDir, "META-INF/MANIFEST.MF"), "Build-Jdk: 1.6.0_15");
+ FileUtils.write(new File(jarRootDir, "META-INF/MANIFEST.MF"), "Build-Jdk: 1.6.0_15", StandardCharsets.UTF_8);
File jar = temp.newFile();
ZipUtils.zipDir(jarRootDir, jar);
- expectedException.expect(MessageException.class);
- expectedException.expectMessage("File is not a plugin. Please delete it and restart: " + jar.getAbsolutePath());
-
- PluginInfo.create(jar);
+ assertThatThrownBy(() -> PluginInfo.create(jar))
+ .isInstanceOf(MessageException.class)
+ .hasMessage("File is not a plugin. Please delete it and restart: " + jar.getAbsolutePath());
}
PluginInfo withMinSqVersion(@Nullable String version) {
public ExplodedPlugin explode(PluginInfo info) {
try {
ZipUtils.unzip(jarFile, toDir, newLibFilter());
- return explodeFromUnzippedDir(info.getKey(), info.getNonNullJarFile(), toDir);
+ return explodeFromUnzippedDir(info, info.getNonNullJarFile(), toDir);
} catch (Exception e) {
throw new IllegalStateException(e);
}
PluginJarExploder exploder = new PluginJarExploder() {
@Override
public ExplodedPlugin explode(PluginInfo info) {
- return explodeFromUnzippedDir("foo", info.getNonNullJarFile(), toDir);
+ return explodeFromUnzippedDir(pluginInfo, info.getNonNullJarFile(), toDir);
}
};
ExplodedPlugin exploded = exploder.explode(pluginInfo);
+++ /dev/null
-/*
- * SonarQube
- * Copyright (C) 2009-2020 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.core.platform;
-
-import com.google.common.collect.ImmutableMap;
-import java.io.File;
-import java.io.IOException;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import org.assertj.core.data.MapEntry;
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.TemporaryFolder;
-import org.sonar.api.utils.log.LogTester;
-import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.updatecenter.common.Version;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.entry;
-import static org.mockito.Mockito.mock;
-
-public class PluginLoaderTest {
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
-
- @Rule
- public LogTester logTester = new LogTester();
-
- private PluginClassloaderFactory classloaderFactory = mock(PluginClassloaderFactory.class);
- private PluginLoader underTest = new PluginLoader(new FakePluginExploder(), classloaderFactory);
-
- @Test
- public void define_classloader() throws Exception {
- File jarFile = temp.newFile();
- PluginInfo info = new PluginInfo("foo")
- .setJarFile(jarFile)
- .setMainClass("org.foo.FooPlugin")
- .setMinimalSqVersion(Version.create("5.2"));
-
- Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(ImmutableMap.of("foo", info));
-
- assertThat(defs).hasSize(1);
- PluginClassLoaderDef def = defs.iterator().next();
- assertThat(def.getBasePluginKey()).isEqualTo("foo");
- assertThat(def.isSelfFirstStrategy()).isFalse();
- assertThat(def.getFiles()).containsOnly(jarFile);
- assertThat(def.getMainClassesByPluginKey()).containsOnly(MapEntry.entry("foo", "org.foo.FooPlugin"));
- // TODO test mask - require change in sonar-classloader
- }
-
- /**
- * A plugin (the "base" plugin) can be extended by other plugins. In this case they share the same classloader.
- */
- @Test
- public void test_plugins_sharing_the_same_classloader() throws Exception {
- File baseJarFile = temp.newFile();
- File extensionJar1 = temp.newFile();
- File extensionJar2 = temp.newFile();
- PluginInfo base = new PluginInfo("foo")
- .setJarFile(baseJarFile)
- .setMainClass("org.foo.FooPlugin")
- .setUseChildFirstClassLoader(false);
-
- PluginInfo extension1 = new PluginInfo("fooExtension1")
- .setJarFile(extensionJar1)
- .setMainClass("org.foo.Extension1Plugin")
- .setBasePlugin("foo");
-
- // This extension tries to change the classloader-ordering strategy of base plugin
- // (see setUseChildFirstClassLoader(true)).
- // That is not allowed and should be ignored -> strategy is still the one
- // defined on base plugin (parent-first in this example)
- PluginInfo extension2 = new PluginInfo("fooExtension2")
- .setJarFile(extensionJar2)
- .setMainClass("org.foo.Extension2Plugin")
- .setBasePlugin("foo")
- .setUseChildFirstClassLoader(true);
-
- Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(ImmutableMap.of(
- base.getKey(), base, extension1.getKey(), extension1, extension2.getKey(), extension2));
-
- assertThat(defs).hasSize(1);
- PluginClassLoaderDef def = defs.iterator().next();
- assertThat(def.getBasePluginKey()).isEqualTo("foo");
- assertThat(def.isSelfFirstStrategy()).isFalse();
- assertThat(def.getFiles()).containsOnly(baseJarFile, extensionJar1, extensionJar2);
- assertThat(def.getMainClassesByPluginKey()).containsOnly(
- entry("foo", "org.foo.FooPlugin"),
- entry("fooExtension1", "org.foo.Extension1Plugin"),
- entry("fooExtension2", "org.foo.Extension2Plugin"));
- // TODO test mask - require change in sonar-classloader
- }
-
- @Test
- public void log_warning_if_plugin_uses_child_first_classloader() throws IOException {
- File jarFile = temp.newFile();
- PluginInfo info = new PluginInfo("foo")
- .setJarFile(jarFile)
- .setUseChildFirstClassLoader(true)
- .setMainClass("org.foo.FooPlugin");
-
- Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(ImmutableMap.of("foo", info));
- assertThat(defs).extracting(PluginClassLoaderDef::getBasePluginKey).containsExactly("foo");
-
- List<String> warnings = logTester.logs(LoggerLevel.WARN);
- assertThat(warnings).contains("Plugin foo [foo] uses a child first classloader which is deprecated");
- }
-
- @Test
- public void log_warning_if_plugin_is_built_with_api_5_2_or_lower() throws Exception {
- File jarFile = temp.newFile();
- PluginInfo info = new PluginInfo("foo")
- .setJarFile(jarFile)
- .setMainClass("org.foo.FooPlugin")
- .setMinimalSqVersion(Version.create("4.5.2"));
-
- Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(ImmutableMap.of("foo", info));
- assertThat(defs).extracting(PluginClassLoaderDef::getBasePluginKey).containsExactly("foo");
-
- List<String> warnings = logTester.logs(LoggerLevel.WARN);
- assertThat(warnings).contains("API compatibility mode is no longer supported. In case of error, plugin foo [foo] should package its dependencies.");
- }
-
- /**
- * Does not unzip jar file. It directly returns the JAR file defined on PluginInfo.
- */
- private static class FakePluginExploder extends PluginJarExploder {
- @Override
- public ExplodedPlugin explode(PluginInfo info) {
- return new ExplodedPlugin(info.getKey(), info.getNonNullJarFile(), Collections.emptyList());
- }
- }
-}
import org.sonar.core.platform.ComponentContainer;
import org.sonar.core.platform.PluginClassloaderFactory;
import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginLoader;
+import org.sonar.core.platform.PluginClassLoader;
import org.sonar.core.platform.PluginRepository;
import org.sonar.core.util.DefaultHttpDownloader;
import org.sonar.core.util.UuidFactoryImpl;
add(
// plugins
ScannerPluginRepository.class,
- PluginLoader.class,
+ PluginClassLoader.class,
PluginClassloaderFactory.class,
ScannerPluginJarExploder.class,
ExtensionInstaller.class,
public ExplodedPlugin explode(PluginInfo info) {
try {
File dir = unzipFile(info.getNonNullJarFile());
- return explodeFromUnzippedDir(info.getKey(), info.getNonNullJarFile(), dir);
+ return explodeFromUnzippedDir(info, info.getNonNullJarFile(), dir);
} catch (Exception e) {
throw new IllegalStateException(String.format("Fail to open plugin [%s]: %s", info.getKey(), info.getNonNullJarFile().getAbsolutePath()), e);
}
import java.util.Collection;
import java.util.HashMap;
import java.util.Map;
-import java.util.function.Function;
+import java.util.stream.Collectors;
import javax.annotation.CheckForNull;
import org.picocontainer.Startable;
import org.sonar.api.Plugin;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.platform.ExplodedPlugin;
+import org.sonar.core.platform.PluginClassLoader;
import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginLoader;
+import org.sonar.core.platform.PluginJarExploder;
import org.sonar.core.platform.PluginRepository;
import static java.util.stream.Collectors.toList;
-import static java.util.stream.Collectors.toMap;
import static org.sonar.api.utils.Preconditions.checkState;
/**
private static final Logger LOG = Loggers.get(ScannerPluginRepository.class);
private final PluginInstaller installer;
- private final PluginLoader loader;
+ private final PluginJarExploder pluginJarExploder;
+ private final PluginClassLoader loader;
private Map<String, Plugin> pluginInstancesByKeys;
private Map<String, ScannerPlugin> pluginsByKeys;
private Map<ClassLoader, String> keysByClassLoader;
- public ScannerPluginRepository(PluginInstaller installer, PluginLoader loader) {
+ public ScannerPluginRepository(PluginInstaller installer, PluginJarExploder pluginJarExploder, PluginClassLoader loader) {
this.installer = installer;
+ this.pluginJarExploder = pluginJarExploder;
this.loader = loader;
}
@Override
public void start() {
pluginsByKeys = new HashMap<>(installer.installRemotes());
- pluginInstancesByKeys = new HashMap<>(
- loader.load(pluginsByKeys.values().stream()
- .map(ScannerPlugin::getInfo)
- .collect(toMap(PluginInfo::getKey, Function.identity()))));
+ Map<String, ExplodedPlugin> explodedPLuginsByKey = pluginsByKeys.entrySet().stream()
+ .collect(Collectors.toMap(Map.Entry::getKey, e -> pluginJarExploder.explode(e.getValue().getInfo())));
+ pluginInstancesByKeys = new HashMap<>(loader.load(explodedPLuginsByKey));
// this part is only used by medium tests
for (Object[] localPlugin : installer.installLocals()) {
return instance;
}
+ @Override
+ public Collection<Plugin> getPluginInstances() {
+ return pluginInstancesByKeys.values();
+ }
+
@Override
public boolean hasPlugin(String key) {
return pluginsByKeys.containsKey(key);
package org.sonar.scanner.bootstrap;
import com.google.common.collect.ImmutableMap;
+import java.io.File;
+import java.util.Collections;
import org.junit.Test;
import org.sonar.api.Plugin;
+import org.sonar.core.platform.ExplodedPlugin;
+import org.sonar.core.platform.PluginClassLoader;
import org.sonar.core.platform.PluginInfo;
-import org.sonar.core.platform.PluginLoader;
+import org.sonar.core.platform.PluginJarExploder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
public class ScannerPluginRepositoryTest {
PluginInstaller installer = mock(PluginInstaller.class);
- PluginLoader loader = mock(PluginLoader.class);
- ScannerPluginRepository underTest = new ScannerPluginRepository(installer, loader);
+ PluginClassLoader loader = mock(PluginClassLoader.class);
+ PluginJarExploder exploder = new FakePluginJarExploder();
+ ScannerPluginRepository underTest = new ScannerPluginRepository(installer, exploder, loader);
@Test
public void install_and_load_plugins() {
assertThat(underTest.getPluginsByKey()).isEqualTo(plugins);
assertThat(underTest.getPluginInfo("squid")).isSameAs(info);
assertThat(underTest.getPluginInstance("squid")).isSameAs(instance);
+ assertThat(underTest.getPluginInstances()).containsOnly(instance);
underTest.stop();
verify(loader).unload(anyCollection());
assertThat(e).hasMessage("Plugin [unknown] does not exist");
}
}
+
+ private static class FakePluginJarExploder extends PluginJarExploder {
+ @Override
+ public ExplodedPlugin explode(PluginInfo plugin) {
+ return new ExplodedPlugin(plugin, plugin.getKey(), new File(plugin.getKey() + ".jar"), Collections
+ .singleton(new File(plugin.getKey() + "-lib.jar")));
+ }
+
+ }
}