aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDuarte Meneses <duarte.meneses@sonarsource.com>2020-08-26 18:30:15 -0500
committersonartech <sonartech@sonarsource.com>2020-09-18 20:07:13 +0000
commitd189558e9c7b3994254eaa8d67dff0169c21d5dd (patch)
treedb9d6937b26ad9ce923c408a8d3ee94045203d82
parent13ef209f1b2cc5f82d0c3bde502ba02649b6c5e2 (diff)
downloadsonarqube-d189558e9c7b3994254eaa8d67dff0169c21d5dd.tar.gz
sonarqube-d189558e9c7b3994254eaa8d67dff0169c21d5dd.zip
SONAR-13643 Save plugins with type
-rw-r--r--server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java8
-rw-r--r--server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java5
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginJarExploder.java2
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginRepository.java24
-rw-r--r--server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java4
-rw-r--r--server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginRepositoryTest.java69
-rw-r--r--server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java28
-rw-r--r--server/sonar-db-dao/src/main/resources/org/sonar/db/plugin/PluginMapper.xml4
-rw-r--r--server/sonar-db-dao/src/schema/schema-sq.ddl3
-rw-r--r--server/sonar-db-dao/src/test/java/org/sonar/db/plugin/PluginDaoTest.java63
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AddTypeToPlugins.java44
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AlterTypeInPluginNotNullable.java44
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/DbVersion85.java3
-rw-r--r--server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/PopulateTypeInPlugins.java37
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AddTypeToPluginsTest.java57
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AlterTypeInPluginNotNullableTest.java58
-rw-r--r--server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/PopulateTypeInPluginsTest.java62
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AddTypeToPluginsTest/schema.sql10
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AlterTypeInPluginNotNullableTest/schema.sql11
-rw-r--r--server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/PopulateTypeInPluginsTest/schema.sql11
-rw-r--r--server/sonar-server-common/src/test/java/org/sonar/server/plugins/ServerExtensionInstallerTest.java5
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginCompressor.java (renamed from server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginFileSystem.java)51
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginDownloader.java11
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginFilesAndMd5.java (renamed from server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/InstalledPlugin.java)13
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginJarLoader.java251
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginType.java24
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginUninstaller.java99
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPlugin.java73
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginInfo.java79
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java24
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginManager.java99
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java372
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java6
-rw-r--r--server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterMatrixFactory.java4
-rw-r--r--server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginCompressorTest.java (renamed from server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginFileSystemTest.java)59
-rw-r--r--server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java4
-rw-r--r--server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginFilesAndMd5Test.java62
-rw-r--r--server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginJarLoaderTest.java319
-rw-r--r--server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java127
-rw-r--r--server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginInfoTest.java49
-rw-r--r--server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java5
-rw-r--r--server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginManagerTest.java104
-rw-r--r--server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java428
-rw-r--r--server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java5
-rw-r--r--server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterMatrixFactoryTest.java8
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java23
-rw-r--r--server/sonar-webserver-core/src/main/java/org/sonar/server/startup/RegisterPlugins.java32
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java27
-rw-r--r--server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java78
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/UpgradesAction.java2
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/AvailableAction.java9
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/DownloadAction.java20
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/InstallAction.java2
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java43
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/PendingAction.java40
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java14
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UninstallAction.java22
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UpdateAction.java26
-rw-r--r--server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UpdatesAction.java2
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/language/LanguageValidationTest.java10
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/UpgradesActionTest.java4
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java2
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/AvailableActionTest.java4
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/DownloadActionTest.java70
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/InstallActionTest.java5
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java141
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java7
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java36
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/UninstallActionTest.java76
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/UpdateActionTest.java4
-rw-r--r--server/sonar-webserver-webapi/src/test/java/org/sonar/server/updatecenter/ws/PluginsActionTestFilesAndMD5.java (renamed from server/sonar-webserver-webapi/src/test/java/org/sonar/server/updatecenter/ws/InstalledPluginsActionTest.java)2
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java13
-rw-r--r--server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java2
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java9
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/PluginClassLoader.java (renamed from sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java)46
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java164
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/PluginJarExploder.java6
-rw-r--r--sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java4
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/PluginClassLoaderTest.java (renamed from sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java)44
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java31
-rw-r--r--sonar-core/src/test/java/org/sonar/core/platform/PluginJarExploderTest.java4
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalContainer.java4
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginJarExploder.java2
-rw-r--r--sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginRepository.java25
-rw-r--r--sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerPluginRepositoryTest.java21
85 files changed, 2411 insertions, 1493 deletions
diff --git a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java
index 9fe44ff2796..582475e04da 100644
--- a/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java
+++ b/server/sonar-ce-task-projectanalysis/src/main/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStep.java
@@ -147,11 +147,11 @@ public class LoadReportAnalysisMetadataHolderStep implements ComputationStep {
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()))));
@@ -171,9 +171,9 @@ public class LoadReportAnalysisMetadataHolderStep implements ComputationStep {
* 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()
diff --git a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java
index 17d681d58e9..4d10423cfff 100644
--- a/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java
+++ b/server/sonar-ce-task-projectanalysis/src/test/java/org/sonar/ce/task/projectanalysis/step/LoadReportAnalysisMetadataHolderStepTest.java
@@ -487,6 +487,11 @@ public class LoadReportAnalysisMetadataHolderStepTest {
}
@Override
+ public Collection<Plugin> getPluginInstances() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public boolean hasPlugin(String key) {
return pluginsMap.containsKey(key);
}
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginJarExploder.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginJarExploder.java
index 18959e76b31..b5d95a6f04c 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginJarExploder.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginJarExploder.java
@@ -51,7 +51,7 @@ public class CePluginJarExploder extends PluginJarExploder {
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);
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginRepository.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginRepository.java
index 4c9476647ce..cc85465e78b 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginRepository.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/CePluginRepository.java
@@ -26,12 +26,14 @@ import java.util.Collections;
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;
@@ -49,16 +51,18 @@ public class CePluginRepository implements PluginRepository, Startable {
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
@@ -66,7 +70,8 @@ public class CePluginRepository implements PluginRepository, Startable {
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);
}
@@ -77,6 +82,12 @@ public class CePluginRepository implements PluginRepository, Startable {
}
}
+ 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
@@ -111,6 +122,11 @@ public class CePluginRepository implements PluginRepository, Startable {
}
@Override
+ public Collection<Plugin> getPluginInstances() {
+ return pluginInstancesByKeys.values();
+ }
+
+ @Override
public boolean hasPlugin(String key) {
checkState(started.get(), NOT_STARTED_YET);
return pluginInfosByKeys.containsKey(key);
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
index f7e3793a818..7d57bbfcb72 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/container/ComputeEngineContainerImpl.java
@@ -77,7 +77,7 @@ import org.sonar.core.platform.EditionProvider;
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;
@@ -335,7 +335,7 @@ public class ComputeEngineContainerImpl implements ComputeEngineContainer {
// plugins
PluginClassloaderFactory.class,
CePluginJarExploder.class,
- PluginLoader.class,
+ PluginClassLoader.class,
CePluginRepository.class,
InstalledPluginReferentialFactory.class,
ComputeEngineExtensionInstaller.class,
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginRepositoryTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginRepositoryTest.java
index 047eebf6ead..4e3c93dd164 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginRepositoryTest.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/container/CePluginRepositoryTest.java
@@ -20,21 +20,22 @@
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;
@@ -43,12 +44,10 @@ public class CePluginRepositoryTest {
@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() {
@@ -67,8 +66,9 @@ public class CePluginRepositoryTest {
}
@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();
@@ -76,74 +76,69 @@ public class CePluginRepositoryTest {
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));
diff --git a/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java b/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java
index 6b3169ab06f..375d4e847f7 100644
--- a/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java
+++ b/server/sonar-db-dao/src/main/java/org/sonar/db/plugin/PluginDto.java
@@ -25,17 +25,19 @@ import org.apache.commons.lang.builder.ToStringBuilder;
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;
@@ -92,6 +94,20 @@ public class PluginDto {
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)
diff --git a/server/sonar-db-dao/src/main/resources/org/sonar/db/plugin/PluginMapper.xml b/server/sonar-db-dao/src/main/resources/org/sonar/db/plugin/PluginMapper.xml
index 0ff5f6cf543..ce585f75d67 100644
--- a/server/sonar-db-dao/src/main/resources/org/sonar/db/plugin/PluginMapper.xml
+++ b/server/sonar-db-dao/src/main/resources/org/sonar/db/plugin/PluginMapper.xml
@@ -9,6 +9,7 @@
kee,
base_plugin_key as basePluginKey,
file_hash as fileHash,
+ type,
created_at as createdAt,
updated_at as updatedAt
</sql>
@@ -31,6 +32,7 @@
kee,
base_plugin_key,
file_hash,
+ type,
created_at,
updated_at
) values (
@@ -38,6 +40,7 @@
#{kee,jdbcType=VARCHAR},
#{basePluginKey,jdbcType=VARCHAR},
#{fileHash,jdbcType=VARCHAR},
+ #{type,jdbcType=VARCHAR},
#{createdAt,jdbcType=TIMESTAMP},
#{updatedAt,jdbcType=TIMESTAMP}
)
@@ -47,6 +50,7 @@
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}
diff --git a/server/sonar-db-dao/src/schema/schema-sq.ddl b/server/sonar-db-dao/src/schema/schema-sq.ddl
index 9345fa56fba..10ff174c94d 100644
--- a/server/sonar-db-dao/src/schema/schema-sq.ddl
+++ b/server/sonar-db-dao/src/schema/schema-sq.ddl
@@ -602,7 +602,8 @@ CREATE TABLE "PLUGINS"(
"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");
diff --git a/server/sonar-db-dao/src/test/java/org/sonar/db/plugin/PluginDaoTest.java b/server/sonar-db-dao/src/test/java/org/sonar/db/plugin/PluginDaoTest.java
index 40492b676db..65d1abf3980 100644
--- a/server/sonar-db-dao/src/test/java/org/sonar/db/plugin/PluginDaoTest.java
+++ b/server/sonar-db-dao/src/test/java/org/sonar/db/plugin/PluginDaoTest.java
@@ -19,15 +19,17 @@
*/
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 {
@@ -40,57 +42,38 @@ 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");
@@ -98,22 +81,32 @@ public class PluginDaoTest {
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();
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AddTypeToPlugins.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AddTypeToPlugins.java
new file mode 100644
index 00000000000..13c2f613e1c
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AddTypeToPlugins.java
@@ -0,0 +1,44 @@
+/*
+ * 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());
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AlterTypeInPluginNotNullable.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AlterTypeInPluginNotNullable.java
new file mode 100644
index 00000000000..c8e7634e36c
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/AlterTypeInPluginNotNullable.java
@@ -0,0 +1,44 @@
+/*
+ * 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());
+ }
+}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/DbVersion85.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/DbVersion85.java
index cf4917d8abe..d6bb98a5bdb 100644
--- a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/DbVersion85.java
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/DbVersion85.java
@@ -44,6 +44,9 @@ public class DbVersion85 implements DbVersion {
.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)
;
}
diff --git a/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/PopulateTypeInPlugins.java b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/PopulateTypeInPlugins.java
new file mode 100644
index 00000000000..33a3f735119
--- /dev/null
+++ b/server/sonar-db-migration/src/main/java/org/sonar/server/platform/db/migration/version/v85/PopulateTypeInPlugins.java
@@ -0,0 +1,37 @@
+/*
+ * 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();
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AddTypeToPluginsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AddTypeToPluginsTest.java
new file mode 100644
index 00000000000..243bcd51057
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AddTypeToPluginsTest.java
@@ -0,0 +1,57 @@
+/*
+ * 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);
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AlterTypeInPluginNotNullableTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AlterTypeInPluginNotNullableTest.java
new file mode 100644
index 00000000000..c08e6f806f1
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/AlterTypeInPluginNotNullableTest.java
@@ -0,0 +1,58 @@
+/*
+ * 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);
+ }
+}
diff --git a/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/PopulateTypeInPluginsTest.java b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/PopulateTypeInPluginsTest.java
new file mode 100644
index 00000000000..7fe72413292
--- /dev/null
+++ b/server/sonar-db-migration/src/test/java/org/sonar/server/platform/db/migration/version/v85/PopulateTypeInPluginsTest.java
@@ -0,0 +1,62 @@
+/*
+ * 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);
+ }
+}
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AddTypeToPluginsTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AddTypeToPluginsTest/schema.sql
new file mode 100644
index 00000000000..c2a001fefdd
--- /dev/null
+++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AddTypeToPluginsTest/schema.sql
@@ -0,0 +1,10 @@
+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");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AlterTypeInPluginNotNullableTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AlterTypeInPluginNotNullableTest/schema.sql
new file mode 100644
index 00000000000..3d438e8eaa8
--- /dev/null
+++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/AlterTypeInPluginNotNullableTest/schema.sql
@@ -0,0 +1,11 @@
+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");
diff --git a/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/PopulateTypeInPluginsTest/schema.sql b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/PopulateTypeInPluginsTest/schema.sql
new file mode 100644
index 00000000000..3d438e8eaa8
--- /dev/null
+++ b/server/sonar-db-migration/src/test/resources/org/sonar/server/platform/db/migration/version/v85/PopulateTypeInPluginsTest/schema.sql
@@ -0,0 +1,11 @@
+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");
diff --git a/server/sonar-server-common/src/test/java/org/sonar/server/plugins/ServerExtensionInstallerTest.java b/server/sonar-server-common/src/test/java/org/sonar/server/plugins/ServerExtensionInstallerTest.java
index 952b36208f0..2496fd0a186 100644
--- a/server/sonar-server-common/src/test/java/org/sonar/server/plugins/ServerExtensionInstallerTest.java
+++ b/server/sonar-server-common/src/test/java/org/sonar/server/plugins/ServerExtensionInstallerTest.java
@@ -151,6 +151,11 @@ public class ServerExtensionInstallerTest {
}
@Override
+ public Collection<Plugin> getPluginInstances() {
+ return pluginsMap.values();
+ }
+
+ @Override
public boolean hasPlugin(String key) {
return pluginsMap.containsKey(key);
}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginFileSystem.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginCompressor.java
index 8dd2162b053..faa2551219d 100644
--- a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginFileSystem.java
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginCompressor.java
@@ -26,9 +26,6 @@ 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;
@@ -38,56 +35,40 @@ 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;
+import org.sonar.server.plugins.PluginFilesAndMd5.FileAndMd5;
@ServerSide
-public class PluginFileSystem {
+public class PluginCompressor {
public static final String PROPERTY_PLUGIN_COMPRESSION_ENABLE = "sonar.pluginsCompression.enable";
- private static final Logger LOG = Loggers.get(PluginFileSystem.class);
+ private static final Logger LOG = Loggers.get(PluginCompressor.class);
private final Configuration configuration;
- private final Map<String, InstalledPlugin> installedFiles = new HashMap<>();
- public PluginFileSystem(Configuration configuration) {
+ public PluginCompressor(Configuration configuration) {
this.configuration = configuration;
}
+ public boolean enabled() {
+ return configuration.getBoolean(PROPERTY_PLUGIN_COMPRESSION_ENABLE).orElse(false);
+ }
+
/**
- * @param plugin
- * @param loadedJar the JAR loaded by classloaders. It differs from {@code plugin.getJarFile()}
+ * @param loadedJar the JAR loaded by classloaders. It differs from {@code jar}
* 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();
+ 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(PluginInfo plugin, File jar) {
+ 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(jar.toPath());
- Path sourcePack200Path = getPack200Path(plugin.getNonNullJarFile().toPath());
+ 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()) {
@@ -98,7 +79,7 @@ public class PluginFileSystem {
throw new IllegalStateException("Failed to copy pack200 file from " + sourcePack200Path + " to " + targetPack200, e);
}
} else {
- pack200(jar.toPath(), targetPack200, plugin.getKey());
+ pack200(loadedJar.toPath(), targetPack200, key);
}
return Optional.of(targetPack200.toFile());
}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginDownloader.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginDownloader.java
index 4c9a9e68fa3..4883c17f69b 100644
--- a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginDownloader.java
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginDownloader.java
@@ -19,15 +19,14 @@
*/
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;
@@ -99,14 +98,6 @@ public class PluginDownloader implements Startable {
}
}
- 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
*/
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/InstalledPlugin.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginFilesAndMd5.java
index f9d899351c4..338f1a2311a 100644
--- a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/InstalledPlugin.java
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginFilesAndMd5.java
@@ -26,27 +26,20 @@ 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;
+public class PluginFilesAndMd5 {
private final FileAndMd5 loadedJar;
@Nullable
private final FileAndMd5 compressedJar;
- public InstalledPlugin(PluginInfo plugin, FileAndMd5 loadedJar, @Nullable FileAndMd5 compressedJar) {
- this.plugin = requireNonNull(plugin);
+ public PluginFilesAndMd5(FileAndMd5 loadedJar, @Nullable FileAndMd5 compressedJar) {
this.loadedJar = requireNonNull(loadedJar);
this.compressedJar = compressedJar;
}
- public PluginInfo getPluginInfo() {
- return plugin;
- }
-
public FileAndMd5 getLoadedJar() {
return loadedJar;
}
@@ -57,7 +50,7 @@ public class InstalledPlugin {
}
@Immutable
- public static final class FileAndMd5 {
+ public static class FileAndMd5 {
private final File file;
private final String md5;
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginJarLoader.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginJarLoader.java
new file mode 100644
index 00000000000..e604d9c434d
--- /dev/null
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginJarLoader.java
@@ -0,0 +1,251 @@
+/*
+ * 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();
+ }
+
+}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginType.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginType.java
new file mode 100644
index 00000000000..0621c10677a
--- /dev/null
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginType.java
@@ -0,0 +1,24 @@
+/*
+ * 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
+}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginUninstaller.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginUninstaller.java
index f734a346440..5c1e7c88203 100644
--- a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginUninstaller.java
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/PluginUninstaller.java
@@ -23,38 +23,39 @@ import java.io.File;
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);
}
}
@@ -63,28 +64,84 @@ public class PluginUninstaller implements Startable {
// 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);
+ }
+ }
}
}
}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPlugin.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPlugin.java
new file mode 100644
index 00000000000..8bffd9d41c0
--- /dev/null
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPlugin.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+ }
+}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginInfo.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginInfo.java
new file mode 100644
index 00000000000..04696020ade
--- /dev/null
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginInfo.java
@@ -0,0 +1,79 @@
+/*
+ * 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);
+ }
+}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java
index 481251fb121..5612fdaa0e1 100644
--- a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginJarExploder.java
@@ -24,6 +24,7 @@ import org.apache.commons.io.FileUtils;
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;
@@ -33,36 +34,31 @@ import static org.apache.commons.io.FileUtils.forceMkdir;
@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);
}
}
}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginManager.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginManager.java
new file mode 100644
index 00000000000..43f90ba0f01
--- /dev/null
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginManager.java
@@ -0,0 +1,99 @@
+/*
+ * 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;
+ }
+}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
index 33b03519040..2b2863f8a3c 100644
--- a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
@@ -19,382 +19,86 @@
*/
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);
}
}
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java
index 4fd62aedbad..29e1857bce8 100644
--- a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterClient.java
@@ -19,12 +19,12 @@
*/
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;
@@ -75,14 +75,14 @@ public class UpdateCenterClient {
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() {
diff --git a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterMatrixFactory.java b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterMatrixFactory.java
index 08d0b9d4592..210f2b92a78 100644
--- a/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterMatrixFactory.java
+++ b/server/sonar-webserver-api/src/main/java/org/sonar/server/plugins/UpdateCenterMatrixFactory.java
@@ -19,7 +19,7 @@
*/
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;
@@ -50,6 +50,6 @@ public class UpdateCenterMatrixFactory {
installedPluginReferentialFactory.getInstalledPluginReferential())
.setDate(centerClient.getLastRefreshDate()));
}
- return Optional.absent();
+ return Optional.empty();
}
}
diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginFileSystemTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginCompressorTest.java
index 98735e46f94..e32b0ddfafe 100644
--- a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginFileSystemTest.java
+++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginCompressorTest.java
@@ -21,6 +21,7 @@ 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;
@@ -30,12 +31,11 @@ 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;
+import static org.sonar.server.plugins.PluginCompressor.PROPERTY_PLUGIN_COMPRESSION_ENABLE;
-public class PluginFileSystemTest {
+public class PluginCompressorTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@@ -43,42 +43,21 @@ public class PluginFileSystemTest {
@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);
+ PluginCompressor underTest = new PluginCompressor(settings.asConfig());
- InstalledPlugin installedPlugin = underTest.getInstalledPlugin("foo").get();
- assertThat(installedPlugin.getPluginInfo()).isSameAs(info);
+ PluginFilesAndMd5 installedPlugin = underTest.compress("foo", jar, loadedJar);
assertThat(installedPlugin.getLoadedJar().getFile().toPath()).isEqualTo(loadedJar.toPath());
assertThat(installedPlugin.getCompressedJar().getFile())
.exists()
@@ -88,33 +67,43 @@ public class PluginFileSystemTest {
}
@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");
- 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);
+ PluginCompressor underTest = new PluginCompressor(settings.asConfig());
- InstalledPlugin installedPlugin = underTest.getInstalledPlugin("foo").get();
- assertThat(installedPlugin.getPluginInfo()).isSameAs(info);
+ 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())
- .hasSameContentAs(packedJar);
+ .hasSameTextualContentAs(packedJar);
}
private static File touch(File dir, String filename) throws IOException {
File file = new File(dir, filename);
- FileUtils.write(file, RandomStringUtils.random(10));
+ FileUtils.write(file, RandomStringUtils.random(10), StandardCharsets.UTF_8);
return file;
}
diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java
index a8319bcff55..57dee7d4e35 100644
--- a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java
+++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java
@@ -19,9 +19,9 @@
*/
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;
@@ -129,7 +129,7 @@ public class PluginDownloaderTest {
@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");
diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginFilesAndMd5Test.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginFilesAndMd5Test.java
new file mode 100644
index 00000000000..034a7a4c06c
--- /dev/null
+++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginFilesAndMd5Test.java
@@ -0,0 +1,62 @@
+/*
+ * 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));
+ }
+}
diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginJarLoaderTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginJarLoaderTest.java
new file mode 100644
index 00000000000..aec253e4feb
--- /dev/null
+++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginJarLoaderTest.java
@@ -0,0 +1,319 @@
+/*
+ * 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;
+ }
+}
diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java
index 0144ead6eb7..5e92e9087e1 100644
--- a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java
+++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/PluginUninstallerTest.java
@@ -21,72 +21,119 @@ package org.sonar.server.plugins;
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
@@ -96,6 +143,30 @@ public class PluginUninstallerTest {
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
diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginInfoTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginInfoTest.java
new file mode 100644
index 00000000000..08162441799
--- /dev/null
+++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginInfoTest.java
@@ -0,0 +1,49 @@
+/*
+ * 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);
+ }
+}
diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java
index 68e60276958..78a9473b5cb 100644
--- a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java
+++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginJarExploderTest.java
@@ -29,7 +29,6 @@ import org.sonar.server.platform.ServerFileSystem;
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 {
@@ -38,8 +37,7 @@ 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 {
@@ -62,6 +60,5 @@ public class ServerPluginJarExploderTest {
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);
}
}
diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginManagerTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginManagerTest.java
new file mode 100644
index 00000000000..bd476600444
--- /dev/null
+++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginManagerTest.java
@@ -0,0 +1,104 @@
+/*
+ * 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);
+ }
+}
diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java
index ff8b394c7e9..1b834913f85 100644
--- a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java
+++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java
@@ -19,422 +19,66 @@
*/
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));
}
}
diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java
index 219653eb7c1..648bc614a5e 100644
--- a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java
+++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterClientTest.java
@@ -32,7 +32,6 @@ import org.sonar.updatecenter.common.UpdateCenter;
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;
@@ -72,7 +71,7 @@ public class UpdateCenterClientTest {
@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
@@ -99,6 +98,6 @@ public class UpdateCenterClientTest {
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();
}
}
diff --git a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterMatrixFactoryTest.java b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterMatrixFactoryTest.java
index fcffc869ba9..5609664ba29 100644
--- a/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterMatrixFactoryTest.java
+++ b/server/sonar-webserver-api/src/test/java/org/sonar/server/plugins/UpdateCenterMatrixFactoryTest.java
@@ -19,12 +19,12 @@
*/
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;
@@ -36,12 +36,12 @@ public class UpdateCenterMatrixFactoryTest {
@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();
}
}
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java
index 78609fa838d..045ce07e2de 100644
--- a/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java
+++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java
@@ -33,8 +33,8 @@ 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.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.
@@ -48,11 +48,11 @@ public final class GeneratePluginIndex implements Startable {
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
@@ -71,7 +71,7 @@ public final class GeneratePluginIndex implements Startable {
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);
}
@@ -82,16 +82,15 @@ public final class GeneratePluginIndex implements Startable {
}
}
- 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();
}
}
diff --git a/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/RegisterPlugins.java b/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/RegisterPlugins.java
index 85710a00b12..9055589c4ee 100644
--- a/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/RegisterPlugins.java
+++ b/server/sonar-webserver-core/src/main/java/org/sonar/server/startup/RegisterPlugins.java
@@ -32,8 +32,9 @@ import org.sonar.core.util.UuidFactory;
import org.sonar.db.DbClient;
import org.sonar.db.DbSession;
import org.sonar.db.plugin.PluginDto;
-import org.sonar.server.plugins.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;
@@ -45,13 +46,13 @@ public class RegisterPlugins implements Startable {
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;
@@ -74,7 +75,7 @@ public class RegisterPlugins implements Startable {
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) {
@@ -83,15 +84,17 @@ public class RegisterPlugins implements Startable {
.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);
}
@@ -101,4 +104,15 @@ public class RegisterPlugins implements Startable {
}
}
+ 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);
+ }
+ }
+
}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
index 73d262e9ff4..d93d0df6b5d 100644
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
+++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
@@ -29,14 +29,15 @@ import org.junit.Test;
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 {
@@ -44,7 +45,7 @@ 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
@@ -55,17 +56,17 @@ public class GeneratePluginIndexTest {
@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();
}
@@ -77,12 +78,12 @@ public class GeneratePluginIndexTest {
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);
}
}
diff --git a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java
index 18d4b454f79..cb13a5200bb 100644
--- a/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java
+++ b/server/sonar-webserver-core/src/test/java/org/sonar/server/startup/RegisterPluginsTest.java
@@ -35,8 +35,11 @@ import org.sonar.core.util.UuidFactory;
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;
@@ -52,10 +55,10 @@ public class RegisterPluginsTest {
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() {
@@ -71,17 +74,17 @@ public class RegisterPluginsTest {
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();
}
@@ -96,6 +99,7 @@ public class RegisterPluginsTest {
.setKee("java")
.setBasePluginKey(null)
.setFileHash("bd451e47a1aa76e73da0359cef63dd63")
+ .setType(Type.BUNDLED)
.setCreatedAt(1L)
.setUpdatedAt(1L));
dbClient.pluginDao().insert(dbTester.getSession(), new PluginDto()
@@ -103,39 +107,69 @@ public class RegisterPluginsTest {
.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);
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/UpgradesAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/UpgradesAction.java
index 042fe1bbbfe..7dee70266d1 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/UpgradesAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/platform/ws/UpgradesAction.java
@@ -19,9 +19,9 @@
*/
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;
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/AvailableAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/AvailableAction.java
index 108210c2f2f..dcfc53c9a56 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/AvailableAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/AvailableAction.java
@@ -19,10 +19,10 @@
*/
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;
@@ -94,10 +94,7 @@ public class AvailableAction implements PluginsWsAction {
}
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());
}
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/DownloadAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/DownloadAction.java
index c837f38ac07..fdccdd69131 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/DownloadAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/DownloadAction.java
@@ -27,9 +27,9 @@ import org.sonar.api.server.ws.Request;
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 {
@@ -37,10 +37,10 @@ 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
@@ -64,22 +64,22 @@ public class DownloadAction implements PluginsWsAction {
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())) {
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/InstallAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/InstallAction.java
index 7ddfef1d761..ee9963cb9f5 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/InstallAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/InstallAction.java
@@ -19,8 +19,8 @@
*/
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;
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java
index 485794fc62a..519b5b6872f 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/InstalledAction.java
@@ -20,12 +20,13 @@
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;
@@ -34,8 +35,10 @@ import org.sonar.api.utils.text.JsonWriter;
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;
@@ -54,14 +57,14 @@ import static org.sonar.server.plugins.ws.PluginWSCommons.compatiblePluginsByKey
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;
}
@@ -72,12 +75,12 @@ public class InstalledAction implements PluginsWsAction {
.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"));
@@ -87,11 +90,18 @@ public class InstalledAction implements PluginsWsAction {
"<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()));
@@ -106,7 +116,7 @@ public class InstalledAction implements PluginsWsAction {
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());
@@ -117,7 +127,12 @@ public class InstalledAction implements PluginsWsAction {
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());
}
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/PendingAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/PendingAction.java
index 5f1726e56f4..220f142ea8d 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/PendingAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/PendingAction.java
@@ -19,15 +19,13 @@
*/
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;
@@ -41,8 +39,6 @@ import org.sonar.server.plugins.UpdateCenterMatrixFactory;
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;
@@ -59,15 +55,15 @@ public class PendingAction implements PluginsWsAction {
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;
}
@@ -100,13 +96,13 @@ public class PendingAction implements PluginsWsAction {
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);
@@ -127,26 +123,4 @@ public class PendingAction implements PluginsWsAction {
}
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());
- }
- }
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java
index 9704a8c89b0..9caea752d46 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java
@@ -20,19 +20,19 @@
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;
@@ -78,8 +78,8 @@ public class PluginWSCommons {
public static final Ordering<PluginInfo> NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural()
.onResultOf(PluginInfo::getName)
.compound(Ordering.natural().onResultOf(PluginInfo::getKey));
- public static final Comparator<InstalledPlugin> NAME_KEY_COMPARATOR = Comparator
- .comparing((java.util.function.Function<InstalledPlugin, String>) installedPluginFile -> installedPluginFile.getPluginInfo().getName())
+ 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)
@@ -92,7 +92,7 @@ public class PluginWSCommons {
// 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());
@@ -115,9 +115,9 @@ public class PluginWSCommons {
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();
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UninstallAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UninstallAction.java
index 6fc841762ee..51933509285 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UninstallAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UninstallAction.java
@@ -22,26 +22,19 @@ package org.sonar.server.plugins.ws;
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;
}
@@ -64,18 +57,7 @@ public class UninstallAction implements PluginsWsAction {
@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();
}
-
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UpdateAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UpdateAction.java
index 69c802e9a56..d142e15854b 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UpdateAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UpdateAction.java
@@ -19,11 +19,8 @@
*/
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;
@@ -86,11 +83,9 @@ public class UpdateAction implements PluginsWsAction {
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) {
@@ -99,17 +94,4 @@ public class UpdateAction implements PluginsWsAction {
}
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());
- }
- }
}
diff --git a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UpdatesAction.java b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UpdatesAction.java
index b6201298727..aef313273c1 100644
--- a/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UpdatesAction.java
+++ b/server/sonar-webserver-webapi/src/main/java/org/sonar/server/plugins/ws/UpdatesAction.java
@@ -19,12 +19,12 @@
*/
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;
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/language/LanguageValidationTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/language/LanguageValidationTest.java
index 3f1af6853cb..4dd4d6ce7b9 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/language/LanguageValidationTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/language/LanguageValidationTest.java
@@ -25,6 +25,7 @@ import org.junit.rules.ExpectedException;
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;
@@ -43,11 +44,11 @@ public class LanguageValidationTest {
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
@@ -67,6 +68,7 @@ public class LanguageValidationTest {
when(lang2.getKey()).thenReturn("key2");
ServerPluginRepository repo = mock(ServerPluginRepository.class);
+
when(repo.getPluginKey(lang1)).thenReturn("plugin1");
when(repo.getPluginKey(lang2)).thenReturn("plugin2");
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/UpgradesActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/UpgradesActionTest.java
index 1ba9387ddf2..fecc923f3e4 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/UpgradesActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/platform/ws/UpgradesActionTest.java
@@ -19,7 +19,7 @@
*/
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;
@@ -114,7 +114,7 @@ public class UpgradesActionTest {
@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();
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java
index a893e478e61..60d60e683e3 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/AbstractUpdateCenterBasedPluginsWsActionTest.java
@@ -19,7 +19,7 @@
*/
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;
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/AvailableActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/AvailableActionTest.java
index 726d7ce490b..5939880a23c 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/AvailableActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/AvailableActionTest.java
@@ -19,7 +19,7 @@
*/
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;
@@ -138,7 +138,7 @@ public class AvailableActionTest extends AbstractUpdateCenterBasedPluginsWsActio
@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();
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/DownloadActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/DownloadActionTest.java
index 3cc798a12be..1a579ae1192 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/DownloadActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/DownloadActionTest.java
@@ -31,14 +31,17 @@ import org.junit.rules.TemporaryFolder;
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;
@@ -49,9 +52,9 @@ public class DownloadActionTest {
@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() {
@@ -66,90 +69,87 @@ public class DownloadActionTest {
@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 {
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/InstallActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/InstallActionTest.java
index 172c52ffa74..8c73582d38a 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/InstallActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/InstallActionTest.java
@@ -19,11 +19,11 @@
*/
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;
@@ -50,7 +50,6 @@ import static org.mockito.Mockito.when;
@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";
@@ -153,7 +152,7 @@ public class InstallActionTest {
@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'");
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java
index dae4f95aebc..11e833ff1d9 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/InstalledActionTest.java
@@ -19,14 +19,17 @@
*/
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;
@@ -38,9 +41,11 @@ import org.sonar.api.server.ws.WebService.Action;
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;
@@ -52,6 +57,7 @@ import static java.util.Collections.singletonList;
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;
@@ -70,8 +76,8 @@ public class InstalledActionTest {
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
@@ -106,7 +112,7 @@ public class InstalledActionTest {
@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();
@@ -114,8 +120,62 @@ public class InstalledActionTest {
}
@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("")
@@ -127,6 +187,7 @@ public class InstalledActionTest {
.setIssueTrackerUrl(""))));
db.pluginDbTester().insertPlugin(
p -> p.setKee("foo"),
+ p -> p.setType(Type.EXTERNAL),
p -> p.setUpdatedAt(100L));
String response = tester.newRequest().execute().getInput();
@@ -139,23 +200,26 @@ public class InstalledActionTest {
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"))
@@ -166,14 +230,15 @@ public class InstalledActionTest {
.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\":" +
@@ -191,8 +256,8 @@ public class InstalledActionTest {
" \"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" +
" }" +
" ]" +
@@ -201,7 +266,7 @@ public class InstalledActionTest {
@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"))
@@ -213,10 +278,11 @@ public class InstalledActionTest {
.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();
@@ -240,8 +306,8 @@ public class InstalledActionTest {
" \"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" +
" }" +
" ]" +
@@ -252,8 +318,8 @@ public class InstalledActionTest {
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"))
@@ -262,8 +328,8 @@ public class InstalledActionTest {
.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(
@@ -273,6 +339,7 @@ public class InstalledActionTest {
db.pluginDbTester().insertPlugin(
p -> p.setKee("plugKey"),
+ p -> p.setType(Type.EXTERNAL),
p -> p.setFileHash("abcdplugKey"),
p -> p.setUpdatedAt(111111L));
@@ -304,7 +371,7 @@ public class InstalledActionTest {
@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"),
@@ -313,18 +380,22 @@ public class InstalledActionTest {
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));
@@ -350,16 +421,19 @@ public class InstalledActionTest {
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
@@ -392,13 +466,14 @@ public class InstalledActionTest {
@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));
@@ -414,13 +489,13 @@ public class InstalledActionTest {
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);
}
}
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java
index 91778eb05ed..a5653c14d0a 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/PendingActionTest.java
@@ -19,9 +19,9 @@
*/
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;
@@ -47,9 +47,6 @@ import static org.mockito.Mockito.when;
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
@@ -105,7 +102,7 @@ public class PendingActionTest {
@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();
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java
index 1c01b48b1a1..d0a43431ed9 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java
@@ -27,8 +27,8 @@ import org.junit.rules.TemporaryFolder;
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;
@@ -37,6 +37,7 @@ import org.sonar.updatecenter.common.Version;
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;
@@ -96,25 +97,25 @@ public class PluginWSCommonsTest {
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() + "\"" +
"}");
}
@@ -193,6 +194,7 @@ public class PluginWSCommonsTest {
" }" +
"}");
}
+
@Test
public void status_COMPATIBLE_is_displayed_COMPATIBLE_in_JSON() {
assertThat(toJSon(COMPATIBLE)).isEqualTo("COMPATIBLE");
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/UninstallActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/UninstallActionTest.java
index a11b034c594..8d4145fea2d 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/UninstallActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/UninstallActionTest.java
@@ -19,61 +19,54 @@
*/
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
@@ -95,53 +88,14 @@ public class UninstallActionTest {
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)
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/UpdateActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/UpdateActionTest.java
index f3501176c1b..a3954f67dd6 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/UpdateActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/plugins/ws/UpdateActionTest.java
@@ -19,8 +19,8 @@
*/
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;
@@ -124,7 +124,7 @@ public class UpdateActionTest {
@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'");
diff --git a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/updatecenter/ws/InstalledPluginsActionTest.java b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/updatecenter/ws/PluginsActionTestFilesAndMD5.java
index e4e22e2ddcf..1f78451a190 100644
--- a/server/sonar-webserver-webapi/src/test/java/org/sonar/server/updatecenter/ws/InstalledPluginsActionTest.java
+++ b/server/sonar-webserver-webapi/src/test/java/org/sonar/server/updatecenter/ws/PluginsActionTestFilesAndMD5.java
@@ -33,7 +33,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.sonar.test.JsonAssert.assertJson;
-public class InstalledPluginsActionTest {
+public class PluginsActionTestFilesAndMD5 {
private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java
index cfbb6f6d802..40361b6e4b1 100644
--- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel2.java
@@ -22,8 +22,8 @@ package org.sonar.server.platform.platformlevel;
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;
@@ -41,8 +41,11 @@ import org.sonar.server.platform.db.migration.history.MigrationHistoryTableImpl;
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;
@@ -70,10 +73,12 @@ public class PlatformLevel2 extends PlatformLevel {
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,
diff --git a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
index 79cf44d96c8..296a3d6f872 100644
--- a/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
+++ b/server/sonar-webserver/src/main/java/org/sonar/server/platform/platformlevel/PlatformLevel4.java
@@ -248,8 +248,8 @@ public class PlatformLevel4 extends PlatformLevel {
ClusterVerification.class,
LogServerId.class,
LogOAuthWarning.class,
- PluginDownloader.class,
PluginUninstaller.class,
+ PluginDownloader.class,
PageRepository.class,
ResourceTypes.class,
DefaultResourceTypes.get(),
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java b/sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java
index 09d846aec04..bda09ba02c1 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/ExplodedPlugin.java
@@ -23,12 +23,13 @@ import java.io.File;
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;
@@ -45,4 +46,8 @@ public class ExplodedPlugin {
public Collection<File> getLibs() {
return libs;
}
+
+ public PluginInfo getPluginInfo() {
+ return pluginInfo;
+ }
}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginClassLoader.java
index 5b997d9d407..503bc4dae0a 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginClassLoader.java
@@ -25,12 +25,13 @@ 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.Arrays.asList;
+import static java.util.Collections.singleton;
/**
* Loads the plugin JAR files by creating the appropriate classloaders and by instantiating
@@ -46,22 +47,22 @@ import static java.util.Arrays.asList;
* <p/>
* This class is stateless. It does not keep pointers to classloaders and {@link org.sonar.api.Plugin}.
*/
-public class PluginLoader {
-
+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 PluginJarExploder jarExploder;
private final PluginClassloaderFactory classloaderFactory;
- public PluginLoader(PluginJarExploder jarExploder, PluginClassloaderFactory classloaderFactory) {
- this.jarExploder = jarExploder;
+ public PluginClassLoader(PluginClassloaderFactory classloaderFactory) {
this.classloaderFactory = classloaderFactory;
}
- public Map<String, Plugin> load(Map<String, PluginInfo> infoByKeys) {
- Collection<PluginClassLoaderDef> defs = defineClassloaders(infoByKeys);
+ 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);
}
@@ -71,19 +72,19 @@ public class PluginLoader {
* different than number of plugins.
*/
@VisibleForTesting
- Collection<PluginClassLoaderDef> defineClassloaders(Map<String, PluginInfo> infoByKeys) {
+ Collection<PluginClassLoaderDef> defineClassloaders(Map<String, ExplodedPlugin> pluginsByKey) {
Map<String, PluginClassLoaderDef> classloadersByBasePlugin = new HashMap<>();
- for (PluginInfo info : infoByKeys.values()) {
- String baseKey = basePluginKey(info, infoByKeys);
+ 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);
}
- ExplodedPlugin explodedPlugin = jarExploder.explode(info);
- def.addFiles(asList(explodedPlugin.getMain()));
- def.addFiles(explodedPlugin.getLibs());
+ def.addFiles(singleton(plugin.getMain()));
+ def.addFiles(plugin.getLibs());
def.addMainClass(info.getKey(), info.getMainClass());
for (String defaultSharedResource : DEFAULT_SHARED_RESOURCES) {
@@ -115,8 +116,7 @@ public class PluginLoader {
* @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) {
+ 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()) {
@@ -128,13 +128,11 @@ public class PluginLoader {
String pluginKey = mainClassEntry.getKey();
String mainClass = mainClassEntry.getValue();
try {
- instancesByPluginKey.put(pluginKey, (Plugin) classLoader.loadClass(mainClass).newInstance());
+ 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);
+ 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);
+ throw new IllegalStateException(String.format("Fail to instantiate class [%s] of plugin [%s]", mainClass, pluginKey), e);
}
}
}
@@ -158,11 +156,11 @@ public class PluginLoader {
* 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) {
+ private static String basePluginKey(PluginInfo plugin, Map<String, ExplodedPlugin> pluginsByKey) {
String base = plugin.getKey();
String parentKey = plugin.getBasePlugin();
while (!Strings.isNullOrEmpty(parentKey)) {
- PluginInfo parentPlugin = allPluginsPerKey.get(parentKey);
+ PluginInfo parentPlugin = pluginsByKey.get(parentKey).getPluginInfo();
base = parentPlugin.getKey();
parentKey = parentPlugin.getBasePlugin();
}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java
index be62f1e8859..fe2a6e3145a 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java
@@ -27,6 +27,7 @@ import java.io.File;
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;
@@ -48,57 +49,6 @@ public class PluginInfo implements Comparable<PluginInfo> {
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;
@@ -385,18 +335,13 @@ public class PluginInfo implements Comparable<PluginInfo> {
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
@@ -419,41 +364,48 @@ public class PluginInfo implements Comparable<PluginInfo> {
@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) {
@@ -467,4 +419,54 @@ public class PluginInfo implements Comparable<PluginInfo> {
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();
+ }
+ }
}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginJarExploder.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginJarExploder.java
index 508aa642666..7c97461c034 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/PluginJarExploder.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginJarExploder.java
@@ -31,13 +31,13 @@ public abstract class PluginJarExploder {
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()) {
@@ -45,6 +45,6 @@ public abstract class PluginJarExploder {
} else {
libs = Collections.emptyList();
}
- return new ExplodedPlugin(pluginKey, jarFile, libs);
+ return new ExplodedPlugin(pluginInfo, pluginInfo.getKey(), jarFile, libs);
}
}
diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java
index c78a88f6d49..b49599f9c12 100644
--- a/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java
+++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java
@@ -21,8 +21,8 @@ package org.sonar.core.platform;
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;
/**
@@ -45,5 +45,7 @@ public interface PluginRepository {
*/
Plugin getPluginInstance(String key);
+ Collection<Plugin> getPluginInstances();
+
boolean hasPlugin(String key);
}
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginClassLoaderTest.java
index 1d12a4edab5..446bb257ae3 100644
--- a/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginClassLoaderTest.java
@@ -37,7 +37,7 @@ 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 {
+public class PluginClassLoaderTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
@@ -46,25 +46,26 @@ public class PluginLoaderTest {
public LogTester logTester = new LogTester();
private PluginClassloaderFactory classloaderFactory = mock(PluginClassloaderFactory.class);
- private PluginLoader underTest = new PluginLoader(new FakePluginExploder(), classloaderFactory);
+ private PluginClassLoader underTest = new PluginClassLoader(classloaderFactory);
@Test
public void define_classloader() throws Exception {
File jarFile = temp.newFile();
- PluginInfo info = new PluginInfo("foo")
+ PluginInfo plugin = new PluginInfo("foo")
.setJarFile(jarFile)
.setMainClass("org.foo.FooPlugin")
.setMinimalSqVersion(Version.create("5.2"));
- Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(ImmutableMap.of("foo", info));
+ 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()).containsOnly(jarFile);
+ assertThat(def.getFiles()).containsAll(explodedPlugin.getLibs());
assertThat(def.getMainClassesByPluginKey()).containsOnly(MapEntry.entry("foo", "org.foo.FooPlugin"));
- // TODO test mask - require change in sonar-classloader
}
/**
@@ -95,19 +96,27 @@ public class PluginLoaderTest {
.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(), base, extension1.getKey(), extension1, extension2.getKey(), extension2));
+ 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()).containsOnly(baseJarFile, extensionJar1, extensionJar2);
+
+ 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"));
- // TODO test mask - require change in sonar-classloader
}
@Test
@@ -118,7 +127,8 @@ public class PluginLoaderTest {
.setUseChildFirstClassLoader(true)
.setMainClass("org.foo.FooPlugin");
- Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(ImmutableMap.of("foo", info));
+ Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(
+ ImmutableMap.of("foo", createExplodedPlugin(info)));
assertThat(defs).extracting(PluginClassLoaderDef::getBasePluginKey).containsExactly("foo");
List<String> warnings = logTester.logs(LoggerLevel.WARN);
@@ -133,20 +143,16 @@ public class PluginLoaderTest {
.setMainClass("org.foo.FooPlugin")
.setMinimalSqVersion(Version.create("4.5.2"));
- Collection<PluginClassLoaderDef> defs = underTest.defineClassloaders(ImmutableMap.of("foo", info));
+ 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.");
}
- /**
- * 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());
- }
+ private ExplodedPlugin createExplodedPlugin(PluginInfo plugin) {
+ return new ExplodedPlugin(plugin, plugin.getKey(), new File(plugin.getKey() + ".jar"), Collections
+ .singleton(new File(plugin.getKey() + "-lib.jar")));
}
}
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java
index ebc67bf175f..d85d614c848 100644
--- a/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java
@@ -24,6 +24,7 @@ import com.tngtech.java.junit.dataprovider.DataProviderRunner;
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;
@@ -31,7 +32,6 @@ import org.apache.commons.io.FileUtils;
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;
@@ -41,6 +41,7 @@ import org.sonar.updatecenter.common.Version;
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)
@@ -49,17 +50,14 @@ public class PluginInfoTest {
@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");
@@ -210,7 +208,7 @@ public class PluginInfoTest {
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();
@@ -237,7 +235,7 @@ public class PluginInfoTest {
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);
@@ -252,7 +250,7 @@ public class PluginInfoTest {
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);
@@ -261,7 +259,7 @@ public class PluginInfoTest {
@DataProvider
public static Object[][] licenseVersions() {
- return new Object[][]{
+ return new Object[][] {
{"0.3"},
{"7.2.0.1253"}
};
@@ -292,7 +290,7 @@ public class PluginInfoTest {
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]");
}
/**
@@ -309,14 +307,13 @@ public class PluginInfoTest {
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) {
diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginJarExploderTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginJarExploderTest.java
index 4e103b1d349..59824693472 100644
--- a/sonar-core/src/test/java/org/sonar/core/platform/PluginJarExploderTest.java
+++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginJarExploderTest.java
@@ -44,7 +44,7 @@ public class PluginJarExploderTest {
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);
}
@@ -65,7 +65,7 @@ public class PluginJarExploderTest {
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);
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalContainer.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalContainer.java
index 6a7f5c663ff..9dfd4d3367d 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalContainer.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/GlobalContainer.java
@@ -42,7 +42,7 @@ import org.sonar.core.extension.CoreExtensionsLoader;
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;
@@ -99,7 +99,7 @@ public class GlobalContainer extends ComponentContainer {
add(
// plugins
ScannerPluginRepository.class,
- PluginLoader.class,
+ PluginClassLoader.class,
PluginClassloaderFactory.class,
ScannerPluginJarExploder.class,
ExtensionInstaller.class,
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginJarExploder.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginJarExploder.java
index c99ab74217a..107d5311268 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginJarExploder.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginJarExploder.java
@@ -42,7 +42,7 @@ public class ScannerPluginJarExploder extends PluginJarExploder {
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);
}
diff --git a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginRepository.java b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginRepository.java
index 801e3b93ad0..2d82aee77a0 100644
--- a/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginRepository.java
+++ b/sonar-scanner-engine/src/main/java/org/sonar/scanner/bootstrap/ScannerPluginRepository.java
@@ -22,18 +22,19 @@ package org.sonar.scanner.bootstrap;
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;
/**
@@ -43,24 +44,25 @@ public class ScannerPluginRepository implements PluginRepository, Startable {
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()) {
@@ -128,6 +130,11 @@ public class ScannerPluginRepository implements PluginRepository, Startable {
}
@Override
+ public Collection<Plugin> getPluginInstances() {
+ return pluginInstancesByKeys.values();
+ }
+
+ @Override
public boolean hasPlugin(String key) {
return pluginsByKeys.containsKey(key);
}
diff --git a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerPluginRepositoryTest.java b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerPluginRepositoryTest.java
index e316868c831..268a8403d7a 100644
--- a/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerPluginRepositoryTest.java
+++ b/sonar-scanner-engine/src/test/java/org/sonar/scanner/bootstrap/ScannerPluginRepositoryTest.java
@@ -20,10 +20,14 @@
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;
@@ -36,8 +40,9 @@ import static org.mockito.Mockito.when;
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() {
@@ -53,6 +58,7 @@ public class ScannerPluginRepositoryTest {
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());
@@ -75,4 +81,13 @@ public class ScannerPluginRepositoryTest {
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")));
+ }
+
+ }
}