From 14a5c982e5f1b28354a853073bd3e225b3914abe Mon Sep 17 00:00:00 2001
From: Simon Brandhof
Date: Fri, 24 Apr 2015 09:15:05 +0200
Subject: [PATCH] SONAR-6370 isolate plugin classloader from core
---
.../plugins/core/charts/package-info.java | 3 -
.../plugins/core/dashboards/package-info.java | 3 -
.../notifications/alerts/package-info.java | 3 -
.../plugins/core/security/package-info.java | 3 -
.../core/timemachine/package-info.java | 3 -
pom.xml | 10 +-
server/sonar-server/pom.xml | 12 +
.../computation/ComputationContainer.java | 2 +-
.../db/migrations/DatabaseMigrator.java | 6 +-
.../debt/DebtModelPluginRepository.java | 22 +-
.../platform/DefaultServerFileSystem.java | 25 +-
.../org/sonar/server/platform/Platform.java | 11 +-
.../server/platform/RailsAppsDeployer.java | 32 +-
.../server/platform/ServerComponents.java | 12 +-
.../platform/monitoring/PluginsMonitor.java | 26 +-
.../InstalledPluginReferentialFactory.java | 4 +-
.../server/plugins/PluginDownloader.java | 24 +-
.../PluginReferentialMetadataConverter.java | 22 +-
.../plugins/ServerExtensionInstaller.java | 43 +-
.../plugins/ServerPluginJarInstaller.java | 62 ---
.../plugins/ServerPluginJarsInstaller.java | 286 -----------
.../plugins/ServerPluginRepository.java | 355 ++++++++++++--
.../server/plugins/ServerPluginUnzipper.java | 65 +++
.../plugins/StaticResourcesServlet.java | 14 +-
.../plugins/ws/CancelAllPluginsWsAction.java | 10 +-
.../plugins/ws/InstalledPluginsWsAction.java | 25 +-
.../plugins/ws/PendingPluginsWsAction.java | 29 +-
.../server/plugins/ws/PluginWSCommons.java | 28 +-
.../plugins/ws/UninstallPluginsWsAction.java | 34 +-
.../org/sonar/server/search/IndexQueue.java | 2 +-
.../server/startup/GeneratePluginIndex.java | 15 +-
.../java/org/sonar/server/ui/JRubyFacade.java | 39 +-
.../debt/DebtModelPluginRepositoryTest.java | 14 +-
.../java/org/sonar/server/es/EsTester.java | 2 +-
.../platform/DefaultServerFileSystemTest.java | 14 -
.../platform/RailsAppsDeployerTest.java | 6 +-
.../monitoring/PluginsMonitorTest.java | 60 +--
...InstalledPluginReferentialFactoryTest.java | 26 +-
.../sonar/server/plugins/MimeTypesTest.java | 6 +
.../server/plugins/PluginDownloaderTest.java | 31 +-
...luginReferentialMetadataConverterTest.java | 18 +-
.../ServerPluginJarsInstallerTest.java | 309 ------------
.../plugins/ServerPluginRepositoryTest.java | 297 +++++++++++-
.../plugins/ServerPluginUnzipperTest.java | 64 +++
.../server/plugins/TestProjectUtils.java | 35 +-
.../ws/CancelAllPluginsWsActionTest.java | 8 +-
.../ws/InstalledPluginsWsActionTest.java | 48 +-
.../ws/PendingPluginsWsActionTest.java | 30 +-
.../plugins/ws/PluginWSCommonsTest.java | 13 +-
.../ws/UninstallPluginsWsActionTest.java | 21 +-
.../startup/GeneratePluginIndexTest.java | 23 +-
.../sonar-server/src/test/projects/.gitignore | 7 +
.../sonar-server/src/test/projects/README.txt | 3 +
server/sonar-server/src/test/projects/pom.xml | 19 +
.../test/projects/test-base-plugin-v2/pom.xml | 36 ++
.../test-base-plugin-v2/src/BasePlugin.java | 11 +
.../target/test-base-plugin-0.2-SNAPSHOT.jar | Bin 0 -> 2437 bytes
.../test/projects/test-base-plugin/pom.xml | 36 ++
.../test-base-plugin/src/BasePlugin.java | 11 +
.../target/test-base-plugin-0.1-SNAPSHOT.jar | Bin 0 -> 2437 bytes
.../test/projects/test-core-plugin/pom.xml | 36 ++
.../test-core-plugin/src/CorePlugin.java | 11 +
.../target/test-core-plugin-0.1-SNAPSHOT.jar | Bin 0 -> 2412 bytes
.../test/projects/test-extend-plugin/pom.xml | 37 ++
.../test-extend-plugin/src/ExtendPlugin.java | 11 +
.../test-extend-plugin-0.1-SNAPSHOT.jar | Bin 0 -> 2481 bytes
.../test/projects/test-libs-plugin/pom.xml | 49 ++
.../test-libs-plugin/src/LibsPlugin.java | 11 +
.../target/test-libs-plugin-0.1-SNAPSHOT.jar | Bin 0 -> 40097 bytes
.../test/projects/test-require-plugin/pom.xml | 37 ++
.../src/RequirePlugin.java | 11 +
.../test-require-plugin-0.1-SNAPSHOT.jar | Bin 0 -> 2488 bytes
.../projects/test-requirenew-plugin/pom.xml | 37 ++
.../src/RequirePlugin.java | 11 +
.../test-requirenew-plugin-0.1-SNAPSHOT.jar | Bin 0 -> 2553 bytes
.../PluginClassLoadersTest/extension.jar | Bin 885 -> 0 bytes
.../PluginClassLoadersTest/foo-plugin.jar | Bin 1460 -> 0 bytes
.../sonar-build-breaker-plugin-0.1.jar | Bin 5027 -> 0 bytes
.../PluginDownloaderTest/foo-plugin-1.0.jar | Bin 955 -> 0 bytes
.../version1/extension.jar | Bin 885 -> 0 bytes
.../version2/extension.jar | Bin 898 -> 0 bytes
.../foo-plugin-1.0.jar | Bin 1076 -> 0 bytes
.../foo-plugin-2.0.jar | Bin 1077 -> 0 bytes
.../PluginMetadataLoaderTest/not-a-plugin.jar | Bin 960 -> 0 bytes
.../PluginMetadataLoaderTest/old-plugin.jar | Bin 980 -> 0 bytes
.../plugins/PluginMetadataTest/foo-plugin.jar | Bin 1460 -> 0 bytes
.../bar-plugin-1.0.jar | Bin 714 -> 0 bytes
.../foo-plugin-1.0.jar | Bin 955 -> 0 bytes
.../foo-plugin-2.0.jar | Bin 901 -> 0 bytes
.../not-a-plugin.jar | Bin 396 -> 0 bytes
.../require-sq-2.5.jar | Bin 12689 -> 0 bytes
.../sonar-artifact-size-plugin-0.2.jar | Bin 7456 -> 0 bytes
.../api/updatecenter_controller.rb | 6 +-
.../bootstrap/BatchExtensionDictionnary.java | 2 +-
...ository.java => BatchPluginInstaller.java} | 70 ++-
.../batch/bootstrap/BatchPluginPredicate.java | 121 +++++
.../bootstrap/BatchPluginRepository.java | 183 ++-----
.../batch/bootstrap/BatchPluginUnzipper.java | 77 +++
.../batch/bootstrap/ExtensionInstaller.java | 23 +-
.../batch/bootstrap/GlobalContainer.java | 68 ++-
...nsRepository.java => PluginInstaller.java} | 31 +-
.../sonar/batch/bootstrap/TaskContainer.java | 2 +-
.../batch/mediumtest/BatchMediumTester.java | 61 +--
.../FakePluginInstaller.java} | 37 +-
.../sonar/batch/scan/ModuleScanContainer.java | 2 +-
.../batch/scan/ProjectScanContainer.java | 2 +-
.../java/org/sonar/batch/scan/ScanTask.java | 2 +-
.../BatchExtensionDictionnaryTest.java | 2 +-
...est.java => BatchPluginInstallerTest.java} | 36 +-
.../bootstrap/BatchPluginPredicateTest.java | 157 ++++++
.../bootstrap/BatchPluginRepositoryTest.java | 450 +++++++++---------
...Test.java => BatchPluginUnzipperTest.java} | 32 +-
.../bootstrap/ExtensionInstallerTest.java | 42 +-
.../batch/bootstrap/GlobalContainerTest.java | 32 --
.../decorator/DecoratorsSelectorTest.java | 2 +-
.../batch/scan/ProjectScanContainerTest.java | 2 +-
.../sonar-checkstyle-plugin-2.8.jar | Bin
.../java/org/sonar/check/package-info.java | 3 -
.../org/sonar/colorizer/package-info.java | 3 -
sonar-core/pom.xml | 14 +-
.../java/org/sonar/core/i18n/DefaultI18n.java | 59 ++-
.../org/sonar/core/i18n/I18nClassloader.java | 36 +-
.../core/issue/workflow/package-info.java | 3 -
.../sonar/core/notification/package-info.java | 3 -
.../core}/platform/ComponentContainer.java | 15 +-
.../sonar/core}/platform/ComponentKeys.java | 9 +-
.../org/sonar/core}/platform/PicoUtils.java | 3 +-
.../org/sonar/core/platform/PluginInfo.java | 353 ++++++++++++++
.../org/sonar/core/platform/PluginLoader.java | 199 ++++++++
.../core}/platform/PluginRepository.java | 27 +-
.../sonar/core/platform/PluginUnzipper.java} | 26 +-
.../sonar/core/platform/UnzippedPlugin.java | 62 +++
.../org/sonar/core/platform/package-info.java | 14 +-
.../core/plugins/DefaultPluginMetadata.java | 310 ------------
.../core/plugins/PluginClassloaders.java | 253 ----------
.../core/plugins/PluginJarInstaller.java | 111 -----
.../org/sonar/core/plugins/RemotePlugin.java | 3 +-
.../org/sonar/core/plugins/package-info.java | 4 +-
.../org/sonar/core/i18n/DefaultI18nTest.java | 12 +-
.../sonar/core/i18n/I18nClassloaderTest.java | 4 +-
.../platform/ComponentContainerTest.java | 14 +-
.../core}/platform/ComponentKeysTest.java | 11 +-
.../sonar/core}/platform/PicoUtilsTest.java | 4 +-
.../sonar/core/platform/PluginInfoTest.java | 203 ++++++++
.../sonar/core/platform/PluginLoaderTest.java | 131 +++++
.../core/platform/PluginUnzipperTest.java | 81 ++++
.../plugins/DefaultPluginMetadataTest.java | 145 ------
.../core/plugins/PluginClassloadersTest.java | 129 -----
.../core/plugins/PluginJarInstallerTest.java | 99 ----
.../plugins/PluginClassloadersTest/bar.jar | Bin 1057 -> 0 bytes
.../plugins/PluginClassloadersTest/foo.jar | Bin 313 -> 0 bytes
.../core/plugins/checkstyle-extension.xml | 1 -
.../sonar/core/plugins/fake2-plugin-1.1.jar | Bin 2673 -> 0 bytes
...ckstyle-extensions-plugin-0.1-SNAPSHOT.jar | Bin 5597 -> 0 bytes
.../plugins/sonar-cobertura-plugin-3.1.1.jar | Bin 14560 -> 0 bytes
...sonar-switch-off-violations-plugin-1.1.jar | Bin 12689 -> 0 bytes
.../org/sonar/api/charts/package-info.java | 3 -
.../java/org/sonar/home/cache/FileCache.java | 44 +-
.../org/sonar/home/cache/FileCacheTest.java | 29 --
.../src/main/java/org/sonar/api/Plugin.java | 2 +-
.../sonar/api/platform/PluginMetadata.java | 74 ---
161 files changed, 3457 insertions(+), 3110 deletions(-)
delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarInstaller.java
delete mode 100644 server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarsInstaller.java
create mode 100644 server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginUnzipper.java
delete mode 100644 server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarsInstallerTest.java
create mode 100644 server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginUnzipperTest.java
rename sonar-core/src/main/java/org/sonar/core/plugins/ResourcesClassloader.java => server/sonar-server/src/test/java/org/sonar/server/plugins/TestProjectUtils.java (57%)
create mode 100644 server/sonar-server/src/test/projects/.gitignore
create mode 100644 server/sonar-server/src/test/projects/README.txt
create mode 100644 server/sonar-server/src/test/projects/pom.xml
create mode 100644 server/sonar-server/src/test/projects/test-base-plugin-v2/pom.xml
create mode 100644 server/sonar-server/src/test/projects/test-base-plugin-v2/src/BasePlugin.java
create mode 100644 server/sonar-server/src/test/projects/test-base-plugin-v2/target/test-base-plugin-0.2-SNAPSHOT.jar
create mode 100644 server/sonar-server/src/test/projects/test-base-plugin/pom.xml
create mode 100644 server/sonar-server/src/test/projects/test-base-plugin/src/BasePlugin.java
create mode 100644 server/sonar-server/src/test/projects/test-base-plugin/target/test-base-plugin-0.1-SNAPSHOT.jar
create mode 100644 server/sonar-server/src/test/projects/test-core-plugin/pom.xml
create mode 100644 server/sonar-server/src/test/projects/test-core-plugin/src/CorePlugin.java
create mode 100644 server/sonar-server/src/test/projects/test-core-plugin/target/test-core-plugin-0.1-SNAPSHOT.jar
create mode 100644 server/sonar-server/src/test/projects/test-extend-plugin/pom.xml
create mode 100644 server/sonar-server/src/test/projects/test-extend-plugin/src/ExtendPlugin.java
create mode 100644 server/sonar-server/src/test/projects/test-extend-plugin/target/test-extend-plugin-0.1-SNAPSHOT.jar
create mode 100644 server/sonar-server/src/test/projects/test-libs-plugin/pom.xml
create mode 100644 server/sonar-server/src/test/projects/test-libs-plugin/src/LibsPlugin.java
create mode 100644 server/sonar-server/src/test/projects/test-libs-plugin/target/test-libs-plugin-0.1-SNAPSHOT.jar
create mode 100644 server/sonar-server/src/test/projects/test-require-plugin/pom.xml
create mode 100644 server/sonar-server/src/test/projects/test-require-plugin/src/RequirePlugin.java
create mode 100644 server/sonar-server/src/test/projects/test-require-plugin/target/test-require-plugin-0.1-SNAPSHOT.jar
create mode 100644 server/sonar-server/src/test/projects/test-requirenew-plugin/pom.xml
create mode 100644 server/sonar-server/src/test/projects/test-requirenew-plugin/src/RequirePlugin.java
create mode 100644 server/sonar-server/src/test/projects/test-requirenew-plugin/target/test-requirenew-plugin-0.1-SNAPSHOT.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/extension.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/foo-plugin.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/sonar-build-breaker-plugin-0.1.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginDownloaderTest/foo-plugin-1.0.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version1/extension.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginExtensionMetadataTest/version2/extension.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-1.0.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/foo-plugin-2.0.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/not-a-plugin.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/old-plugin.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataTest/foo-plugin.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/bar-plugin-1.0.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-1.0.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/foo-plugin-2.0.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/not-a-plugin.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/require-sq-2.5.jar
delete mode 100644 server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginRepositoryTest/sonar-artifact-size-plugin-0.2.jar
rename sonar-batch/src/main/java/org/sonar/batch/bootstrap/{DefaultPluginsRepository.java => BatchPluginInstaller.java} (57%)
create mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java
create mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginUnzipper.java
rename sonar-batch/src/main/java/org/sonar/batch/bootstrap/{PluginsRepository.java => PluginInstaller.java} (63%)
rename sonar-batch/src/main/java/org/sonar/batch/{bootstrap/BatchPluginJarInstaller.java => mediumtest/FakePluginInstaller.java} (52%)
rename sonar-batch/src/test/java/org/sonar/batch/bootstrap/{DefaultPluginsRepositoryTest.java => BatchPluginInstallerTest.java} (66%)
create mode 100644 sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java
rename sonar-batch/src/test/java/org/sonar/batch/bootstrap/{BatchPluginJarInstallerTest.java => BatchPluginUnzipperTest.java} (68%)
rename sonar-batch/src/test/resources/org/sonar/batch/bootstrap/{BatchPluginJarInstallerTest => BatchPluginUnzipperTest}/sonar-checkstyle-plugin-2.8.jar (100%)
rename {sonar-plugin-api/src/main/java/org/sonar/api => sonar-core/src/main/java/org/sonar/core}/platform/ComponentContainer.java (94%)
rename {sonar-plugin-api/src/main/java/org/sonar/api => sonar-core/src/main/java/org/sonar/core}/platform/ComponentKeys.java (93%)
rename {sonar-plugin-api/src/main/java/org/sonar/api => sonar-core/src/main/java/org/sonar/core}/platform/PicoUtils.java (96%)
create mode 100644 sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java
create mode 100644 sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java
rename {sonar-plugin-api/src/main/java/org/sonar/api => sonar-core/src/main/java/org/sonar/core}/platform/PluginRepository.java (64%)
rename sonar-core/src/{test/java/org/sonar/core/plugins/ResourcesClassloaderTest.java => main/java/org/sonar/core/platform/PluginUnzipper.java} (60%)
create mode 100644 sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java
rename server/sonar-server/src/test/java/org/sonar/server/platform/SonarHomeTest.java => sonar-core/src/main/java/org/sonar/core/platform/package-info.java (80%)
delete mode 100644 sonar-core/src/main/java/org/sonar/core/plugins/DefaultPluginMetadata.java
delete mode 100644 sonar-core/src/main/java/org/sonar/core/plugins/PluginClassloaders.java
delete mode 100644 sonar-core/src/main/java/org/sonar/core/plugins/PluginJarInstaller.java
rename {sonar-plugin-api/src/test/java/org/sonar/api => sonar-core/src/test/java/org/sonar/core}/platform/ComponentContainerTest.java (97%)
rename {sonar-plugin-api/src/test/java/org/sonar/api => sonar-core/src/test/java/org/sonar/core}/platform/ComponentKeysTest.java (86%)
rename {sonar-plugin-api/src/test/java/org/sonar/api => sonar-core/src/test/java/org/sonar/core}/platform/PicoUtilsTest.java (99%)
create mode 100644 sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java
create mode 100644 sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java
create mode 100644 sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java
delete mode 100644 sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java
delete mode 100644 sonar-core/src/test/java/org/sonar/core/plugins/PluginClassloadersTest.java
delete mode 100644 sonar-core/src/test/java/org/sonar/core/plugins/PluginJarInstallerTest.java
delete mode 100644 sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar
delete mode 100644 sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/foo.jar
delete mode 100644 sonar-core/src/test/resources/org/sonar/core/plugins/checkstyle-extension.xml
delete mode 100644 sonar-core/src/test/resources/org/sonar/core/plugins/fake2-plugin-1.1.jar
delete mode 100644 sonar-core/src/test/resources/org/sonar/core/plugins/sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar
delete mode 100644 sonar-core/src/test/resources/org/sonar/core/plugins/sonar-cobertura-plugin-3.1.1.jar
delete mode 100644 sonar-core/src/test/resources/org/sonar/core/plugins/sonar-switch-off-violations-plugin-1.1.jar
delete mode 100644 sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginMetadata.java
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/charts/package-info.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/charts/package-info.java
index 080a6b643ec..6d9fc80d519 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/charts/package-info.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/charts/package-info.java
@@ -17,9 +17,6 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-/**
- * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts.
- */
@ParametersAreNonnullByDefault
package org.sonar.plugins.core.charts;
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/package-info.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/package-info.java
index 30550df1482..d901586ae25 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/package-info.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/dashboards/package-info.java
@@ -17,9 +17,6 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-/**
- * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts.
- */
@ParametersAreNonnullByDefault
package org.sonar.plugins.core.dashboards;
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/notifications/alerts/package-info.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/notifications/alerts/package-info.java
index a2cb7d768c1..5e28de8ee2c 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/notifications/alerts/package-info.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/notifications/alerts/package-info.java
@@ -17,9 +17,6 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-/**
- * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts.
- */
@ParametersAreNonnullByDefault
package org.sonar.plugins.core.notifications.alerts;
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/package-info.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/package-info.java
index 03fd8c46350..df9ccea5073 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/package-info.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/security/package-info.java
@@ -17,9 +17,6 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-/**
- * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts.
- */
@ParametersAreNonnullByDefault
package org.sonar.plugins.core.security;
diff --git a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/package-info.java b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/package-info.java
index 870522ec56f..8d487ae9500 100644
--- a/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/package-info.java
+++ b/plugins/sonar-core-plugin/src/main/java/org/sonar/plugins/core/timemachine/package-info.java
@@ -17,9 +17,6 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-/**
- * Deprecated in 4.5.1. JFreechart charts are replaced by Javascript charts.
- */
@ParametersAreNonnullByDefault
package org.sonar.plugins.core.timemachine;
diff --git a/pom.xml b/pom.xml
index 2dee8a71b58..3d6388fb8b0 100644
--- a/pom.xml
+++ b/pom.xml
@@ -595,6 +595,11 @@
sonar-channel
4.1
+
+ org.codehaus.sonar
+ sonar-classloader
+ 1.0
+
org.codehaus.sonar
sonar-markdown
@@ -707,11 +712,6 @@
-
- org.codehaus.plexus
- plexus-classworlds
- 2.5.1
-
com.tinkerpop.blueprints
blueprints-core
diff --git a/server/sonar-server/pom.xml b/server/sonar-server/pom.xml
index 78e7f995a1e..fd3ee697d4f 100644
--- a/server/sonar-server/pom.xml
+++ b/server/sonar-server/pom.xml
@@ -248,7 +248,19 @@
src/main/resources
true
+
+
+
+ src/test/resources
+ false
+
+
+ src/test/projects
+ false
+
+
+
diff --git a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContainer.java b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContainer.java
index 95ad3ef370e..d95084053ae 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContainer.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/computation/ComputationContainer.java
@@ -19,7 +19,7 @@
*/
package org.sonar.server.computation;
-import org.sonar.api.platform.ComponentContainer;
+import org.sonar.core.platform.ComponentContainer;
import org.sonar.core.issue.db.UpdateConflictResolver;
import org.sonar.server.computation.issue.*;
import org.sonar.server.computation.measure.MetricCache;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java
index b506fc5244a..7158b0d5e6d 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/db/migrations/DatabaseMigrator.java
@@ -29,9 +29,9 @@ import org.sonar.api.platform.ServerUpgradeStatus;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.persistence.DdlUtils;
import org.sonar.server.db.DbClient;
-import org.sonar.server.plugins.ServerPluginRepository;
import com.google.common.annotations.VisibleForTesting;
+import org.sonar.server.plugins.ServerPluginRepository;
/**
* Restore schema by executing DDL scripts. Only H2 database is supported.
@@ -46,10 +46,10 @@ public class DatabaseMigrator implements ServerComponent, Startable {
private final ServerUpgradeStatus serverUpgradeStatus;
/**
- * ServerPluginRepository is used to ensure H2 schema creation is done only after copy of bundle plugins have been done
+ * ServerPluginInstaller is used to ensure H2 schema creation is done only after copy of bundle plugins have been done
*/
public DatabaseMigrator(DbClient dbClient, MigrationStep[] migrations, ServerUpgradeStatus serverUpgradeStatus,
- ServerPluginRepository serverPluginRepository) {
+ ServerPluginRepository unused) {
this.dbClient = dbClient;
this.migrations = migrations;
this.serverUpgradeStatus = serverUpgradeStatus;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java b/server/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java
index b0e1cb6bd20..da45c47bf68 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/debt/DebtModelPluginRepository.java
@@ -25,9 +25,9 @@ import com.google.common.collect.Maps;
import org.apache.commons.io.Charsets;
import org.picocontainer.Startable;
import org.sonar.api.Plugin;
-import org.sonar.api.ServerExtension;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
+import org.sonar.api.ServerComponent;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
import java.io.InputStreamReader;
import java.io.Reader;
@@ -45,7 +45,7 @@ import static com.google.common.collect.Lists.newArrayList;
* they must be named "-model.xml".
*
*/
-public class DebtModelPluginRepository implements ServerExtension, Startable {
+public class DebtModelPluginRepository implements ServerComponent, Startable {
public static final String DEFAULT_MODEL = "technical-debt";
@@ -87,14 +87,12 @@ public class DebtModelPluginRepository implements ServerExtension, Startable {
contributingPluginKeyToClassLoader = Maps.newTreeMap();
// Add default model
contributingPluginKeyToClassLoader.put(DEFAULT_MODEL, getClass().getClassLoader());
- for (PluginMetadata metadata : pluginRepository.getMetadata()) {
- String pluginKey = metadata.getKey();
- Plugin plugin = pluginRepository.getPlugin(pluginKey);
- if (plugin != null) {
- ClassLoader classLoader = plugin.getClass().getClassLoader();
- if (classLoader.getResource(getXMLFilePath(pluginKey)) != null) {
- contributingPluginKeyToClassLoader.put(pluginKey, classLoader);
- }
+ for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) {
+ String pluginKey = pluginInfo.getKey();
+ Plugin plugin = pluginRepository.getPluginInstance(pluginKey);
+ ClassLoader classLoader = plugin.getClass().getClassLoader();
+ if (classLoader.getResource(getXMLFilePath(pluginKey)) != null) {
+ contributingPluginKeyToClassLoader.put(pluginKey, classLoader);
}
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java b/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java
index df520c3900a..5258e753057 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/DefaultServerFileSystem.java
@@ -127,27 +127,16 @@ public class DefaultServerFileSystem implements ServerFileSystem, Startable {
return new File(getHomeDir(), "extensions/downloads");
}
- public File getTrashPluginsDir() {
- return new File(getHomeDir(), "extensions/trash");
- }
-
- public List getCorePlugins() {
- File corePluginsDir = new File(getHomeDir(), "lib/core-plugins");
- return getFiles(corePluginsDir, "jar");
- }
-
- public List getBundledPlugins() {
- File corePluginsDir = new File(getHomeDir(), "lib/bundled-plugins");
- return getFiles(corePluginsDir, "jar");
+ public File getInstalledPluginsDir() {
+ return new File(getHomeDir(), "extensions/plugins");
}
- public List getUserPlugins() {
- File pluginsDir = getUserPluginsDir();
- return getFiles(pluginsDir, "jar");
+ public File getBundledPluginsDir() {
+ return new File(getHomeDir(), "lib/bundled-plugins");
}
- public File getUserPluginsDir() {
- return new File(getHomeDir(), "extensions/plugins");
+ public File getCorePluginsDir() {
+ return new File(getHomeDir(), "lib/core-plugins");
}
public File getDeprecatedPluginsDir() {
@@ -172,7 +161,7 @@ public class DefaultServerFileSystem implements ServerFileSystem, Startable {
}
private List getFiles(File dir, String... fileSuffixes) {
- List files = new ArrayList();
+ List files = new ArrayList<>();
if (dir != null && dir.exists()) {
if (fileSuffixes != null && fileSuffixes.length > 0) {
files.addAll(FileUtils.listFiles(dir, fileSuffixes, false));
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java b/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
index 5d857910be2..4f28227339e 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/Platform.java
@@ -19,16 +19,17 @@
*/
package org.sonar.server.platform;
-import java.util.Collection;
-import java.util.Properties;
-import javax.annotation.CheckForNull;
-import javax.servlet.ServletContext;
-import org.sonar.api.platform.ComponentContainer;
+import org.sonar.core.platform.ComponentContainer;
import org.sonar.api.platform.Server;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.persistence.DatabaseVersion;
+import javax.annotation.CheckForNull;
+import javax.servlet.ServletContext;
+import java.util.Collection;
+import java.util.Properties;
+
/**
* @since 2.2
*/
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java b/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java
index c034a78ee38..8f7cb251897 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/RailsAppsDeployer.java
@@ -25,11 +25,11 @@ import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.picocontainer.Startable;
import org.sonar.api.Plugin;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
import org.sonar.api.platform.ServerFileSystem;
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.PluginRepository;
import javax.annotation.Nullable;
@@ -47,28 +47,26 @@ public class RailsAppsDeployer implements Startable {
private static final Logger LOG = Loggers.get(RailsAppsDeployer.class);
private static final String ROR_PATH = "org/sonar/ror/";
- private final ServerFileSystem fileSystem;
+ private final ServerFileSystem fs;
private final PluginRepository pluginRepository;
- public RailsAppsDeployer(ServerFileSystem fileSystem, PluginRepository pluginRepository) {
- this.fileSystem = fileSystem;
+ public RailsAppsDeployer(ServerFileSystem fs, PluginRepository pluginRepository) {
+ this.fs = fs;
this.pluginRepository = pluginRepository;
}
@Override
public void start() {
- LOG.info("Deploy Ruby on Rails applications");
+ LOG.info("Deploying Ruby on Rails applications");
File appsDir = prepareRailsDirectory();
- for (PluginMetadata pluginMetadata : pluginRepository.getMetadata()) {
- String pluginKey = pluginMetadata.getKey();
- Plugin plugin = pluginRepository.getPlugin(pluginKey);
- if (plugin != null) {
- try {
- deployRailsApp(appsDir, pluginKey, plugin.getClass().getClassLoader());
- } catch (Exception e) {
- throw new IllegalStateException("Fail to deploy Ruby on Rails application: " + pluginKey, e);
- }
+ for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) {
+ String pluginKey = pluginInfo.getKey();
+ Plugin plugin = pluginRepository.getPluginInstance(pluginKey);
+ try {
+ deployRailsApp(appsDir, pluginKey, plugin.getClass().getClassLoader());
+ } catch (Exception e) {
+ throw new IllegalStateException(String.format("Fail to deploy Ruby on Rails application of plugin [%s]", pluginKey), e);
}
}
}
@@ -80,7 +78,7 @@ public class RailsAppsDeployer implements Startable {
@VisibleForTesting
File prepareRailsDirectory() {
- File appsDir = new File(fileSystem.getTempDir(), "ror");
+ File appsDir = new File(fs.getTempDir(), "ror");
prepareDir(appsDir);
return appsDir;
}
@@ -88,7 +86,7 @@ public class RailsAppsDeployer implements Startable {
@VisibleForTesting
static void deployRailsApp(File appsDir, final String pluginKey, ClassLoader appClassLoader) {
if (hasRailsApp(pluginKey, appClassLoader)) {
- LOG.info("Deploy app: " + pluginKey);
+ LOG.info("Deploying app: " + pluginKey);
File appDir = new File(appsDir, pluginKey);
ClassLoaderUtils.copyResources(appClassLoader, pathToRubyInitFile(pluginKey), appDir, new Function() {
@Override
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
index b6347f84e61..6844878d9b9 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ServerComponents.java
@@ -22,7 +22,6 @@ package org.sonar.server.platform;
import com.google.common.collect.Lists;
import org.sonar.api.config.EmailSettings;
import org.sonar.api.issue.action.Actions;
-import org.sonar.api.platform.ComponentContainer;
import org.sonar.api.profiles.AnnotationProfileParser;
import org.sonar.api.profiles.XMLProfileParser;
import org.sonar.api.profiles.XMLProfileSerializer;
@@ -58,6 +57,8 @@ import org.sonar.core.persistence.DefaultDatabase;
import org.sonar.core.persistence.MyBatis;
import org.sonar.core.persistence.SemaphoreUpdater;
import org.sonar.core.persistence.SemaphoresImpl;
+import org.sonar.core.platform.ComponentContainer;
+import org.sonar.core.platform.PluginLoader;
import org.sonar.core.purge.PurgeProfiler;
import org.sonar.core.qualitygate.db.ProjectQgateAssociationDao;
import org.sonar.core.qualitygate.db.QualityGateConditionDao;
@@ -216,9 +217,8 @@ import org.sonar.server.platform.ws.UpgradesSystemWsAction;
import org.sonar.server.plugins.InstalledPluginReferentialFactory;
import org.sonar.server.plugins.PluginDownloader;
import org.sonar.server.plugins.ServerExtensionInstaller;
-import org.sonar.server.plugins.ServerPluginJarInstaller;
-import org.sonar.server.plugins.ServerPluginJarsInstaller;
import org.sonar.server.plugins.ServerPluginRepository;
+import org.sonar.server.plugins.ServerPluginUnzipper;
import org.sonar.server.plugins.UpdateCenterClient;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.server.plugins.ws.AvailablePluginsWsAction;
@@ -519,10 +519,10 @@ class ServerComponents {
PlatformRubyBridge.class,
// plugins
- ServerPluginJarsInstaller.class,
- ServerPluginJarInstaller.class,
- InstalledPluginReferentialFactory.class,
ServerPluginRepository.class,
+ ServerPluginUnzipper.class,
+ PluginLoader.class,
+ InstalledPluginReferentialFactory.class,
ServerExtensionInstaller.class,
// depends on plugins
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java
index 9ec58dc4044..591168f2831 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/monitoring/PluginsMonitor.java
@@ -22,12 +22,13 @@ package org.sonar.server.platform.monitoring;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
-import com.google.common.collect.Lists;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
+import org.sonar.updatecenter.common.Version;
+
+import javax.annotation.Nonnull;
import java.util.LinkedHashMap;
-import java.util.List;
/**
* Installed plugins (excluding core plugins)
@@ -47,21 +48,24 @@ public class PluginsMonitor implements Monitor {
@Override
public LinkedHashMap attributes() {
LinkedHashMap attributes = new LinkedHashMap<>();
- for (PluginMetadata plugin : plugins()) {
+ for (PluginInfo plugin : plugins()) {
LinkedHashMap pluginAttributes = new LinkedHashMap<>();
pluginAttributes.put("Name", plugin.getName());
- pluginAttributes.put("Version", plugin.getVersion());
+ Version version = plugin.getVersion();
+ if (version != null) {
+ pluginAttributes.put("Version", version.getName());
+ }
attributes.put(plugin.getKey(), pluginAttributes);
}
return attributes;
}
- private List plugins() {
- return Lists.newArrayList(Iterables.filter(repository.getMetadata(), new Predicate() {
+ private Iterable plugins() {
+ return Iterables.filter(repository.getPluginInfos(), new Predicate() {
@Override
- public boolean apply(PluginMetadata input) {
- return !input.isCore();
+ public boolean apply(@Nonnull PluginInfo info) {
+ return !info.isCore();
}
- }));
+ });
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPluginReferentialFactory.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPluginReferentialFactory.java
index 257cd22ca05..599899c475c 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPluginReferentialFactory.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/InstalledPluginReferentialFactory.java
@@ -20,7 +20,7 @@
package org.sonar.server.plugins;
import org.picocontainer.Startable;
-import org.sonar.api.platform.PluginRepository;
+import org.sonar.core.platform.PluginRepository;
import org.sonar.updatecenter.common.PluginReferential;
public class InstalledPluginReferentialFactory implements Startable {
@@ -51,7 +51,7 @@ public class InstalledPluginReferentialFactory implements Startable {
}
private void init() {
- installedPluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(pluginRepository.getMetadata());
+ installedPluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(pluginRepository.getPluginInfos());
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java
index 1e2dd190f49..b17ac977342 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginDownloader.java
@@ -25,7 +25,7 @@ import org.sonar.api.utils.HttpDownloader;
import org.sonar.api.utils.SonarException;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
-import org.sonar.core.plugins.DefaultPluginMetadata;
+import org.sonar.core.platform.PluginInfo;
import org.sonar.server.platform.DefaultServerFileSystem;
import org.sonar.updatecenter.common.Release;
import org.sonar.updatecenter.common.Version;
@@ -48,6 +48,10 @@ import static org.apache.commons.io.FileUtils.forceMkdir;
import static org.apache.commons.io.FileUtils.toFile;
import static org.apache.commons.lang.StringUtils.substringAfterLast;
+/**
+ * Downloads plugins from update center. Files are copied in the directory extensions/downloads and then
+ * moved to extensions/plugins after server restart.
+ */
public class PluginDownloader implements Startable {
private static final Logger LOG = Loggers.get(PluginDownloader.class);
@@ -56,21 +60,17 @@ public class PluginDownloader implements Startable {
private final UpdateCenterMatrixFactory updateCenterMatrixFactory;
private final HttpDownloader downloader;
- private final ServerPluginJarInstaller installer;
private final File downloadDir;
public PluginDownloader(UpdateCenterMatrixFactory updateCenterMatrixFactory, HttpDownloader downloader,
- DefaultServerFileSystem fileSystem, ServerPluginJarInstaller installer) {
+ DefaultServerFileSystem fileSystem) {
this.updateCenterMatrixFactory = updateCenterMatrixFactory;
this.downloader = downloader;
- this.installer = installer;
this.downloadDir = fileSystem.getDownloadedPluginsDir();
}
/**
- * Delete the temporary files remaining from previous downloads
- *
- * @see #downloadRelease(org.sonar.updatecenter.common.Release)
+ * Deletes the temporary files remaining from previous downloads
*/
@Override
public void start() {
@@ -112,10 +112,10 @@ public class PluginDownloader implements Startable {
}
/**
- * @return the list of download plugins as {@link DefaultPluginMetadata} instances
+ * @return the list of download plugins as {@link PluginInfo} instances
*/
- public Collection getDownloadedPlugins() {
- return newArrayList(transform(listPlugins(this.downloadDir), installer.fileToPlugin()));
+ public Collection getDownloadedPlugins() {
+ return newArrayList(transform(listPlugins(this.downloadDir), PluginInfo.JarToPluginInfo.INSTANCE));
}
public void download(String pluginKey, Version version) {
@@ -154,10 +154,10 @@ public class PluginDownloader implements Startable {
}
private static Collection listTempFile(File dir) {
- return FileUtils.listFiles(dir, new String[]{TMP_SUFFIX}, false);
+ return FileUtils.listFiles(dir, new String[] {TMP_SUFFIX}, false);
}
private static Collection listPlugins(File dir) {
- return FileUtils.listFiles(dir, new String[]{PLUGIN_EXTENSION}, false);
+ return FileUtils.listFiles(dir, new String[] {PLUGIN_EXTENSION}, false);
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginReferentialMetadataConverter.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginReferentialMetadataConverter.java
index 30646c02a95..70afcb42068 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginReferentialMetadataConverter.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/PluginReferentialMetadataConverter.java
@@ -19,10 +19,11 @@
*/
package org.sonar.server.plugins;
-import org.sonar.api.platform.PluginMetadata;
+import org.sonar.core.platform.PluginInfo;
import org.sonar.updatecenter.common.PluginManifest;
import org.sonar.updatecenter.common.PluginReferential;
import org.sonar.updatecenter.common.PluginReferentialManifestConverter;
+import org.sonar.updatecenter.common.Version;
import java.util.Collection;
import java.util.List;
@@ -35,14 +36,14 @@ public class PluginReferentialMetadataConverter {
// Only static call
}
- public static PluginReferential getInstalledPluginReferential(Collection metadata) {
+ public static PluginReferential getInstalledPluginReferential(Collection metadata) {
List pluginManifestList = getPluginManifestList(metadata);
return PluginReferentialManifestConverter.fromPluginManifests(pluginManifestList);
}
- private static List getPluginManifestList(Collection metadata) {
+ private static List getPluginManifestList(Collection metadata) {
List pluginManifestList = newArrayList();
- for (PluginMetadata plugin : metadata) {
+ for (PluginInfo plugin : metadata) {
if (!plugin.isCore()) {
pluginManifestList.add(toPluginManifest(plugin));
}
@@ -50,20 +51,23 @@ public class PluginReferentialMetadataConverter {
return pluginManifestList;
}
- private static PluginManifest toPluginManifest(PluginMetadata metadata) {
+ private static PluginManifest toPluginManifest(PluginInfo metadata) {
PluginManifest pluginManifest = new PluginManifest();
pluginManifest.setKey(metadata.getKey());
pluginManifest.setName(metadata.getName());
- pluginManifest.setVersion(metadata.getVersion());
+ Version version = metadata.getVersion();
+ if (version != null) {
+ pluginManifest.setVersion(version.getName());
+ }
pluginManifest.setDescription(metadata.getDescription());
pluginManifest.setMainClass(metadata.getMainClass());
- pluginManifest.setOrganization(metadata.getOrganization());
+ pluginManifest.setOrganization(metadata.getOrganizationName());
pluginManifest.setOrganizationUrl(metadata.getOrganizationUrl());
pluginManifest.setLicense(metadata.getLicense());
- pluginManifest.setHomepage(metadata.getHomepage());
+ pluginManifest.setHomepage(metadata.getHomepageUrl());
pluginManifest.setIssueTrackerUrl(metadata.getIssueTrackerUrl());
pluginManifest.setBasePlugin(metadata.getBasePlugin());
- pluginManifest.setRequirePlugins(metadata.getRequiredPlugins().toArray(new String []{}));
+ pluginManifest.setRequirePlugins(metadata.getRequiredPlugins().toArray(new String[] {}));
return pluginManifest;
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java
index c084470711c..e9297de4353 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerExtensionInstaller.java
@@ -24,40 +24,43 @@ import com.google.common.collect.ListMultimap;
import org.sonar.api.Extension;
import org.sonar.api.ExtensionProvider;
import org.sonar.api.Plugin;
+import org.sonar.api.ServerComponent;
import org.sonar.api.ServerExtension;
-import org.sonar.api.platform.ComponentContainer;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
+import org.sonar.core.platform.ComponentContainer;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
import java.util.Map;
/**
- * This class adds to picocontainer the server extensions provided by plugins
+ * Loads the plugins server extensions and injects them to DI container
*/
-public class ServerExtensionInstaller {
- private PluginRepository pluginRepository;
+public class ServerExtensionInstaller implements ServerComponent {
+
+ private final PluginRepository pluginRepository;
public ServerExtensionInstaller(PluginRepository pluginRepository) {
this.pluginRepository = pluginRepository;
}
public void installExtensions(ComponentContainer container) {
- ListMultimap installedExtensionsByPlugin = ArrayListMultimap.create();
+ ListMultimap installedExtensionsByPlugin = ArrayListMultimap.create();
- for (PluginMetadata pluginMetadata : pluginRepository.getMetadata()) {
- Plugin plugin = pluginRepository.getPlugin(pluginMetadata.getKey());
- container.addExtension(pluginMetadata, plugin);
+ for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) {
+ String pluginKey = pluginInfo.getKey();
+ Plugin plugin = pluginRepository.getPluginInstance(pluginKey);
+ container.addExtension(pluginInfo, plugin);
for (Object extension : plugin.getExtensions()) {
- if (installExtension(container, pluginMetadata, extension, true) != null) {
- installedExtensionsByPlugin.put(pluginMetadata, extension);
+ if (installExtension(container, pluginInfo, extension, true) != null) {
+ installedExtensionsByPlugin.put(pluginInfo, extension);
} else {
- container.declareExtension(pluginMetadata, extension);
+ container.declareExtension(pluginInfo, extension);
}
}
}
- for (Map.Entry entry : installedExtensionsByPlugin.entries()) {
- PluginMetadata plugin = entry.getKey();
+ for (Map.Entry entry : installedExtensionsByPlugin.entries()) {
+ PluginInfo plugin = entry.getKey();
Object extension = entry.getValue();
if (isExtensionProvider(extension)) {
ExtensionProvider provider = (ExtensionProvider) container.getComponentByKey(extension);
@@ -66,25 +69,25 @@ public class ServerExtensionInstaller {
}
}
- private void installProvider(ComponentContainer container, PluginMetadata plugin, ExtensionProvider provider) {
+ private void installProvider(ComponentContainer container, PluginInfo pluginInfo, ExtensionProvider provider) {
Object obj = provider.provide();
if (obj != null) {
if (obj instanceof Iterable) {
for (Object ext : (Iterable) obj) {
- installExtension(container, plugin, ext, false);
+ installExtension(container, pluginInfo, ext, false);
}
} else {
- installExtension(container, plugin, obj, false);
+ installExtension(container, pluginInfo, obj, false);
}
}
}
- Object installExtension(ComponentContainer container, PluginMetadata pluginMetadata, Object extension, boolean acceptProvider) {
+ Object installExtension(ComponentContainer container, PluginInfo pluginInfo, Object extension, boolean acceptProvider) {
if (isType(extension, ServerExtension.class)) {
if (!acceptProvider && isExtensionProvider(extension)) {
throw new IllegalStateException("ExtensionProvider can not include providers itself: " + extension);
}
- container.addExtension(pluginMetadata, extension);
+ container.addExtension(pluginInfo, extension);
return extension;
}
return null;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarInstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarInstaller.java
deleted file mode 100644
index 777a197defd..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarInstaller.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.apache.commons.io.FileUtils;
-import org.sonar.api.utils.ZipUtils;
-import org.sonar.core.plugins.DefaultPluginMetadata;
-import org.sonar.core.plugins.PluginJarInstaller;
-
-import java.io.File;
-import java.io.IOException;
-import java.util.zip.ZipEntry;
-
-public class ServerPluginJarInstaller extends PluginJarInstaller {
-
- public void installToDir(DefaultPluginMetadata metadata, File pluginBasedir) {
- try {
- File pluginFile = metadata.getFile();
- File deployedPlugin = copyPlugin(pluginBasedir, pluginFile);
- install(metadata, pluginBasedir, deployedPlugin);
- } catch (IOException e) {
- throw new IllegalStateException(FAIL_TO_INSTALL_PLUGIN + metadata, e);
- }
- }
-
- private File copyPlugin(File pluginBasedir, File pluginFile) throws IOException {
- FileUtils.forceMkdir(pluginBasedir);
- File targetFile = new File(pluginBasedir, pluginFile.getName());
- FileUtils.copyFile(pluginFile, targetFile);
- return targetFile;
- }
-
- @Override
- protected File extractPluginDependencies(File pluginFile, File pluginBasedir) throws IOException {
- ZipUtils.unzip(pluginFile, pluginBasedir, new LibFilter());
- return pluginBasedir;
- }
-
- private static final class LibFilter implements ZipUtils.ZipEntryFilter {
- @Override
- public boolean accept(ZipEntry entry) {
- return entry.getName().startsWith("META-INF/lib");
- }
- }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarsInstaller.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarsInstaller.java
deleted file mode 100644
index 9b9ce32dc81..00000000000
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginJarsInstaller.java
+++ /dev/null
@@ -1,286 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.base.Function;
-import com.google.common.base.Joiner;
-import com.google.common.collect.Maps;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.Server;
-import org.sonar.api.platform.ServerUpgradeStatus;
-import org.sonar.api.utils.MessageException;
-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.plugins.DefaultPluginMetadata;
-import org.sonar.server.platform.DefaultServerFileSystem;
-import org.sonar.updatecenter.common.PluginReferential;
-
-import javax.annotation.Nonnull;
-import java.io.File;
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import static com.google.common.collect.Iterables.transform;
-import static com.google.common.collect.Lists.newArrayList;
-import static java.lang.String.format;
-import static org.apache.commons.io.FileUtils.cleanDirectory;
-import static org.apache.commons.io.FileUtils.copyFile;
-import static org.apache.commons.io.FileUtils.deleteDirectory;
-import static org.apache.commons.io.FileUtils.deleteQuietly;
-import static org.apache.commons.io.FileUtils.forceMkdir;
-import static org.apache.commons.io.FileUtils.listFiles;
-import static org.apache.commons.io.FileUtils.moveFile;
-import static org.apache.commons.io.FileUtils.moveFileToDirectory;
-import static org.apache.commons.lang.StringUtils.isNotBlank;
-
-public class ServerPluginJarsInstaller {
-
- private static final Logger LOG = Loggers.get(ServerPluginJarsInstaller.class);
- private static final String FILE_EXTENSION_JAR = "jar";
- private static final Joiner SLASH_JOINER = Joiner.on(" / ").skipNulls();
-
- private final Server server;
- private final DefaultServerFileSystem fs;
- private final ServerPluginJarInstaller installer;
- private final Map pluginByKeys = Maps.newHashMap();
- private final ServerUpgradeStatus serverUpgradeStatus;
- private static final Set BLACKLISTED_PLUGINS = new HashSet(Arrays.asList("scmactivity", "issuesreport"));
-
- public ServerPluginJarsInstaller(Server server, ServerUpgradeStatus serverUpgradeStatus,
- DefaultServerFileSystem fs, ServerPluginJarInstaller installer) {
- this.server = server;
- this.serverUpgradeStatus = serverUpgradeStatus;
- this.fs = fs;
- this.installer = installer;
- }
-
- public void install() {
- Profiler profiler = Profiler.create(LOG).startInfo("Install plugins");
- deleteTrash();
- loadInstalledPlugins();
- copyBundledPlugins();
- moveDownloadedPlugins();
- loadCorePlugins();
- deployPlugins();
- profiler.stopDebug();
- }
-
- private void deleteTrash() {
- File trashDir = fs.getTrashPluginsDir();
- try {
- if (trashDir.exists()) {
- deleteDirectory(trashDir);
- }
- } catch (IOException e) {
- throw new IllegalStateException("Fail to clean the plugin trash directory: " + trashDir, e);
- }
- }
-
- private void loadInstalledPlugins() {
- for (File file : fs.getUserPlugins()) {
- PluginMetadata metadata = installer.fileToPlugin().apply(file);
- if (isNotBlank(metadata.getKey())) {
- loadInstalledPlugin(metadata);
- }
- }
- }
-
- private void loadInstalledPlugin(PluginMetadata metadata) {
- if (BLACKLISTED_PLUGINS.contains(metadata.getKey())) {
- LOG.warn("Plugin {} is blacklisted. Please uninstall it.", metadata.getName());
- } else {
- PluginMetadata existing = pluginByKeys.put(metadata.getKey(), metadata);
- if (existing != null) {
- throw MessageException.of(format("Found two files for the same plugin '%s': %s and %s",
- metadata.getKey(), metadata.getFile().getName(), existing.getFile().getName()));
- }
- }
- }
-
- private void moveDownloadedPlugins() {
- if (fs.getDownloadedPluginsDir().exists()) {
- for (File sourceFile : listJarFiles(fs.getDownloadedPluginsDir())) {
- overridePlugin(sourceFile, true);
- }
- }
- }
-
- private void copyBundledPlugins() {
- if (serverUpgradeStatus.isFreshInstall()) {
- for (File sourceFile : fs.getBundledPlugins()) {
- PluginMetadata metadata = installer.fileToPlugin().apply(sourceFile);
- // lib/bundled-plugins should be copied only if the plugin is not already
- // available in extensions/plugins
- if (!pluginByKeys.containsKey(metadata.getKey())) {
- overridePlugin(sourceFile, false);
- }
- }
- }
- }
-
- private void overridePlugin(File sourceFile, boolean deleteSource) {
- File destDir = fs.getUserPluginsDir();
- File destFile = new File(destDir, sourceFile.getName());
- if (destFile.exists()) {
- // plugin with same filename already installed
- deleteQuietly(destFile);
- }
-
- try {
- if (deleteSource) {
- moveFile(sourceFile, destFile);
- } else {
- copyFile(sourceFile, destFile, true);
- }
- } catch (IOException e) {
- LOG.error(format("Fail to move or copy plugin: %s to %s",
- sourceFile.getAbsolutePath(), destFile.getAbsolutePath()), e);
- }
-
- PluginMetadata metadata = installer.fileToPlugin().apply(destFile);
- if (isNotBlank(metadata.getKey())) {
- PluginMetadata existing = pluginByKeys.put(metadata.getKey(), metadata);
- if (existing != null) {
- if (!existing.getFile().getName().equals(destFile.getName())) {
- deleteQuietly(existing.getFile());
- }
- LOG.info("Plugin " + metadata.getKey() + " replaced by new version");
- }
- }
- }
-
- private void loadCorePlugins() {
- for (File file : fs.getCorePlugins()) {
- PluginMetadata metadata = installer.fileToCorePlugin().apply(file);
- PluginMetadata existing = pluginByKeys.put(metadata.getKey(), metadata);
- if (existing != null) {
- throw new IllegalStateException("Found two plugins with the same key '" + metadata.getKey() + "': " + metadata.getFile().getName() + " and "
- + existing.getFile().getName());
- }
- }
- }
-
- public void uninstall(String pluginKey) {
- for (String key : getPluginReferential().findLastReleasesWithDependencies(pluginKey)) {
- uninstallPlugin(key);
- }
- }
-
- private void uninstallPlugin(String pluginKey) {
- PluginMetadata metadata = pluginByKeys.get(pluginKey);
- if (metadata != null && !metadata.isCore()) {
- try {
- File masterFile = new File(fs.getUserPluginsDir(), metadata.getFile().getName());
- moveFileToDirectory(masterFile, fs.getTrashPluginsDir(), true);
- } catch (IOException e) {
- throw new IllegalStateException("Fail to uninstall plugin: " + pluginKey, e);
- }
- }
- }
-
- public List getUninstalledPluginFilenames() {
- if (!fs.getTrashPluginsDir().exists()) {
- return Collections.emptyList();
- }
-
- return newArrayList(transform(listJarFiles(fs.getTrashPluginsDir()), FileToName.INSTANCE));
- }
-
- /**
- * @return the list of plugins to be uninstalled as {@link DefaultPluginMetadata} instances
- */
- public Collection getUninstalledPlugins() {
- if (!fs.getTrashPluginsDir().exists()) {
- return Collections.emptyList();
- }
-
- return newArrayList(transform(listJarFiles(fs.getTrashPluginsDir()), installer.fileToPlugin()));
- }
-
- public void cancelUninstalls() {
- if (fs.getTrashPluginsDir().exists()) {
- for (File file : listJarFiles(fs.getTrashPluginsDir())) {
- try {
- moveFileToDirectory(file, fs.getUserPluginsDir(), false);
- } catch (IOException e) {
- throw new IllegalStateException("Fail to cancel plugin uninstalls", e);
- }
- }
- }
- }
-
- private void deployPlugins() {
- for (PluginMetadata metadata : pluginByKeys.values()) {
- deploy((DefaultPluginMetadata) metadata);
- }
- }
-
- private void deploy(DefaultPluginMetadata plugin) {
- LOG.info("Deploy plugin {}", SLASH_JOINER.join(plugin.getName(), plugin.getVersion(), plugin.getImplementationBuild()));
-
- if (!plugin.isCompatibleWith(server.getVersion())) {
- throw MessageException.of(format(
- "Plugin %s needs a more recent version of SonarQube than %s. At least %s is expected",
- plugin.getKey(), server.getVersion(), plugin.getSonarVersion()));
- }
-
- try {
- File pluginDeployDir = new File(fs.getDeployedPluginsDir(), plugin.getKey());
- forceMkdir(pluginDeployDir);
- cleanDirectory(pluginDeployDir);
-
- installer.installToDir(plugin, pluginDeployDir);
- } catch (IOException e) {
- throw new IllegalStateException("Fail to deploy the plugin " + plugin, e);
- }
- }
-
- public Collection getMetadata() {
- return pluginByKeys.values();
- }
-
- public PluginMetadata getMetadata(String pluginKey) {
- return pluginByKeys.get(pluginKey);
- }
-
- private PluginReferential getPluginReferential() {
- return PluginReferentialMetadataConverter.getInstalledPluginReferential(getMetadata());
- }
-
- private static Collection listJarFiles(File pluginDir) {
- return listFiles(pluginDir, new String[] {FILE_EXTENSION_JAR}, false);
- }
-
- private enum FileToName implements Function {
- INSTANCE;
-
- @Override
- public String apply(@Nonnull File file) {
- return file.getName();
- }
- }
-}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
index 16028617966..cb9db1f4042 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginRepository.java
@@ -19,76 +19,361 @@
*/
package org.sonar.server.plugins;
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Ordering;
+import org.apache.commons.io.FileUtils;
import org.picocontainer.Startable;
-import org.sonar.api.utils.log.Loggers;
import org.sonar.api.Plugin;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
-import org.sonar.core.plugins.PluginClassloaders;
+import org.sonar.api.platform.Server;
+import org.sonar.api.platform.ServerUpgradeStatus;
+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.DefaultServerFileSystem;
+
+import javax.annotation.Nonnull;
-import javax.annotation.CheckForNull;
+import java.io.File;
+import java.io.IOException;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
import java.util.Map;
+import java.util.Set;
+
+import static com.google.common.collect.Iterables.transform;
+import static com.google.common.collect.Lists.newArrayList;
+import static java.lang.String.format;
+import static org.apache.commons.io.FileUtils.copyFile;
+import static org.apache.commons.io.FileUtils.deleteQuietly;
+import static org.apache.commons.io.FileUtils.moveFile;
+import static org.apache.commons.io.FileUtils.moveFileToDirectory;
/**
- * This class loads JAR files and initializes classloaders of plugins
+ * Manages installation and loading of plugins:
+ *
+ * - installation of bundled plugins on first server startup
+ * - installation of new plugins (effective after server startup)
+ * - un-installation of plugins (effective after server startup)
+ * - cancel pending installations/un-installations
+ * - load plugin bytecode
+ *
*/
public class ServerPluginRepository implements PluginRepository, Startable {
- private final ServerPluginJarsInstaller jarsInstaller;
- private final PluginClassloaders classloaders;
- private Map pluginsByKey;
+ private static final Logger LOG = Loggers.get(ServerPluginRepository.class);
+ private static final String FILE_EXTENSION_JAR = "jar";
+ private static final Set DEFAULT_BLACKLISTED_PLUGINS = ImmutableSet.of("scmactivity", "issuesreport");
+ private static final Joiner SLASH_JOINER = Joiner.on(" / ").skipNulls();
+
+ private final Server server;
+ private final DefaultServerFileSystem fs;
+ private final ServerUpgradeStatus upgradeStatus;
+ private final PluginLoader loader;
+ private Set blacklistedPluginKeys = DEFAULT_BLACKLISTED_PLUGINS;
- public ServerPluginRepository(ServerPluginJarsInstaller jarsInstaller) {
- this.classloaders = new PluginClassloaders(getClass().getClassLoader());
- this.jarsInstaller = jarsInstaller;
+ // following fields are available after startup
+ private final Map pluginInfosByKeys = new HashMap<>();
+ private final Map pluginInstancesByKeys = new HashMap<>();
+
+ public ServerPluginRepository(Server server, ServerUpgradeStatus upgradeStatus,
+ DefaultServerFileSystem fs, PluginLoader loader) {
+ this.server = server;
+ this.upgradeStatus = upgradeStatus;
+ this.fs = fs;
+ this.loader = loader;
+ }
+
+ @VisibleForTesting
+ void setBlacklistedPluginKeys(Set keys) {
+ this.blacklistedPluginKeys = keys;
}
@Override
public void start() {
- jarsInstaller.install();
- Collection metadata = jarsInstaller.getMetadata();
- pluginsByKey = classloaders.init(metadata);
+ loadPreInstalledPlugins();
+ copyBundledPlugins();
+ moveDownloadedPlugins();
+ loadCorePlugins();
+ unloadIncompatiblePlugins();
+ logInstalledPlugins();
+ loadInstances();
}
@Override
public void stop() {
- classloaders.clean();
+ // close classloaders
+ loader.unload(pluginInstancesByKeys.values());
+
+ pluginInstancesByKeys.clear();
+ pluginInfosByKeys.clear();
}
- @Override
- public Plugin getPlugin(String key) {
- return pluginsByKey.get(key);
+ /**
+ * Load the plugins that are located in extensions/plugins. Blacklisted plugins are
+ * deleted.
+ */
+ private void loadPreInstalledPlugins() {
+ for (File file : listJarFiles(fs.getInstalledPluginsDir())) {
+ 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, true);
+ }
+ }
+ }
+
+ /**
+ * Copies the plugins bundled with SonarQube distribution to directory extensions/plugins.
+ * Does nothing if not a fresh installation.
+ */
+ private void copyBundledPlugins() {
+ if (upgradeStatus.isFreshInstall()) {
+ for (File sourceFile : listJarFiles(fs.getBundledPluginsDir())) {
+ PluginInfo info = PluginInfo.create(sourceFile);
+ // lib/bundled-plugins should be copied only if the plugin is not already
+ // available in extensions/plugins
+ if (!pluginInfosByKeys.containsKey(info.getKey())) {
+ overrideAndRegisterPlugin(sourceFile, false);
+ }
+ }
+ }
+ }
+
+ private void registerPluginInfo(PluginInfo info) {
+ if (blacklistedPluginKeys.contains(info.getKey())) {
+ LOG.warn("Plugin {} [{}] is blacklisted and is being uninstalled.", info.getName(), info.getKey());
+ deleteQuietly(info.getFile());
+ return;
+ }
+ PluginInfo existing = pluginInfosByKeys.put(info.getKey(), info);
+ if (existing != null) {
+ throw MessageException.of(format("Found two files for the same plugin [%s]: %s and %s",
+ info.getKey(), info.getFile().getName(), existing.getFile().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, boolean deleteSource) {
+ File destDir = fs.getInstalledPluginsDir();
+ File destFile = new File(destDir, sourceFile.getName());
+ if (destFile.exists()) {
+ // plugin with same filename already installed
+ deleteQuietly(destFile);
+ }
+
+ try {
+ if (deleteSource) {
+ moveFile(sourceFile, destFile);
+ } else {
+ copyFile(sourceFile, destFile, true);
+ }
+ } catch (IOException e) {
+ LOG.error(format("Fail to move or copy plugin: %s to %s",
+ sourceFile.getAbsolutePath(), destFile.getAbsolutePath()), e);
+ }
+
+ PluginInfo info = PluginInfo.create(destFile);
+ PluginInfo existing = pluginInfosByKeys.put(info.getKey(), info);
+ if (existing != null) {
+ if (!existing.getFile().getName().equals(destFile.getName())) {
+ deleteQuietly(existing.getFile());
+ }
+ LOG.info("Plugin {} [{}] updated to version {}", info.getName(), info.getKey(), info.getVersion());
+ } else {
+ LOG.info("Plugin {} [{}] installed", info.getName(), info.getKey());
+ }
+ }
+
+ private void loadCorePlugins() {
+ for (File file : listJarFiles(fs.getCorePluginsDir())) {
+ PluginInfo info = PluginInfo.create(file).setCore(true);
+ registerPluginInfo(info);
+ }
+ }
+
+ /**
+ * Removes the plugins that are not compatible with current environment. In some cases
+ * plugin files can be deleted.
+ */
+ 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.
+ List removedKeys = new ArrayList<>();
+ do {
+ removedKeys.clear();
+ for (PluginInfo plugin : pluginInfosByKeys.values()) {
+ if (!Strings.isNullOrEmpty(plugin.getBasePlugin()) && !pluginInfosByKeys.containsKey(plugin.getBasePlugin())) {
+ // this plugin 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());
+ removedKeys.add(plugin.getKey());
+ }
+
+ if (!plugin.isCompatibleWith(server.getVersion())) {
+ LOG.warn("Plugin {} [{}] is ignored because it requires at least SonarQube {}", plugin.getName(), plugin.getKey(), plugin.getMinimalSqVersion());
+ removedKeys.add(plugin.getKey());
+ }
+
+ for (PluginInfo.RequiredPlugin requiredPlugin : plugin.getRequiredPlugins()) {
+ PluginInfo available = pluginInfosByKeys.get(requiredPlugin.getKey());
+ if (available == null) {
+ // this plugin 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());
+ removedKeys.add(plugin.getKey());
+ } else if (requiredPlugin.getMinimalVersion().compareToIgnoreQualifier(available.getVersion()) > 0) {
+ LOG.warn("Plugin {} [{}] is ignored because the version {} of required plugin [{}] is not supported", plugin.getName(), plugin.getKey(),
+ requiredPlugin.getKey(), requiredPlugin.getMinimalVersion());
+ removedKeys.add(plugin.getKey());
+ }
+ }
+ }
+ for (String removedKey : removedKeys) {
+ pluginInfosByKeys.remove(removedKey);
+ }
+ } while (!removedKeys.isEmpty());
+ }
+
+ private void logInstalledPlugins() {
+ List orderedPlugins = Ordering.natural().sortedCopy(pluginInfosByKeys.values());
+ for (PluginInfo plugin : orderedPlugins) {
+ LOG.info("Plugin: {}", SLASH_JOINER.join(plugin.getName(), plugin.getVersion(), plugin.getImplementationBuild()));
+ }
}
- @CheckForNull
- public ClassLoader getClassLoader(String pluginKey) {
- return classloaders.get(pluginKey);
+ private void loadInstances() {
+ pluginInstancesByKeys.clear();
+ pluginInstancesByKeys.putAll(loader.load(pluginInfosByKeys));
}
- @CheckForNull
- public Class getClass(String pluginKey, String classname) {
- Class clazz = null;
- ClassLoader classloader = getClassLoader(pluginKey);
- if (classloader != null) {
+ /**
+ * Uninstall a plugin and its dependents (the plugins that require the plugin to be uninstalled)
+ */
+ public void uninstall(String pluginKey) {
+ for (PluginInfo otherPlugin : pluginInfosByKeys.values()) {
+ if (!otherPlugin.getKey().equals(pluginKey)) {
+ for (PluginInfo.RequiredPlugin requiredPlugin : otherPlugin.getRequiredPlugins()) {
+ if (requiredPlugin.getKey().equals(pluginKey)) {
+ uninstall(otherPlugin.getKey());
+ }
+ }
+ }
+ }
+
+ PluginInfo info = pluginInfosByKeys.get(pluginKey);
+ if (!info.isCore()) {
try {
- clazz = classloader.loadClass(classname);
+ // we don't reuse info.getFile() just to be sure that file is located in from extensions/plugins
+ File masterFile = new File(fs.getInstalledPluginsDir(), info.getFile().getName());
+ moveFileToDirectory(masterFile, uninstalledPluginsDir(), true);
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to uninstall plugin [" + pluginKey + "]", e);
+ }
+ }
+ }
+
+ public List getUninstalledPluginFilenames() {
+ return newArrayList(transform(listJarFiles(uninstalledPluginsDir()), FileToName.INSTANCE));
+ }
- } catch (ClassNotFoundException e) {
- Loggers.get(getClass()).warn("Class not found in plugin " + pluginKey + ": " + classname, e);
+ /**
+ * @return the list of plugins to be uninstalled as {@link PluginInfo} instances
+ */
+ public Collection getUninstalledPlugins() {
+ return newArrayList(transform(listJarFiles(uninstalledPluginsDir()), PluginInfo.JarToPluginInfo.INSTANCE));
+ }
+
+ public void cancelUninstalls() {
+ for (File file : listJarFiles(uninstalledPluginsDir())) {
+ try {
+ moveFileToDirectory(file, fs.getInstalledPluginsDir(), false);
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to cancel plugin uninstalls", e);
}
}
- return clazz;
+ }
+
+ public Map getPluginInfosByKeys() {
+ return pluginInfosByKeys;
+ }
+
+ @Override
+ public Collection getPluginInfos() {
+ return pluginInfosByKeys.values();
+ }
+
+ @Override
+ public PluginInfo getPluginInfo(String key) {
+ PluginInfo info = pluginInfosByKeys.get(key);
+ if (info == null) {
+ throw new IllegalArgumentException(String.format("Plugin [%s] does not exist", key));
+ }
+ return info;
}
@Override
- public Collection getMetadata() {
- return jarsInstaller.getMetadata();
+ public Plugin getPluginInstance(String key) {
+ Plugin plugin = pluginInstancesByKeys.get(key);
+ if (plugin == null) {
+ throw new IllegalArgumentException(String.format("Plugin [%s] does not exist", key));
+ }
+ return plugin;
}
@Override
- public PluginMetadata getMetadata(String pluginKey) {
- return jarsInstaller.getMetadata(pluginKey);
+ public boolean hasPlugin(String key) {
+ return pluginInfosByKeys.containsKey(key);
}
+ private enum FileToName implements Function {
+ INSTANCE;
+
+ @Override
+ public String apply(@Nonnull File file) {
+ return file.getName();
+ }
+
+ }
+
+ /**
+ * @return existing trash dir
+ */
+ private File uninstalledPluginsDir() {
+ File dir = new File(fs.getTempDir(), "uninstalled-plugins");
+ try {
+ FileUtils.forceMkdir(dir);
+ return dir;
+ } catch (IOException e) {
+ throw new IllegalStateException("Fail to create temp directory: " + dir.getAbsolutePath(), e);
+ }
+ }
+
+ private static Collection listJarFiles(File dir) {
+ if (dir.exists()) {
+ return FileUtils.listFiles(dir, new String[] {FILE_EXTENSION_JAR}, false);
+ }
+ return Collections.emptyList();
+ }
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginUnzipper.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginUnzipper.java
new file mode 100644
index 00000000000..59bc5a850fa
--- /dev/null
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ServerPluginUnzipper.java
@@ -0,0 +1,65 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.apache.commons.io.FileUtils;
+import org.sonar.api.ServerComponent;
+import org.sonar.api.utils.ZipUtils;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginUnzipper;
+import org.sonar.core.platform.UnzippedPlugin;
+import org.sonar.server.platform.DefaultServerFileSystem;
+
+import java.io.File;
+
+import static org.apache.commons.io.FileUtils.cleanDirectory;
+import static org.apache.commons.io.FileUtils.forceMkdir;
+
+public class ServerPluginUnzipper extends PluginUnzipper implements ServerComponent {
+
+ private final DefaultServerFileSystem fs;
+
+ public ServerPluginUnzipper(DefaultServerFileSystem fs) {
+ this.fs = fs;
+ }
+
+ /**
+ * 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}.
+ */
+ @Override
+ public UnzippedPlugin unzip(PluginInfo pluginInfo) {
+ File toDir = new File(fs.getDeployedPluginsDir(), pluginInfo.getKey());
+ try {
+ forceMkdir(toDir);
+ cleanDirectory(toDir);
+
+ File jarSource = pluginInfo.getFile();
+ File jarTarget = new File(toDir, jarSource.getName());
+ FileUtils.copyFile(jarSource, jarTarget);
+ ZipUtils.unzip(jarSource, toDir, newLibFilter());
+ return UnzippedPlugin.createFromUnzippedDir(pluginInfo.getKey(), jarTarget, toDir);
+ } catch (Exception e) {
+ throw new IllegalStateException(String.format(
+ "Fail to unzip plugin [%s] %s to %s", pluginInfo.getKey(), pluginInfo.getFile().getAbsolutePath(), toDir.getAbsolutePath()), e);
+ }
+ }
+}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java
index 8d24a1ae5f0..9a7ab9c3690 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/StaticResourcesServlet.java
@@ -22,8 +22,10 @@ package org.sonar.server.plugins;
import com.google.common.annotations.VisibleForTesting;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
+import org.sonar.api.Plugin;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
+import org.sonar.core.platform.PluginRepository;
import org.sonar.server.platform.Platform;
import javax.servlet.ServletException;
@@ -45,29 +47,25 @@ public class StaticResourcesServlet extends HttpServlet {
String pluginKey = getPluginKey(request);
String resource = getResourcePath(request);
- ServerPluginRepository pluginRepository = Platform.getInstance().getContainer().getComponentByType(ServerPluginRepository.class);
- ClassLoader classLoader = pluginRepository.getClassLoader(pluginKey);
- if (classLoader == null) {
- LOG.error("Plugin not found: " + pluginKey);
+ PluginRepository pluginRepository = Platform.getInstance().getContainer().getComponentByType(PluginRepository.class);
+ if (!pluginRepository.hasPlugin(pluginKey)) {
response.sendError(HttpServletResponse.SC_NOT_FOUND);
return;
}
InputStream in = null;
OutputStream out = null;
try {
- in = classLoader.getResourceAsStream(resource);
+ in = pluginRepository.getPluginInstance(pluginKey).getClass().getClassLoader().getResourceAsStream(resource);
if (in != null) {
// mime type must be set before writing response body
completeContentType(response, resource);
out = response.getOutputStream();
IOUtils.copy(in, out);
-
} else {
- LOG.error("Unable to find resource '" + resource + "' in plugin '" + pluginKey + "'");
response.sendError(HttpServletResponse.SC_NOT_FOUND);
}
} catch (Exception e) {
- LOG.error("Unable to load static resource '" + resource + "' from plugin '" + pluginKey + "'", e);
+ LOG.error(String.format("Unable to load resource [%s] from plugin [%s]", resource, pluginKey), e);
response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
} finally {
IOUtils.closeQuietly(in);
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/CancelAllPluginsWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/CancelAllPluginsWsAction.java
index 93124251589..3899e50985d 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/CancelAllPluginsWsAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/CancelAllPluginsWsAction.java
@@ -24,17 +24,17 @@ import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.server.plugins.PluginDownloader;
-import org.sonar.server.plugins.ServerPluginJarsInstaller;
+import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.user.UserSession;
public class CancelAllPluginsWsAction implements PluginsWsAction {
private final PluginDownloader pluginDownloader;
- private final ServerPluginJarsInstaller pluginJarsInstaller;
+ private final ServerPluginRepository pluginRepository;
- public CancelAllPluginsWsAction(PluginDownloader pluginDownloader, ServerPluginJarsInstaller pluginJarsInstaller) {
+ public CancelAllPluginsWsAction(PluginDownloader pluginDownloader, ServerPluginRepository pluginRepository) {
this.pluginDownloader = pluginDownloader;
- this.pluginJarsInstaller = pluginJarsInstaller;
+ this.pluginRepository = pluginRepository;
}
@Override
@@ -52,7 +52,7 @@ public class CancelAllPluginsWsAction implements PluginsWsAction {
UserSession.get().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);
pluginDownloader.cancelDownloads();
- pluginJarsInstaller.cancelUninstalls();
+ pluginRepository.cancelUninstalls();
response.noContent();
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledPluginsWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledPluginsWsAction.java
index c84b730c96c..dc2f3ff4be7 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledPluginsWsAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/InstalledPluginsWsAction.java
@@ -22,14 +22,15 @@ package org.sonar.server.plugins.ws;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableSortedSet;
import com.google.common.io.Resources;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.server.plugins.ServerPluginRepository;
import javax.annotation.Nullable;
+
import java.util.Collection;
import java.util.SortedSet;
@@ -42,10 +43,10 @@ import static org.sonar.server.plugins.ws.PluginWSCommons.NAME_KEY_PLUGIN_METADA
public class InstalledPluginsWsAction implements PluginsWsAction {
private static final String ARRAY_PLUGINS = "plugins";
- private final PluginRepository pluginRepository;
+ private final ServerPluginRepository pluginRepository;
private final PluginWSCommons pluginWSCommons;
- public InstalledPluginsWsAction(PluginRepository pluginRepository,
+ public InstalledPluginsWsAction(ServerPluginRepository pluginRepository,
PluginWSCommons pluginWSCommons) {
this.pluginRepository = pluginRepository;
this.pluginWSCommons = pluginWSCommons;
@@ -62,39 +63,39 @@ public class InstalledPluginsWsAction implements PluginsWsAction {
@Override
public void handle(Request request, Response response) throws Exception {
- Collection pluginMetadatas = retrieveAndSortPluginMetadata();
+ Collection infos = retrieveAndSortPluginMetadata();
JsonWriter jsonWriter = response.newJsonWriter();
jsonWriter.setSerializeEmptys(false);
jsonWriter.beginObject();
- writeMetadataList(jsonWriter, pluginMetadatas);
+ writeMetadataList(jsonWriter, infos);
jsonWriter.endObject();
jsonWriter.close();
}
- private SortedSet retrieveAndSortPluginMetadata() {
+ private SortedSet retrieveAndSortPluginMetadata() {
return ImmutableSortedSet.copyOf(
NAME_KEY_PLUGIN_METADATA_COMPARATOR,
- filter(pluginRepository.getMetadata(), NotCorePluginsPredicate.INSTANCE)
+ filter(pluginRepository.getPluginInfos(), NotCorePluginsPredicate.INSTANCE)
);
}
- private void writeMetadataList(JsonWriter jsonWriter, Collection pluginMetadatas) {
+ private void writeMetadataList(JsonWriter jsonWriter, Collection pluginMetadatas) {
jsonWriter.name(ARRAY_PLUGINS);
jsonWriter.beginArray();
- for (PluginMetadata pluginMetadata : pluginMetadatas) {
+ for (PluginInfo pluginMetadata : pluginMetadatas) {
pluginWSCommons.writePluginMetadata(jsonWriter, pluginMetadata);
}
jsonWriter.endArray();
}
- private enum NotCorePluginsPredicate implements Predicate {
+ private enum NotCorePluginsPredicate implements Predicate {
INSTANCE;
@Override
- public boolean apply(@Nullable PluginMetadata input) {
+ public boolean apply(@Nullable PluginInfo input) {
return input != null && !input.isCore();
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingPluginsWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingPluginsWsAction.java
index 8c55caeaf68..29eb3cc9b53 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingPluginsWsAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PendingPluginsWsAction.java
@@ -19,14 +19,13 @@
*/
package org.sonar.server.plugins.ws;
-import org.sonar.api.platform.PluginMetadata;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.Response;
import org.sonar.api.server.ws.WebService;
import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.core.plugins.DefaultPluginMetadata;
+import org.sonar.core.platform.PluginInfo;
import org.sonar.server.plugins.PluginDownloader;
-import org.sonar.server.plugins.ServerPluginJarsInstaller;
+import org.sonar.server.plugins.ServerPluginRepository;
import java.util.Collection;
@@ -43,24 +42,24 @@ public class PendingPluginsWsAction implements PluginsWsAction {
private static final String ARRAY_REMOVING = "removing";
private final PluginDownloader pluginDownloader;
- private final ServerPluginJarsInstaller serverPluginJarsInstaller;
+ private final ServerPluginRepository installer;
private final PluginWSCommons pluginWSCommons;
public PendingPluginsWsAction(PluginDownloader pluginDownloader,
- ServerPluginJarsInstaller serverPluginJarsInstaller,
- PluginWSCommons pluginWSCommons) {
+ ServerPluginRepository installer,
+ PluginWSCommons pluginWSCommons) {
this.pluginDownloader = pluginDownloader;
- this.serverPluginJarsInstaller = serverPluginJarsInstaller;
+ this.installer = installer;
this.pluginWSCommons = pluginWSCommons;
}
@Override
public void define(WebService.NewController controller) {
controller.createAction("pending")
- .setDescription("Get the list of plugins which will either be installed or removed at the next startup of the SonarQube instance, sorted by plugin name")
- .setSince("5.2")
- .setHandler(this)
- .setResponseExample(getResource(this.getClass(), "example-pending_plugins.json"));
+ .setDescription("Get the list of plugins which will either be installed or removed at the next startup of the SonarQube instance, sorted by plugin name")
+ .setSince("5.2")
+ .setHandler(this)
+ .setResponseExample(getResource(this.getClass(), "example-pending_plugins.json"));
}
@Override
@@ -80,8 +79,8 @@ public class PendingPluginsWsAction implements PluginsWsAction {
private void writeInstalling(JsonWriter jsonWriter) {
jsonWriter.name(ARRAY_INSTALLING);
jsonWriter.beginArray();
- Collection plugins = pluginDownloader.getDownloadedPlugins();
- for (PluginMetadata pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) {
+ Collection plugins = pluginDownloader.getDownloadedPlugins();
+ for (PluginInfo pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) {
pluginWSCommons.writePluginMetadata(jsonWriter, pluginMetadata);
}
jsonWriter.endArray();
@@ -90,8 +89,8 @@ public class PendingPluginsWsAction implements PluginsWsAction {
private void writeRemoving(JsonWriter jsonWriter) {
jsonWriter.name(ARRAY_REMOVING);
jsonWriter.beginArray();
- Collection plugins = serverPluginJarsInstaller.getUninstalledPlugins();
- for (PluginMetadata pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) {
+ Collection plugins = installer.getUninstalledPlugins();
+ for (PluginInfo pluginMetadata : copyOf(NAME_KEY_PLUGIN_METADATA_COMPARATOR, plugins)) {
pluginWSCommons.writePluginMetadata(jsonWriter, pluginMetadata);
}
jsonWriter.endArray();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java
index 08cc27555b4..c2f8a8b6b63 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/PluginWSCommons.java
@@ -24,13 +24,14 @@ import com.google.common.base.Function;
import com.google.common.collect.Ordering;
import java.util.Comparator;
import javax.annotation.Nonnull;
-import org.sonar.api.platform.PluginMetadata;
import org.sonar.api.utils.text.JsonWriter;
+import org.sonar.core.platform.PluginInfo;
import org.sonar.updatecenter.common.Artifact;
import org.sonar.updatecenter.common.Plugin;
import org.sonar.updatecenter.common.PluginUpdate;
import org.sonar.updatecenter.common.Release;
import org.sonar.updatecenter.common.UpdateCenter;
+import org.sonar.updatecenter.common.Version;
import static com.google.common.collect.Iterables.filter;
import static com.google.common.collect.Iterables.transform;
@@ -59,7 +60,7 @@ public class PluginWSCommons {
static final String PROPERTY_IMPLEMENTATION_BUILD = "implementationBuild";
static final String PROPERTY_CHANGE_LOG_URL = "changeLogUrl";
- public static final Ordering NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural()
+ public static final Ordering NAME_KEY_PLUGIN_METADATA_COMPARATOR = Ordering.natural()
.onResultOf(PluginMetadataToName.INSTANCE)
.compound(Ordering.natural().onResultOf(PluginMetadataToKey.INSTANCE));
public static final Comparator NAME_KEY_PLUGIN_ORDERING = Ordering.from(CASE_INSENSITIVE_ORDER)
@@ -70,23 +71,26 @@ public class PluginWSCommons {
public static final Comparator NAME_KEY_PLUGIN_UPDATE_ORDERING = Ordering.from(NAME_KEY_PLUGIN_ORDERING)
.onResultOf(PluginUpdateToPlugin.INSTANCE);
- public void writePluginMetadata(JsonWriter jsonWriter, PluginMetadata pluginMetadata) {
+ public void writePluginMetadata(JsonWriter jsonWriter, PluginInfo info) {
jsonWriter.beginObject();
- writeMetadata(jsonWriter, pluginMetadata);
+ writeMetadata(jsonWriter, info);
jsonWriter.endObject();
}
- public void writeMetadata(JsonWriter jsonWriter, PluginMetadata pluginMetadata) {
+ public void writeMetadata(JsonWriter jsonWriter, PluginInfo pluginMetadata) {
jsonWriter.prop(PROPERTY_KEY, pluginMetadata.getKey());
jsonWriter.prop(PROPERTY_NAME, pluginMetadata.getName());
jsonWriter.prop(PROPERTY_DESCRIPTION, pluginMetadata.getDescription());
- jsonWriter.prop(PROPERTY_VERSION, pluginMetadata.getVersion());
+ Version version = pluginMetadata.getVersion();
+ if (version != null) {
+ jsonWriter.prop(PROPERTY_VERSION, version.getName());
+ }
jsonWriter.prop(PROPERTY_LICENSE, pluginMetadata.getLicense());
- jsonWriter.prop(PROPERTY_ORGANIZATION_NAME, pluginMetadata.getOrganization());
+ jsonWriter.prop(PROPERTY_ORGANIZATION_NAME, pluginMetadata.getOrganizationName());
jsonWriter.prop(PROPERTY_ORGANIZATION_URL, pluginMetadata.getOrganizationUrl());
- jsonWriter.prop(PROPERTY_HOMEPAGE, pluginMetadata.getHomepage());
+ jsonWriter.prop(PROPERTY_HOMEPAGE, pluginMetadata.getHomepageUrl());
jsonWriter.prop(PROPERTY_ISSUE_TRACKER_URL, pluginMetadata.getIssueTrackerUrl());
jsonWriter.prop(PROPERTY_IMPLEMENTATION_BUILD, pluginMetadata.getImplementationBuild());
}
@@ -221,20 +225,20 @@ public class PluginWSCommons {
}
}
- private enum PluginMetadataToName implements Function {
+ private enum PluginMetadataToName implements Function {
INSTANCE;
@Override
- public String apply(@Nonnull PluginMetadata input) {
+ public String apply(@Nonnull PluginInfo input) {
return input.getName();
}
}
- private enum PluginMetadataToKey implements Function {
+ private enum PluginMetadataToKey implements Function {
INSTANCE;
@Override
- public String apply(@Nonnull PluginMetadata input) {
+ public String apply(@Nonnull PluginInfo input) {
return input.getKey();
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UninstallPluginsWsAction.java b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UninstallPluginsWsAction.java
index bb2e9e1c43a..98399499acd 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UninstallPluginsWsAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/plugins/ws/UninstallPluginsWsAction.java
@@ -19,18 +19,13 @@
*/
package org.sonar.server.plugins.ws;
-import com.google.common.base.Predicate;
-import javax.annotation.Nullable;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
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.permission.GlobalPermissions;
-import org.sonar.server.plugins.ServerPluginJarsInstaller;
+import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.user.UserSession;
-import static com.google.common.collect.Iterables.find;
import static java.lang.String.format;
/**
@@ -39,12 +34,10 @@ import static java.lang.String.format;
public class UninstallPluginsWsAction implements PluginsWsAction {
private static final String PARAM_KEY = "key";
- private final PluginRepository pluginRepository;
- private final ServerPluginJarsInstaller pluginJarsInstaller;
+ private final ServerPluginRepository pluginRepository;
- public UninstallPluginsWsAction(PluginRepository pluginRepository, ServerPluginJarsInstaller pluginJarsInstaller) {
+ public UninstallPluginsWsAction(ServerPluginRepository pluginRepository) {
this.pluginRepository = pluginRepository;
- this.pluginJarsInstaller = pluginJarsInstaller;
}
@Override
@@ -66,27 +59,14 @@ public class UninstallPluginsWsAction implements PluginsWsAction {
UserSession.get().checkGlobalPermission(GlobalPermissions.SYSTEM_ADMIN);
String key = request.mandatoryParam(PARAM_KEY);
ensurePluginIsInstalled(key);
- pluginJarsInstaller.uninstall(key);
+ pluginRepository.uninstall(key);
response.noContent();
}
+ // FIXME should be moved to {@link ServerPluginRepository#uninstall(String)}
private void ensurePluginIsInstalled(String key) {
- if (find(pluginRepository.getMetadata(), new PluginKeyPredicate(key), null) == null) {
- throw new IllegalArgumentException(
- format("No plugin with key '%s' or plugin '%s' is not installed", key, key));
- }
- }
-
- private static class PluginKeyPredicate implements Predicate {
- private final String key;
-
- public PluginKeyPredicate(String key) {
- this.key = key;
- }
-
- @Override
- public boolean apply(@Nullable PluginMetadata input) {
- return input != null && key.equals(input.getKey());
+ if (!pluginRepository.hasPlugin(key)) {
+ throw new IllegalArgumentException(format("Plugin [%s] is not installed", key));
}
}
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/search/IndexQueue.java b/server/sonar-server/src/main/java/org/sonar/server/search/IndexQueue.java
index c29cd525cc9..d20d2c4e825 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/search/IndexQueue.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/search/IndexQueue.java
@@ -29,7 +29,7 @@ import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.sonar.api.ServerComponent;
-import org.sonar.api.platform.ComponentContainer;
+import org.sonar.core.platform.ComponentContainer;
import org.sonar.api.utils.log.Logger;
import org.sonar.api.utils.log.Loggers;
import org.sonar.core.cluster.WorkQueue;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java b/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java
index d79adb553c2..4fe91fb829f 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/startup/GeneratePluginIndex.java
@@ -22,9 +22,9 @@ package org.sonar.server.startup;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.CharUtils;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
-import org.sonar.core.plugins.DefaultPluginMetadata;
+import org.sonar.api.ServerComponent;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
import org.sonar.core.plugins.RemotePlugin;
import org.sonar.server.platform.DefaultServerFileSystem;
@@ -33,10 +33,7 @@ import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
-/**
- * @since 2.11
- */
-public final class GeneratePluginIndex {
+public final class GeneratePluginIndex implements ServerComponent {
private DefaultServerFileSystem fileSystem;
private PluginRepository repository;
@@ -54,8 +51,8 @@ public final class GeneratePluginIndex {
FileUtils.forceMkdir(indexFile.getParentFile());
Writer writer = new FileWriter(indexFile, false);
try {
- for (PluginMetadata metadata : repository.getMetadata()) {
- writer.append(RemotePlugin.create((DefaultPluginMetadata) metadata).marshal());
+ for (PluginInfo pluginInfo : repository.getPluginInfos()) {
+ writer.append(RemotePlugin.create(pluginInfo).marshal());
writer.append(CharUtils.LF);
}
writer.flush();
diff --git a/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java b/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
index 4c2ed9deef1..71a39f1e1f9 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/ui/JRubyFacade.java
@@ -27,13 +27,12 @@ import java.util.List;
import java.util.Map;
import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
+
+import org.sonar.api.Plugin;
import org.sonar.api.config.License;
import org.sonar.api.config.PropertyDefinitions;
import org.sonar.api.config.Settings;
-import org.sonar.api.platform.ComponentContainer;
import org.sonar.api.platform.NewUserHandler;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
import org.sonar.api.resources.Language;
import org.sonar.api.resources.ResourceType;
import org.sonar.api.resources.ResourceTypes;
@@ -44,6 +43,9 @@ import org.sonar.api.web.RubyRailsWebservice;
import org.sonar.api.web.Widget;
import org.sonar.core.persistence.Database;
import org.sonar.core.persistence.DatabaseVersion;
+import org.sonar.core.platform.ComponentContainer;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
import org.sonar.core.resource.ResourceIndexerDao;
import org.sonar.core.timemachine.Periods;
import org.sonar.process.ProcessProperties;
@@ -58,7 +60,6 @@ import org.sonar.server.platform.ServerSettings;
import org.sonar.server.platform.SettingsChangeNotifier;
import org.sonar.server.plugins.InstalledPluginReferentialFactory;
import org.sonar.server.plugins.PluginDownloader;
-import org.sonar.server.plugins.ServerPluginJarsInstaller;
import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.plugins.UpdateCenterMatrixFactory;
import org.sonar.server.rule.RuleRepositories;
@@ -149,15 +150,15 @@ public final class JRubyFacade {
}
public void uninstallPlugin(String pluginKey) {
- get(ServerPluginJarsInstaller.class).uninstall(pluginKey);
+ get(ServerPluginRepository.class).uninstall(pluginKey);
}
public void cancelPluginUninstalls() {
- get(ServerPluginJarsInstaller.class).cancelUninstalls();
+ get(ServerPluginRepository.class).cancelUninstalls();
}
public List getPluginUninstalls() {
- return get(ServerPluginJarsInstaller.class).getUninstalledPluginFilenames();
+ return get(ServerPluginRepository.class).getUninstalledPluginFilenames();
}
public UpdateCenter getUpdatePluginCenter(boolean forceReload) {
@@ -173,12 +174,11 @@ public final class JRubyFacade {
return get(PropertyDefinitions.class);
}
- public boolean hasPlugin(String key) {
- return get(PluginRepository.class).getPlugin(key) != null;
- }
-
- public Collection getPluginsMetadata() {
- return get(PluginRepository.class).getMetadata();
+ /**
+ * Used for WS api/updatecenter/installed_plugins, to be replaced by api/plugins/installed.
+ */
+ public Collection getPluginInfos() {
+ return get(PluginRepository.class).getPluginInfos();
}
public List> getWidgets(String resourceScope, String resourceQualifier, String resourceLanguage, Object[] availableMeasures) {
@@ -285,12 +285,13 @@ public final class JRubyFacade {
}
public Object getComponentByClassname(String pluginKey, String className) {
- Object component = null;
- Class> componentClass = get(ServerPluginRepository.class).getClass(pluginKey, className);
- if (componentClass != null) {
- component = get(componentClass);
+ Plugin plugin = get(PluginRepository.class).getPluginInstance(pluginKey);
+ try {
+ Class componentClass = plugin.getClass().getClassLoader().loadClass(className);
+ return get(componentClass);
+ } catch (ClassNotFoundException e) {
+ throw new IllegalStateException(String.format("Class [%s] not found in plugin [%s]", className, pluginKey), e);
}
- return component;
}
private JRubyI18n getJRubyI18n() {
@@ -391,7 +392,7 @@ public final class JRubyFacade {
ComponentContainer container = Platform.getInstance().getContainer();
DatabaseMigration databaseMigration = container.getComponentByType(DatabaseMigration.class);
if (databaseMigration.status() == DatabaseMigration.Status.RUNNING
- || databaseMigration.status() == DatabaseMigration.Status.FAILED) {
+ || databaseMigration.status() == DatabaseMigration.Status.FAILED) {
return false;
}
if (databaseMigration.status() == DatabaseMigration.Status.SUCCEEDED) {
diff --git a/server/sonar-server/src/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java
index 942bb973c09..730584d829b 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/debt/DebtModelPluginRepositoryTest.java
@@ -28,8 +28,8 @@ import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.sonar.api.SonarPlugin;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
import java.io.FileInputStream;
import java.io.InputStream;
@@ -54,17 +54,15 @@ public class DebtModelPluginRepositoryTest {
@Test
public void test_component_initialization() throws Exception {
// we do have the "csharp-model.xml" file in src/test/resources
- PluginMetadata csharpPluginMetadata = mock(PluginMetadata.class);
- when(csharpPluginMetadata.getKey()).thenReturn("csharp");
+ PluginInfo csharpPluginMetadata = new PluginInfo("csharp");
// but we don' have the "php-model.xml" one
- PluginMetadata phpPluginMetadata = mock(PluginMetadata.class);
- when(phpPluginMetadata.getKey()).thenReturn("php");
+ PluginInfo phpPluginMetadata = new PluginInfo("php");
PluginRepository repository = mock(PluginRepository.class);
- when(repository.getMetadata()).thenReturn(Lists.newArrayList(csharpPluginMetadata, phpPluginMetadata));
+ when(repository.getPluginInfos()).thenReturn(Lists.newArrayList(csharpPluginMetadata, phpPluginMetadata));
FakePlugin fakePlugin = new FakePlugin();
- when(repository.getPlugin(anyString())).thenReturn(fakePlugin);
+ when(repository.getPluginInstance(anyString())).thenReturn(fakePlugin);
modelFinder = new DebtModelPluginRepository(repository, TEST_XML_PREFIX_PATH);
// when
diff --git a/server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java b/server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java
index 5c49de4aef1..e8df89bb003 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/es/EsTester.java
@@ -41,7 +41,7 @@ import org.elasticsearch.node.Node;
import org.elasticsearch.node.NodeBuilder;
import org.elasticsearch.search.SearchHit;
import org.junit.rules.ExternalResource;
-import org.sonar.api.platform.ComponentContainer;
+import org.sonar.core.platform.ComponentContainer;
import org.sonar.server.search.BaseDoc;
import org.sonar.test.TestUtils;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerFileSystemTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerFileSystemTest.java
index c06a7887022..57bf62c771a 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerFileSystemTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/DefaultServerFileSystemTest.java
@@ -37,20 +37,6 @@ public class DefaultServerFileSystemTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
- @Test
- public void find_plugins() throws Exception {
- List plugins = new DefaultServerFileSystem(
- new File(Resources.getResource(PATH + "shouldFindPlugins").toURI()), temp.newFolder(), null).getUserPlugins();
- assertThat(plugins).hasSize(2);
- }
-
- @Test
- public void not_fail_if_no_plugins() throws Exception {
- List plugins = new DefaultServerFileSystem(
- new File(Resources.getResource(PATH + "shouldNotFailIfNoPlugins").toURI()), temp.newFolder(), null).getUserPlugins();
- assertThat(plugins).isEmpty();
- }
-
@Test
public void find_checkstyle_extensions() throws Exception {
ServerFileSystem fs = new DefaultServerFileSystem(
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/RailsAppsDeployerTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/RailsAppsDeployerTest.java
index a64486ca9d9..d110225210d 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/RailsAppsDeployerTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/RailsAppsDeployerTest.java
@@ -23,9 +23,9 @@ import org.apache.commons.io.FileUtils;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
import org.sonar.api.platform.ServerFileSystem;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
import java.io.File;
import java.net.URL;
@@ -77,7 +77,7 @@ public class RailsAppsDeployerTest {
when(fileSystem.getTempDir()).thenReturn(tempDir);
PluginRepository pluginRepository = mock(PluginRepository.class);
- when(pluginRepository.getMetadata()).thenReturn(Collections.emptyList());
+ when(pluginRepository.getPluginInfos()).thenReturn(Collections.emptyList());
new RailsAppsDeployer(fileSystem, pluginRepository).start();
File appDir = new File(tempDir, "ror");
diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java
index ef082c716b2..9e70d80a699 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/platform/monitoring/PluginsMonitorTest.java
@@ -21,22 +21,22 @@
package org.sonar.server.platform.monitoring;
import org.junit.Test;
-import org.sonar.api.Plugin;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
-import org.sonar.core.plugins.DefaultPluginMetadata;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
+import org.sonar.updatecenter.common.Version;
-import java.util.ArrayList;
-import java.util.Collection;
+import java.util.Arrays;
import java.util.LinkedHashMap;
-import java.util.List;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
public class PluginsMonitorTest {
- PluginsMonitor sut = new PluginsMonitor(new FakePluginRepository());;
+ PluginRepository repo = mock(PluginRepository.class);
+ PluginsMonitor sut = new PluginsMonitor(repo);
@Test
public void name() {
@@ -44,42 +44,28 @@ public class PluginsMonitorTest {
}
@Test
- public void plugin_name_and_version() {
+ public void plugin_name_and_version() throws Exception {
+ when(repo.getPluginInfos()).thenReturn(Arrays.asList(
+ new PluginInfo("key-1")
+ .setName("plugin-1")
+ .setVersion(Version.create("1.1")),
+ new PluginInfo("key-2")
+ .setName("plugin-2")
+ .setVersion(Version.create("2.2")),
+ new PluginInfo("no-version")
+ .setName("No Version")));
+
LinkedHashMap attributes = sut.attributes();
assertThat(attributes).containsKeys("key-1", "key-2");
assertThat((Map) attributes.get("key-1"))
.containsEntry("Name", "plugin-1")
.containsEntry("Version", "1.1");
- assertThat((Map)attributes.get("key-2"))
+ assertThat((Map) attributes.get("key-2"))
.containsEntry("Name", "plugin-2")
.containsEntry("Version", "2.2");
- }
-
- private static class FakePluginRepository implements PluginRepository {
-
- @Override
- public Plugin getPlugin(String key) {
- return null;
- }
-
- @Override
- public Collection getMetadata() {
- List plugins = new ArrayList<>();
- plugins.add(DefaultPluginMetadata
- .create("key-1")
- .setName("plugin-1")
- .setVersion("1.1"));
- plugins.add(DefaultPluginMetadata
- .create("key-2")
- .setName("plugin-2")
- .setVersion("2.2"));
- return plugins;
- }
-
- @Override
- public PluginMetadata getMetadata(String pluginKey) {
- return null;
- }
+ assertThat((Map) attributes.get("no-version"))
+ .containsEntry("Name", "No Version")
+ .doesNotContainKey("Version");
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/InstalledPluginReferentialFactoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/InstalledPluginReferentialFactoryTest.java
index 40e1ada4f35..58c0e12c449 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/InstalledPluginReferentialFactoryTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/InstalledPluginReferentialFactoryTest.java
@@ -20,9 +20,8 @@
package org.sonar.server.plugins;
import org.junit.Test;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
-import org.sonar.core.plugins.DefaultPluginMetadata;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
@@ -33,23 +32,22 @@ public class InstalledPluginReferentialFactoryTest {
@Test
public void should_create_plugin_referential() {
- PluginMetadata metadata = mock(DefaultPluginMetadata.class);
- when(metadata.getKey()).thenReturn("foo");
+ PluginInfo info = new PluginInfo("foo");
PluginRepository pluginRepository = mock(PluginRepository.class);
- when(pluginRepository.getMetadata()).thenReturn(newArrayList(metadata));
- InstalledPluginReferentialFactory installedPluginReferentialFactory = new InstalledPluginReferentialFactory(pluginRepository);
+ when(pluginRepository.getPluginInfos()).thenReturn(newArrayList(info));
+ InstalledPluginReferentialFactory factory = new InstalledPluginReferentialFactory(pluginRepository);
- assertThat(installedPluginReferentialFactory.getInstalledPluginReferential()).isNull();
- installedPluginReferentialFactory.start();
- assertThat(installedPluginReferentialFactory.getInstalledPluginReferential()).isNotNull();
- assertThat(installedPluginReferentialFactory.getInstalledPluginReferential().getPlugins()).hasSize(1);
+ assertThat(factory.getInstalledPluginReferential()).isNull();
+ factory.start();
+ assertThat(factory.getInstalledPluginReferential()).isNotNull();
+ assertThat(factory.getInstalledPluginReferential().getPlugins()).hasSize(1);
}
@Test(expected = RuntimeException.class)
public void should_encapsulate_exception() {
PluginRepository pluginRepository = mock(PluginRepository.class);
- when(pluginRepository.getMetadata()).thenThrow(new IllegalArgumentException());
- InstalledPluginReferentialFactory installedPluginReferentialFactory = new InstalledPluginReferentialFactory(pluginRepository);
- installedPluginReferentialFactory.start();
+ when(pluginRepository.getPluginInfos()).thenThrow(new IllegalArgumentException());
+ InstalledPluginReferentialFactory factory = new InstalledPluginReferentialFactory(pluginRepository);
+ factory.start();
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/MimeTypesTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/MimeTypesTest.java
index 6c5f4412026..25b5b4978c4 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/MimeTypesTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/MimeTypesTest.java
@@ -20,6 +20,7 @@
package org.sonar.server.plugins;
import org.junit.Test;
+import org.sonar.test.TestUtils;
import static org.assertj.core.api.Assertions.assertThat;
@@ -35,4 +36,9 @@ public class MimeTypesTest {
assertThat(MimeTypes.getByFilename("static/sqale/sqale.css")).isEqualTo("text/css");
assertThat(MimeTypes.getByFilename("sqale.css")).isEqualTo("text/css");
}
+
+ @Test
+ public void only_statics() throws Exception {
+ assertThat(TestUtils.hasOnlyPrivateConstructors(MimeTypes.class)).isTrue();
+ }
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java
index bf460fffd2b..a5178f40383 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginDownloaderTest.java
@@ -29,11 +29,12 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.sonar.api.utils.HttpDownloader;
import org.sonar.api.utils.SonarException;
-import org.sonar.core.plugins.DefaultPluginMetadata;
+import org.sonar.core.platform.PluginInfo;
import org.sonar.server.platform.DefaultServerFileSystem;
import org.sonar.updatecenter.common.Plugin;
import org.sonar.updatecenter.common.Release;
import org.sonar.updatecenter.common.UpdateCenter;
+import org.sonar.updatecenter.common.Version;
import java.io.File;
import java.net.URI;
@@ -66,7 +67,6 @@ public class PluginDownloaderTest {
UpdateCenter updateCenter;
HttpDownloader httpDownloader;
PluginDownloader pluginDownloader;
- ServerPluginJarInstaller installer;
@Before
public void before() throws Exception {
@@ -88,8 +88,7 @@ public class PluginDownloaderTest {
downloadDir = testFolder.newFolder("downloads");
when(defaultServerFileSystem.getDownloadedPluginsDir()).thenReturn(downloadDir);
- installer = new ServerPluginJarInstaller();
- pluginDownloader = new PluginDownloader(updateCenterMatrixFactory, httpDownloader, defaultServerFileSystem, installer);
+ pluginDownloader = new PluginDownloader(updateCenterMatrixFactory, httpDownloader, defaultServerFileSystem);
}
@After
@@ -155,7 +154,7 @@ public class PluginDownloaderTest {
File downloadDir = testFolder.newFile();
when(defaultServerFileSystem.getDownloadedPluginsDir()).thenReturn(downloadDir);
- pluginDownloader = new PluginDownloader(updateCenterMatrixFactory, httpDownloader, defaultServerFileSystem, installer);
+ pluginDownloader = new PluginDownloader(updateCenterMatrixFactory, httpDownloader, defaultServerFileSystem);
try {
pluginDownloader.start();
fail();
@@ -220,26 +219,18 @@ public class PluginDownloaderTest {
pluginDownloader.start();
assertThat(pluginDownloader.getDownloadedPluginFilenames()).hasSize(0);
- copyFileToDirectory(new File(resource("foo-plugin-1.0.jar")), downloadDir);
+ copyFileToDirectory(TestProjectUtils.jarOf("test-base-plugin"), downloadDir);
assertThat(pluginDownloader.getDownloadedPlugins()).hasSize(1);
- DefaultPluginMetadata metadata = pluginDownloader.getDownloadedPlugins().iterator().next();
- assertThat(metadata.getKey()).isEqualTo("foo");
- assertThat(metadata.getName()).isEqualTo("Foo");
- assertThat(metadata.getVersion()).isEqualTo("1.0");
- assertThat(metadata.getOrganization()).isEqualTo("SonarSource");
- assertThat(metadata.getOrganizationUrl()).isEqualTo("http://www.sonarsource.org");
- assertThat(metadata.getLicense()).isEqualTo("LGPL 3");
- assertThat(metadata.getMainClass()).isEqualTo("foo.Main");
- }
-
- private URI resource(String fileName) throws URISyntaxException {
- URL resource = getClass().getResource(getClass().getSimpleName() + "/" + fileName);
- return resource.toURI();
+ PluginInfo info = pluginDownloader.getDownloadedPlugins().iterator().next();
+ assertThat(info.getKey()).isEqualTo("testbase");
+ assertThat(info.getName()).isEqualTo("Base Plugin");
+ assertThat(info.getVersion()).isEqualTo(Version.create("0.1-SNAPSHOT"));
+ assertThat(info.getMainClass()).isEqualTo("BasePlugin");
}
@Test
- public void getDownloadedPluginFilenames_reads_plugin_metadata_of_files_in_download_folder() throws Exception {
+ public void getDownloadedPluginFilenames_reads_plugin_info_of_files_in_download_folder() throws Exception {
pluginDownloader.start();
assertThat(pluginDownloader.getDownloadedPlugins()).hasSize(0);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginReferentialMetadataConverterTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginReferentialMetadataConverterTest.java
index a7cc85c9694..c7891203caa 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginReferentialMetadataConverterTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/PluginReferentialMetadataConverterTest.java
@@ -20,23 +20,19 @@
package org.sonar.server.plugins;
import org.junit.Test;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.core.plugins.DefaultPluginMetadata;
+import org.sonar.core.platform.PluginInfo;
import org.sonar.updatecenter.common.PluginReferential;
import static com.google.common.collect.Lists.newArrayList;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
public class PluginReferentialMetadataConverterTest {
@Test
- public void should_convert_metadata_to_plugin_referential() {
- PluginMetadata metadata = mock(DefaultPluginMetadata.class);
- when(metadata.getKey()).thenReturn("foo");
+ public void should_convert_info_to_plugin_referential() {
+ PluginInfo info = new PluginInfo("foo");
- PluginReferential pluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(newArrayList(metadata));
+ PluginReferential pluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(newArrayList(info));
assertThat(pluginReferential).isNotNull();
assertThat(pluginReferential.getPlugins()).hasSize(1);
assertThat(pluginReferential.getPlugins().get(0).getKey()).isEqualTo("foo");
@@ -44,11 +40,9 @@ public class PluginReferentialMetadataConverterTest {
@Test
public void should_not_add_core_plugin() {
- PluginMetadata metadata = mock(DefaultPluginMetadata.class);
- when(metadata.getKey()).thenReturn("foo");
- when(metadata.isCore()).thenReturn(true);
+ PluginInfo info = new PluginInfo("foo").setCore(true);
- PluginReferential pluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(newArrayList(metadata));
+ PluginReferential pluginReferential = PluginReferentialMetadataConverter.getInstalledPluginReferential(newArrayList(info));
assertThat(pluginReferential).isNotNull();
assertThat(pluginReferential.getPlugins()).hasSize(0);
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarsInstallerTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarsInstallerTest.java
deleted file mode 100644
index 8edde701e28..00000000000
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginJarsInstallerTest.java
+++ /dev/null
@@ -1,309 +0,0 @@
-/*
- * SonarQube, open source software quality management tool.
- * Copyright (C) 2008-2014 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * SonarQube 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.
- *
- * SonarQube 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.io.Resources;
-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.platform.PluginMetadata;
-import org.sonar.api.platform.Server;
-import org.sonar.api.platform.ServerUpgradeStatus;
-import org.sonar.api.utils.MessageException;
-import org.sonar.core.plugins.DefaultPluginMetadata;
-import org.sonar.server.platform.DefaultServerFileSystem;
-
-import java.io.File;
-import java.util.Collection;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.when;
-
-public class ServerPluginJarsInstallerTest {
-
- @Rule
- public ExpectedException exception = ExpectedException.none();
-
- @Rule
- public TemporaryFolder temp = new TemporaryFolder();
-
- DefaultServerFileSystem fileSystem;
- File homeDir, pluginsDir, downloadsDir, bundledDir, trashDir, coreDir;
- ServerPluginJarInstaller jarInstaller;
- ServerPluginJarsInstaller jarsInstaller;
- Server server = mock(Server.class);
- ServerUpgradeStatus upgradeStatus = mock(ServerUpgradeStatus.class);
-
- @Before
- public void before() throws Exception {
- when(server.getVersion()).thenReturn("3.1");
- when(server.getDeployDir()).thenReturn(temp.newFolder("deploy"));
-
- homeDir = temp.newFolder("home");
- pluginsDir = new File(homeDir, "extensions/plugins");
- FileUtils.forceMkdir(pluginsDir);
- downloadsDir = new File(homeDir, "extensions/downloads");
- trashDir = new File(homeDir, "extensions/trash");
- bundledDir = new File(homeDir, "lib/bundled-plugins");
- coreDir = new File(homeDir, "lib/core-plugins");
- FileUtils.forceMkdir(bundledDir);
-
- fileSystem = new DefaultServerFileSystem(homeDir, temp.newFolder(), server);
- jarInstaller = new ServerPluginJarInstaller();
- jarsInstaller = new ServerPluginJarsInstaller(server, upgradeStatus, fileSystem, jarInstaller);
- }
-
- private File jar(String name) throws Exception {
- return new File(Resources.getResource(getClass(), "ServerPluginJarsInstallerTest/" + name).toURI());
- }
-
- @Test
- public void copy_bundled_plugin_on_fresh_install() throws Exception {
- when(upgradeStatus.isFreshInstall()).thenReturn(true);
- FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), bundledDir);
-
- jarsInstaller.install();
-
- assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1);
- assertThat(new File(pluginsDir, "foo-plugin-1.0.jar")).exists().isFile();
- PluginMetadata plugin = jarsInstaller.getMetadata("foo");
- assertThat(plugin.getName()).isEqualTo("Foo");
- assertThat(plugin.getDeployedFiles()).hasSize(1);
- assertThat(plugin.isCore()).isFalse();
- assertThat(plugin.isUseChildFirstClassLoader()).isFalse();
- }
-
- @Test
- public void do_not_copy_bundled_plugin_on_non_fresh_install() throws Exception {
- when(upgradeStatus.isFreshInstall()).thenReturn(false);
- FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), bundledDir);
-
- jarsInstaller.install();
-
- assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).isEmpty();
- }
-
- @Test
- public void do_not_copy_bundled_plugin_if_already_installed() throws Exception {
- // fresh install but plugins are already packaged in extensions/plugins
- when(upgradeStatus.isFreshInstall()).thenReturn(true);
- FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), bundledDir);
- FileUtils.copyFileToDirectory(jar("foo-plugin-2.0.jar"), pluginsDir);
- FileUtils.copyFileToDirectory(jar("bar-plugin-1.0.jar"), pluginsDir);
-
- jarsInstaller.install();
-
- // do not copy foo 1.0
- assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(2);
- assertThat(new File(pluginsDir, "foo-plugin-2.0.jar")).exists().isFile();
- assertThat(new File(pluginsDir, "bar-plugin-1.0.jar")).exists().isFile();
- PluginMetadata plugin = jarsInstaller.getMetadata("foo");
- assertThat(plugin.getVersion()).isEqualTo("2.0");
- }
-
- @Test
- public void deploy_installed_plugin() throws Exception {
- when(upgradeStatus.isFreshInstall()).thenReturn(false);
- FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), pluginsDir);
-
- jarsInstaller.install();
-
- // check that the plugin is registered
- assertThat(jarsInstaller.getMetadata()).hasSize(1);
- PluginMetadata plugin = jarsInstaller.getMetadata("foo");
- assertThat(plugin.getName()).isEqualTo("Foo");
- assertThat(plugin.getDeployedFiles()).hasSize(1);
- assertThat(plugin.isCore()).isFalse();
- assertThat(plugin.isUseChildFirstClassLoader()).isFalse();
-
- // check that the file is still present in extensions/plugins
- assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(1);
- assertThat(new File(pluginsDir, "foo-plugin-1.0.jar")).exists().isFile();
- }
-
- @Test
- public void ignore_non_plugin_jars() throws Exception {
- when(upgradeStatus.isFreshInstall()).thenReturn(false);
- FileUtils.copyFileToDirectory(jar("not-a-plugin.jar"), pluginsDir);
-
- jarsInstaller.install();
-
- // nothing to install but keep the file
- assertThat(jarsInstaller.getMetadata()).isEmpty();
- assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1);
- assertThat(new File(pluginsDir, "not-a-plugin.jar")).exists().isFile();
- }
-
- @Test
- public void fail_if_plugin_requires_greater_SQ_version() throws Exception {
- exception.expect(MessageException.class);
- exception.expectMessage("Plugin switchoffviolations needs a more recent version of SonarQube than 2.0. At least 2.5 is expected");
-
- when(upgradeStatus.isFreshInstall()).thenReturn(false);
- when(server.getVersion()).thenReturn("2.0");
- FileUtils.copyFileToDirectory(jar("require-sq-2.5.jar"), pluginsDir);
-
- jarsInstaller.install();
- }
-
- @Test
- public void move_downloaded_plugins() throws Exception {
- FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), downloadsDir);
- when(upgradeStatus.isFreshInstall()).thenReturn(false);
-
- jarsInstaller.install();
-
- assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).hasSize(1);
- assertThat(FileUtils.listFiles(downloadsDir, new String[] {"jar"}, false)).isEmpty();
- assertThat(new File(pluginsDir, "foo-plugin-1.0.jar")).exists().isFile();
- }
-
- @Test
- public void downloaded_plugins_overrides_existing_plugin() throws Exception {
- FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), pluginsDir);
- FileUtils.copyFileToDirectory(jar("foo-plugin-2.0.jar"), downloadsDir);
- when(upgradeStatus.isFreshInstall()).thenReturn(false);
-
- jarsInstaller.install();
-
- assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1);
- assertThat(FileUtils.listFiles(downloadsDir, new String[] {"jar"}, false)).isEmpty();
- assertThat(new File(pluginsDir, "foo-plugin-2.0.jar")).exists().isFile();
- }
-
- @Test
- public void downloaded_plugins_overrides_existing_plugin_even_if_same_filename() throws Exception {
- FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), pluginsDir, true);
- // foo-plugin-1.0.jar in extensions/downloads is in fact version 2.0. It's used to verify
- // that it has correctly overridden extensions/plugins/foo-plugin-1.0.jar
- FileUtils.copyFile(jar("foo-plugin-2.0.jar"), new File(downloadsDir, "foo-plugin-1.0.jar"));
- when(upgradeStatus.isFreshInstall()).thenReturn(false);
-
- jarsInstaller.install();
-
- PluginMetadata plugin = jarsInstaller.getMetadata("foo");
- assertThat(plugin).isNotNull();
- assertThat(plugin.getVersion()).isEqualTo("2.0");
- assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1);
- assertThat(FileUtils.listFiles(downloadsDir, new String[] {"jar"}, false)).isEmpty();
- File installed = new File(pluginsDir, "foo-plugin-1.0.jar");
- assertThat(installed).exists().isFile();
- }
-
- @Test
- public void delete_trash() throws Exception {
- FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), trashDir);
- when(upgradeStatus.isFreshInstall()).thenReturn(false);
-
- jarsInstaller.install();
-
- assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).isEmpty();
- assertThat(trashDir).doesNotExist();
- }
-
- @Test
- public void fail_if_two_installed_plugins_with_same_key() throws Exception {
- when(upgradeStatus.isFreshInstall()).thenReturn(false);
- FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), pluginsDir);
- FileUtils.copyFileToDirectory(jar("foo-plugin-2.0.jar"), pluginsDir);
-
- try {
- jarsInstaller.install();
- fail();
- } catch (MessageException e) {
- assertThat(e.getMessage())
- .contains("Found two files for the same plugin 'foo'")
- .contains("foo-plugin-1.0.jar")
- .contains("foo-plugin-2.0.jar");
- }
- }
-
- @Test
- public void uninstall_plugin() throws Exception {
- when(upgradeStatus.isFreshInstall()).thenReturn(false);
- FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), pluginsDir);
-
- jarsInstaller.install();
- jarsInstaller.uninstall("foo");
-
- assertThat(FileUtils.listFiles(pluginsDir, new String[]{"jar"}, false)).isEmpty();
- assertThat(FileUtils.listFiles(trashDir, new String[] {"jar"}, false)).hasSize(1);
- assertThat(jarsInstaller.getUninstalledPluginFilenames()).containsOnly("foo-plugin-1.0.jar");
- }
-
- @Test
- public void pending_removals_reads_metadata() throws Exception {
- when(upgradeStatus.isFreshInstall()).thenReturn(false);
- FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), pluginsDir);
-
- jarsInstaller.install();
- jarsInstaller.uninstall("foo");
-
- assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).isEmpty();
- assertThat(FileUtils.listFiles(trashDir, new String[] {"jar"}, false)).hasSize(1);
- Collection removals = jarsInstaller.getUninstalledPlugins();
- assertThat(removals).hasSize(1);
- PluginMetadata metadata = removals.iterator().next();
- assertThat(metadata.getKey()).isEqualTo("foo");
- assertThat(metadata.getName()).isEqualTo("Foo");
- assertThat(metadata.getVersion()).isEqualTo("1.0");
- assertThat(metadata.getOrganization()).isEqualTo("SonarSource");
- assertThat(metadata.getOrganizationUrl()).isEqualTo("http://www.sonarsource.org");
- assertThat(metadata.getLicense()).isEqualTo("LGPL 3");
- assertThat(metadata.getMainClass()).isEqualTo("foo.Main");
- }
-
- @Test
- public void cancel_uninstallation() throws Exception {
- when(upgradeStatus.isFreshInstall()).thenReturn(false);
- FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), pluginsDir);
-
- jarsInstaller.install();
- jarsInstaller.uninstall("foo");
- jarsInstaller.cancelUninstalls();
-
- assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(1);
- assertThat(FileUtils.listFiles(trashDir, new String[] {"jar"}, false)).hasSize(0);
- assertThat(jarsInstaller.getUninstalledPluginFilenames()).isEmpty();
- }
-
- @Test
- public void deploy_core_plugins() throws Exception {
- when(upgradeStatus.isFreshInstall()).thenReturn(false);
- FileUtils.copyFileToDirectory(jar("foo-plugin-1.0.jar"), coreDir);
-
- jarsInstaller.install();
-
- // do not deploy in extensions/plugins
- assertThat(FileUtils.listFiles(pluginsDir, new String[] {"jar"}, false)).hasSize(0);
-
- // do not remove from lib/core-plugins
- assertThat(FileUtils.listFiles(coreDir, new String[] {"jar"}, false)).hasSize(1);
-
- PluginMetadata plugin = jarsInstaller.getMetadata("foo");
- assertThat(plugin).isNotNull();
- assertThat(plugin.isCore()).isTrue();
- }
-}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java
index 5f1898dae31..ea87cb3af88 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginRepositoryTest.java
@@ -19,47 +19,298 @@
*/
package org.sonar.server.plugins;
-import com.google.common.io.Resources;
+import com.google.common.collect.ImmutableSet;
+import org.apache.commons.io.FileUtils;
import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
import org.junit.Test;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.core.plugins.DefaultPluginMetadata;
+import org.junit.rules.TemporaryFolder;
+import org.mockito.Mockito;
+import org.sonar.api.platform.Server;
+import org.sonar.api.platform.ServerUpgradeStatus;
+import org.sonar.api.utils.MessageException;
+import org.sonar.core.platform.PluginLoader;
+import org.sonar.server.platform.DefaultServerFileSystem;
+import org.sonar.updatecenter.common.Version;
import java.io.File;
-import java.util.Arrays;
+import java.io.IOException;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class ServerPluginRepositoryTest {
- ServerPluginRepository repository;
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ Server server = mock(Server.class);
+ ServerUpgradeStatus upgradeStatus = mock(ServerUpgradeStatus.class);
+ DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class, Mockito.RETURNS_DEEP_STUBS);
+ PluginLoader pluginLoader = new PluginLoader(new ServerPluginUnzipper(fs));
+ ServerPluginRepository underTest = new ServerPluginRepository(server, upgradeStatus, fs, pluginLoader);
+
+ @Before
+ public void setUp() throws IOException {
+ when(fs.getBundledPluginsDir()).thenReturn(temp.newFolder());
+ when(fs.getCorePluginsDir()).thenReturn(temp.newFolder());
+ when(fs.getDeployedPluginsDir()).thenReturn(temp.newFolder());
+ when(fs.getDownloadedPluginsDir()).thenReturn(temp.newFolder());
+ when(fs.getHomeDir()).thenReturn(temp.newFolder());
+ when(fs.getInstalledPluginsDir()).thenReturn(temp.newFolder());
+ when(fs.getTempDir()).thenReturn(temp.newFolder());
+ when(server.getVersion()).thenReturn("5.2");
+ }
@After
- public void stop() {
- if (repository != null) {
- repository.stop();
+ public void tearDown() throws Exception {
+ underTest.stop();
+ }
+
+ /**
+ * The first server startup (fresh db) installs bundled plugins and instantiates bundled + core plugins.
+ */
+ @Test
+ public void first_startup_installs_bundled_plugins() throws Exception {
+ copyTestPluginTo("test-base-plugin", fs.getBundledPluginsDir());
+ copyTestPluginTo("test-core-plugin", fs.getCorePluginsDir());
+ when(upgradeStatus.isFreshInstall()).thenReturn(true);
+
+ underTest.start();
+
+ // both plugins are installed
+ assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("core", "testbase");
+ assertThat(underTest.getPluginInstance("core").getClass().getName()).isEqualTo("CorePlugin");
+ assertThat(underTest.getPluginInstance("testbase").getClass().getName()).isEqualTo("BasePlugin");
+ assertThat(underTest.hasPlugin("testbase")).isTrue();
+ }
+
+ @Test
+ public void bundled_plugins_are_not_installed_if_not_fresh_server() throws Exception {
+ copyTestPluginTo("test-base-plugin", fs.getBundledPluginsDir());
+ when(upgradeStatus.isFreshInstall()).thenReturn(false);
+
+ underTest.start();
+
+ assertThat(underTest.getPluginInfos()).isEmpty();
+ }
+
+ @Test
+ public void standard_startup_loads_core_and_installed_plugins() throws Exception {
+ copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir());
+ copyTestPluginTo("test-core-plugin", fs.getCorePluginsDir());
+
+ underTest.start();
+
+ // both plugins are installed
+ assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("core", "testbase");
+ assertThat(underTest.getPluginInstance("core").getClass().getName()).isEqualTo("CorePlugin");
+ assertThat(underTest.getPluginInstance("testbase").getClass().getName()).isEqualTo("BasePlugin");
+ }
+
+ /**
+ * That sounds impossible, there are still core plugins for now, but it's still valuable
+ * to test sensibility to null values.
+ */
+ @Test
+ public void no_plugins_at_all_on_startup() throws Exception {
+ underTest.start();
+
+ assertThat(underTest.getPluginInfos()).isEmpty();
+ assertThat(underTest.getPluginInfosByKeys()).isEmpty();
+ assertThat(underTest.getUninstalledPlugins()).isEmpty();
+ assertThat(underTest.hasPlugin("testbase")).isFalse();
+ }
+
+ @Test
+ public void fail_if_multiple_jars_for_same_installed_plugin_on_startup() throws Exception {
+ copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir());
+ copyTestPluginTo("test-base-plugin-v2", fs.getInstalledPluginsDir());
+
+ try {
+ underTest.start();
+ fail();
+ } catch (MessageException e) {
+ assertThat(e)
+ .hasMessageStartingWith("Found two files for the same plugin [testbase]: ")
+ // order is not guaranteed, so assertion is split
+ .hasMessageContaining("test-base-plugin-0.1-SNAPSHOT.jar")
+ .hasMessageContaining("test-base-plugin-0.2-SNAPSHOT.jar");
}
}
@Test
- public void testStart() throws Exception {
- ServerPluginJarsInstaller deployer = mock(ServerPluginJarsInstaller.class);
- File pluginFile = new File(Resources.getResource("org/sonar/server/plugins/ServerPluginRepositoryTest/sonar-artifact-size-plugin-0.2.jar").toURI());
- PluginMetadata plugin = DefaultPluginMetadata.create(pluginFile)
- .setKey("artifactsize")
- .setMainClass("org.sonar.plugins.artifactsize.ArtifactSizePlugin")
- .addDeployedFile(pluginFile);
- when(deployer.getMetadata()).thenReturn(Arrays.asList(plugin));
+ 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.getInstalledPluginsDir(), downloadedJar.getName())).isFile().exists();
+ assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
+ assertThat(underTest.getPluginInstance("testbase").getClass().getName()).isEqualTo("BasePlugin");
+ }
+
+ @Test
+ public void downloaded_file_overrides_existing_installed_file_on_startup() throws Exception {
+ File installedV1 = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir());
+ 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.getInstalledPluginsDir(), downloadedV2.getName())).exists();
+ assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
+ assertThat(underTest.getPluginInfo("testbase").getVersion()).isEqualTo(Version.create("0.2-SNAPSHOT"));
+ }
+
+ @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.getInstalledPluginsDir());
+
+ underTest.start();
- repository = new ServerPluginRepository(deployer);
- repository.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.getInstalledPluginsDir());
+ copyTestPluginTo("test-require-plugin", fs.getInstalledPluginsDir());
+
+ 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.getInstalledPluginsDir());
+
+ 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.getInstalledPluginsDir());
+ copyTestPluginTo("test-requirenew-plugin", fs.getInstalledPluginsDir());
+
+ underTest.start();
+
+ // the plugin "requirenew" is not installed as it requires base 0.2+ to be installed.
+ assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
+ }
+
+ @Test
+ public void plugin_is_ignored_at_startup_if_unsupported_sq() throws Exception {
+ when(server.getVersion()).thenReturn("1.0");
+ copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir());
+
+ underTest.start();
+
+ // plugin requires SQ 4.5.1 but SQ 1.0 is installed
+ assertThat(underTest.getPluginInfos()).isEmpty();
+ }
+
+ @Test
+ public void uninstall() throws Exception {
+ File installedJar = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir());
+
+ underTest.start();
+ assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
+ underTest.uninstall("testbase");
+
+ assertThat(installedJar).doesNotExist();
+ // still up. Will be dropped after next startup
+ assertThat(underTest.getPluginInfosByKeys()).containsOnlyKeys("testbase");
+ assertThat(underTest.getUninstalledPluginFilenames()).containsOnly(installedJar.getName());
+ assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase");
+ }
+
+ @Test
+ public void uninstall_dependents() throws Exception {
+ File base = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir());
+ File extension = copyTestPluginTo("test-require-plugin", fs.getInstalledPluginsDir());
+
+ underTest.start();
+ assertThat(underTest.getPluginInfos()).hasSize(2);
+ underTest.uninstall("testbase");
+
+ assertThat(base).doesNotExist();
+ assertThat(extension).doesNotExist();
+ assertThat(underTest.getUninstalledPluginFilenames()).containsOnly(base.getName(), extension.getName());
+ assertThat(underTest.getUninstalledPlugins()).extracting("key").containsOnly("testbase", "testrequire");
+ }
+
+ @Test
+ public void cancel_uninstall() throws Exception {
+ File base = copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir());
+ underTest.start();
+
+ underTest.uninstall("testbase");
+ assertThat(base).doesNotExist();
+
+ underTest.cancelUninstalls();
+ assertThat(base).exists();
+ assertThat(underTest.getUninstalledPluginFilenames()).isEmpty();
+ assertThat(underTest.getUninstalledPlugins()).isEmpty();
+ }
+
+ @Test
+ public void install_plugin_and_its_extension_plugins_at_startup() throws Exception {
+ copyTestPluginTo("test-base-plugin", fs.getInstalledPluginsDir());
+ copyTestPluginTo("test-extend-plugin", fs.getInstalledPluginsDir());
+
+ 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.getInstalledPluginsDir());
+
+ underTest.start();
+
+ // plugin is not installed as its base plugin is not installed
+ assertThat(underTest.getPluginInfos()).isEmpty();
+ }
+
+ @Test
+ public void fail_is_missing_required_plugin() throws Exception {
+ 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");
+ }
+ }
- assertThat(repository.getPlugin("artifactsize")).isNotNull();
- assertThat(repository.getClassLoader("artifactsize")).isNotNull();
- assertThat(repository.getClass("artifactsize", "org.sonar.plugins.artifactsize.ArtifactSizeMetrics")).isNotNull();
- assertThat(repository.getClass("artifactsize", "org.Unknown")).isNull();
- assertThat(repository.getClass("other", "org.sonar.plugins.artifactsize.ArtifactSizeMetrics")).isNull();
+ 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());
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginUnzipperTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginUnzipperTest.java
new file mode 100644
index 00000000000..efea25d39e8
--- /dev/null
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ServerPluginUnzipperTest.java
@@ -0,0 +1,64 @@
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube 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.
+ *
+ * SonarQube 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.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.UnzippedPlugin;
+import org.sonar.server.platform.DefaultServerFileSystem;
+
+import java.io.File;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ServerPluginUnzipperTest {
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
+ DefaultServerFileSystem fs = mock(DefaultServerFileSystem.class);
+ ServerPluginUnzipper underTest = new ServerPluginUnzipper(fs);
+
+ @Test
+ public void copy_all_classloader_files_to_dedicated_directory() throws Exception {
+ File deployDir = temp.newFolder();
+ when(fs.getDeployedPluginsDir()).thenReturn(deployDir);
+ File jar = TestProjectUtils.jarOf("test-libs-plugin");
+ PluginInfo info = PluginInfo.create(jar);
+
+ UnzippedPlugin unzipped = underTest.unzip(info);
+
+ // all the files loaded by classloaders (JAR + META-INF/libs/*.jar) are copied to the dedicated directory
+ // web/deploy/{pluginKey}
+ File pluginDeployDir = new File(deployDir, "testlibs");
+
+ assertThat(unzipped.getKey()).isEqualTo("testlibs");
+ assertThat(unzipped.getMain()).isFile().exists().hasParent(pluginDeployDir);
+ assertThat(unzipped.getLibs()).extracting("name").containsOnly("commons-daemon-1.0.15.jar", "commons-email-20030310.165926.jar");
+ for (File lib : unzipped.getLibs()) {
+ assertThat(lib).exists().isFile();
+ assertThat(lib.getCanonicalPath()).startsWith(pluginDeployDir.getCanonicalPath());
+ }
+ }
+}
diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/ResourcesClassloader.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/TestProjectUtils.java
similarity index 57%
rename from sonar-core/src/main/java/org/sonar/core/plugins/ResourcesClassloader.java
rename to server/sonar-server/src/test/java/org/sonar/server/plugins/TestProjectUtils.java
index 04aea1fb391..00d579118ee 100644
--- a/sonar-core/src/main/java/org/sonar/core/plugins/ResourcesClassloader.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/TestProjectUtils.java
@@ -17,33 +17,24 @@
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
-package org.sonar.core.plugins;
+package org.sonar.server.plugins;
-import com.google.common.collect.Lists;
-import org.apache.commons.lang.StringUtils;
+import org.apache.commons.io.FileUtils;
-import java.net.URL;
-import java.net.URLClassLoader;
+import java.io.File;
import java.util.Collection;
-/**
- * This class loader is used to load resources from a list of URLs - see SONAR-1861.
- */
-public class ResourcesClassloader extends URLClassLoader {
- private Collection urls;
-
- public ResourcesClassloader(Collection urls, ClassLoader parent) {
- super(new URL[] {}, parent);
- this.urls = Lists.newArrayList(urls);
- }
+public class TestProjectUtils {
- @Override
- public URL findResource(String name) {
- for (URL url : urls) {
- if (StringUtils.endsWith(url.getPath(), name)) {
- return url;
- }
+ /**
+ * Get the artifact of plugins stored in src/test/projects
+ */
+ public static File jarOf(String dirName) {
+ File target = FileUtils.toFile(TestProjectUtils.class.getResource(String.format("/%s/target/", dirName)));
+ Collection jars = FileUtils.listFiles(target, new String[] {"jar"}, false);
+ if (jars == null || jars.size() != 1) {
+ throw new IllegalArgumentException("Test project is badly defined: " + dirName);
}
- return null;
+ return jars.iterator().next();
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/CancelAllPluginsWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/CancelAllPluginsWsActionTest.java
index 4ff9f43f340..3d2260eba4f 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/CancelAllPluginsWsActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/CancelAllPluginsWsActionTest.java
@@ -28,7 +28,7 @@ import org.sonar.api.server.ws.WebService;
import org.sonar.core.permission.GlobalPermissions;
import org.sonar.server.exceptions.ForbiddenException;
import org.sonar.server.plugins.PluginDownloader;
-import org.sonar.server.plugins.ServerPluginJarsInstaller;
+import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.user.MockUserSession;
import org.sonar.server.ws.WsTester;
@@ -41,8 +41,8 @@ public class CancelAllPluginsWsActionTest {
private static final String DUMMY_CONTROLLER_KEY = "dummy";
private PluginDownloader pluginDownloader = mock(PluginDownloader.class);
- private ServerPluginJarsInstaller pluginJarsInstaller = mock(ServerPluginJarsInstaller.class);
- private CancelAllPluginsWsAction underTest = new CancelAllPluginsWsAction(pluginDownloader, pluginJarsInstaller);
+ private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
+ private CancelAllPluginsWsAction underTest = new CancelAllPluginsWsAction(pluginDownloader, pluginRepository);
private Request request = mock(Request.class);
private WsTester.TestResponse response = new WsTester.TestResponse();
@@ -90,7 +90,7 @@ public class CancelAllPluginsWsActionTest {
underTest.handle(request, response);
verify(pluginDownloader, times(1)).cancelDownloads();
- verify(pluginJarsInstaller, times(1)).cancelUninstalls();
+ verify(pluginRepository, times(1)).cancelUninstalls();
assertThat(response.outputAsString()).isEmpty();
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledPluginsWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledPluginsWsActionTest.java
index 3780d88af00..11a544e1d23 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledPluginsWsActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/InstalledPluginsWsActionTest.java
@@ -19,17 +19,17 @@
*/
package org.sonar.server.plugins.ws;
-import java.io.File;
import org.junit.Test;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.WebService;
-import org.sonar.core.plugins.DefaultPluginMetadata;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.ws.WsTester;
+import org.sonar.updatecenter.common.Version;
+
+import java.io.File;
import static com.google.common.collect.ImmutableList.of;
-import static java.lang.String.valueOf;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@@ -42,12 +42,12 @@ public class InstalledPluginsWsActionTest {
" \"plugins\":" + "[]" +
"}";
- private PluginRepository pluginRepository = mock(PluginRepository.class);
+ private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
private InstalledPluginsWsAction underTest = new InstalledPluginsWsAction(pluginRepository, new PluginWSCommons());
private Request request = mock(Request.class);
private WsTester.TestResponse response = new WsTester.TestResponse();
- private PluginMetadata corePlugin = corePlugin("core1", 10);
+ private PluginInfo corePlugin = corePlugin("core1", "1.0");
@Test
public void action_installed_is_defined() {
@@ -75,7 +75,7 @@ public class InstalledPluginsWsActionTest {
@Test
public void core_plugin_are_not_returned() throws Exception {
- when(pluginRepository.getMetadata()).thenReturn(of(corePlugin));
+ when(pluginRepository.getPluginInfos()).thenReturn(of(corePlugin));
underTest.handle(request, response);
@@ -84,9 +84,9 @@ public class InstalledPluginsWsActionTest {
@Test
public void empty_fields_are_not_serialized_to_json() throws Exception {
- when(pluginRepository.getMetadata()).thenReturn(
+ when(pluginRepository.getPluginInfos()).thenReturn(
of(
- (PluginMetadata) DefaultPluginMetadata.create("").setName("").setCore(false)
+ new PluginInfo("").setName("").setCore(false)
)
);
@@ -98,14 +98,14 @@ public class InstalledPluginsWsActionTest {
@Test
public void verify_properties_displayed_in_json_per_plugin() throws Exception {
String jarFilename = getClass().getSimpleName() + "/" + "some.jar";
- when(pluginRepository.getMetadata()).thenReturn(of(
- (PluginMetadata) DefaultPluginMetadata.create("plugKey").setName("plugName").setCore(false)
+ when(pluginRepository.getPluginInfos()).thenReturn(of(
+ new PluginInfo("plugKey").setName("plugName").setCore(false)
.setDescription("desc_it")
- .setVersion(valueOf(10))
+ .setVersion(Version.create("1.0"))
.setLicense("license_hey")
- .setOrganization("org_name")
+ .setOrganizationName("org_name")
.setOrganizationUrl("org_url")
- .setHomepage("homepage_url")
+ .setHomepageUrl("homepage_url")
.setIssueTrackerUrl("issueTracker_url")
.setFile(new File(getClass().getResource(jarFilename).toURI()))
.setImplementationBuild("sou_rev_sha1")
@@ -122,7 +122,7 @@ public class InstalledPluginsWsActionTest {
" \"key\": \"plugKey\"," +
" \"name\": \"plugName\"," +
" \"description\": \"desc_it\"," +
- " \"version\": \"10\"," +
+ " \"version\": \"1.0\"," +
" \"license\": \"license_hey\"," +
" \"organizationName\": \"org_name\"," +
" \"organizationUrl\": \"org_url\"," +
@@ -137,7 +137,7 @@ public class InstalledPluginsWsActionTest {
@Test
public void plugins_are_sorted_by_name_then_key_and_only_one_plugin_can_have_a_specific_name() throws Exception {
- when(pluginRepository.getMetadata()).thenReturn(
+ when(pluginRepository.getPluginInfos()).thenReturn(
of(
plugin("A", "name2"),
plugin("B", "name1"),
@@ -163,7 +163,7 @@ public class InstalledPluginsWsActionTest {
@Test
public void only_one_plugin_can_have_a_specific_name_and_key() throws Exception {
- when(pluginRepository.getMetadata()).thenReturn(
+ when(pluginRepository.getPluginInfos()).thenReturn(
of(
plugin("A", "name2"),
plugin("A", "name2")
@@ -183,15 +183,15 @@ public class InstalledPluginsWsActionTest {
assertThat(response.outputAsString()).containsOnlyOnce("name2");
}
- private static PluginMetadata corePlugin(String key, int version) {
- return DefaultPluginMetadata.create(key).setName(key).setCore(true).setVersion(valueOf(version));
+ private static PluginInfo corePlugin(String key, String version) {
+ return new PluginInfo(key).setName(key).setCore(true).setVersion(Version.create(version));
}
- private static PluginMetadata plugin(String key, String name, int version) {
- return DefaultPluginMetadata.create(key).setName(name).setCore(false).setVersion(valueOf(version));
+ private static PluginInfo plugin(String key, String name, String version) {
+ return new PluginInfo(key).setName(name).setCore(false).setVersion(Version.create(version));
}
- private static PluginMetadata plugin(String key, String name) {
- return DefaultPluginMetadata.create(key).setName(name).setCore(false).setVersion("1.0");
+ private static PluginInfo plugin(String key, String name) {
+ return new PluginInfo(key).setName(name).setCore(false).setVersion(Version.create("1.0"));
}
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingPluginsWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingPluginsWsActionTest.java
index 558ed57839a..1849151312a 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingPluginsWsActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PendingPluginsWsActionTest.java
@@ -23,39 +23,39 @@ import java.io.File;
import org.junit.Test;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.WebService;
-import org.sonar.core.plugins.DefaultPluginMetadata;
+import org.sonar.core.platform.PluginInfo;
import org.sonar.server.plugins.PluginDownloader;
-import org.sonar.server.plugins.ServerPluginJarsInstaller;
+import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.ws.WsTester;
+import org.sonar.updatecenter.common.Version;
import static com.google.common.collect.ImmutableList.of;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
-import static org.sonar.core.plugins.DefaultPluginMetadata.create;
import static org.sonar.test.JsonAssert.assertJson;
public class PendingPluginsWsActionTest {
- public static final DefaultPluginMetadata GIT_PLUGIN_METADATA = create("scmgit")
+ public static final PluginInfo GIT_PLUGIN_INFO = new PluginInfo("scmgit")
.setName("Git")
.setDescription("Git SCM Provider.")
- .setVersion("1.0")
+ .setVersion(Version.create("1.0"))
.setLicense("GNU LGPL 3")
- .setOrganization("SonarSource")
+ .setOrganizationName("SonarSource")
.setOrganizationUrl("http://www.sonarsource.com")
- .setHomepage("http://redirect.sonarsource.com/plugins/scmgit.html")
+ .setHomepageUrl("http://redirect.sonarsource.com/plugins/scmgit.html")
.setIssueTrackerUrl("http://jira.codehaus.org/browse/SONARSCGIT")
.setFile(new File("/home/user/sonar-scm-git-plugin-1.0.jar"))
.setImplementationBuild("9ce9d330c313c296fab051317cc5ad4b26319e07");
private static final String DUMMY_CONTROLLER_KEY = "dummy";
- public static final DefaultPluginMetadata PLUGIN_2_2 = create("key2").setName("name2");
- public static final DefaultPluginMetadata PLUGIN_2_1 = create("key1").setName("name2");
- public static final DefaultPluginMetadata PLUGIN_0_0 = create("key0").setName("name0");
+ public static final PluginInfo PLUGIN_2_2 = new PluginInfo("key2").setName("name2");
+ public static final PluginInfo PLUGIN_2_1 = new PluginInfo("key1").setName("name2");
+ public static final PluginInfo PLUGIN_0_0 = new PluginInfo("key0").setName("name0");
private PluginDownloader pluginDownloader = mock(PluginDownloader.class);
- private ServerPluginJarsInstaller serverPluginJarsInstaller = mock(ServerPluginJarsInstaller.class);
- private PendingPluginsWsAction underTest = new PendingPluginsWsAction(pluginDownloader, serverPluginJarsInstaller, new PluginWSCommons());
+ private ServerPluginRepository serverPluginRepository = mock(ServerPluginRepository.class);
+ private PendingPluginsWsAction underTest = new PendingPluginsWsAction(pluginDownloader, serverPluginRepository, new PluginWSCommons());
private Request request = mock(Request.class);
private WsTester.TestResponse response = new WsTester.TestResponse();
@@ -91,7 +91,7 @@ public class PendingPluginsWsActionTest {
@Test
public void verify_properties_displayed_in_json_per_installing_plugin() throws Exception {
- when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(GIT_PLUGIN_METADATA));
+ when(pluginDownloader.getDownloadedPlugins()).thenReturn(of(GIT_PLUGIN_INFO));
underTest.handle(request, response);
@@ -119,7 +119,7 @@ public class PendingPluginsWsActionTest {
@Test
public void verify_properties_displayed_in_json_per_removing_plugin() throws Exception {
- when(serverPluginJarsInstaller.getUninstalledPlugins()).thenReturn(of(GIT_PLUGIN_METADATA));
+ when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(GIT_PLUGIN_INFO));
underTest.handle(request, response);
@@ -180,7 +180,7 @@ public class PendingPluginsWsActionTest {
@Test
public void removing_plugin_are_sorted_and_unique() throws Exception {
- when(serverPluginJarsInstaller.getUninstalledPlugins()).thenReturn(of(
+ when(serverPluginRepository.getUninstalledPlugins()).thenReturn(of(
PLUGIN_2_2,
PLUGIN_2_1,
PLUGIN_2_2,
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java
index 831bdbd7b78..e055635b343 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/PluginWSCommonsTest.java
@@ -22,7 +22,7 @@ package org.sonar.server.plugins.ws;
import java.io.File;
import org.junit.Test;
import org.sonar.api.utils.text.JsonWriter;
-import org.sonar.core.plugins.DefaultPluginMetadata;
+import org.sonar.core.platform.PluginInfo;
import org.sonar.server.ws.WsTester;
import org.sonar.updatecenter.common.Plugin;
import org.sonar.updatecenter.common.PluginUpdate;
@@ -31,7 +31,6 @@ 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.core.plugins.DefaultPluginMetadata.create;
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;
@@ -40,14 +39,14 @@ import static org.sonar.updatecenter.common.PluginUpdate.Status.INCOMPATIBLE;
import static org.sonar.updatecenter.common.PluginUpdate.Status.REQUIRE_SONAR_UPGRADE;
public class PluginWSCommonsTest {
- private static final DefaultPluginMetadata GIT_PLUGIN_METADATA = create("scmgit")
+ private static final PluginInfo GIT_PLUGIN_METADATA = new PluginInfo("scmgit")
.setName("Git")
.setDescription("Git SCM Provider.")
- .setVersion("1.0")
+ .setVersion(Version.create("1.0"))
.setLicense("GNU LGPL 3")
- .setOrganization("SonarSource")
+ .setOrganizationName("SonarSource")
.setOrganizationUrl("http://www.sonarsource.com")
- .setHomepage("http://redirect.sonarsource.com/plugins/scmgit.html")
+ .setHomepageUrl("http://redirect.sonarsource.com/plugins/scmgit.html")
.setIssueTrackerUrl("http://jira.codehaus.org/browse/SONARSCGIT")
.setFile(new File("/home/user/sonar-scm-git-plugin-1.0.jar"));
private static final Plugin PLUGIN = new Plugin("p_key")
@@ -202,7 +201,7 @@ public class PluginWSCommonsTest {
PluginUpdate pluginUpdate = new PluginUpdate();
pluginUpdate.setRelease(
new Release(PLUGIN, version("1.0")).addOutgoingDependency(RELEASE)
- );
+ );
jsonWriter.beginObject();
underTest.writeUpdate(jsonWriter, pluginUpdate);
diff --git a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UninstallPluginsWsActionTest.java b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UninstallPluginsWsActionTest.java
index fa8adaa0f98..85a3bba714f 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UninstallPluginsWsActionTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/plugins/ws/UninstallPluginsWsActionTest.java
@@ -19,19 +19,15 @@
*/
package org.sonar.server.plugins.ws;
-import com.google.common.collect.ImmutableList;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
import org.sonar.api.server.ws.Request;
import org.sonar.api.server.ws.WebService;
import org.sonar.core.permission.GlobalPermissions;
-import org.sonar.core.plugins.DefaultPluginMetadata;
import org.sonar.server.exceptions.ForbiddenException;
-import org.sonar.server.plugins.ServerPluginJarsInstaller;
+import org.sonar.server.plugins.ServerPluginRepository;
import org.sonar.server.user.MockUserSession;
import org.sonar.server.ws.WsTester;
@@ -45,11 +41,10 @@ public class UninstallPluginsWsActionTest {
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 = "pluginKey";
+ private static final String PLUGIN_KEY = "findbugs";
- private PluginRepository pluginRepository = mock(PluginRepository.class);
- private ServerPluginJarsInstaller pluginJarsInstaller = mock(ServerPluginJarsInstaller.class);
- private UninstallPluginsWsAction underTest = new UninstallPluginsWsAction(pluginRepository, pluginJarsInstaller);
+ private ServerPluginRepository pluginRepository = mock(ServerPluginRepository.class);
+ private UninstallPluginsWsAction underTest = new UninstallPluginsWsAction(pluginRepository);
private WsTester wsTester = new WsTester(new PluginsWs(underTest));
private Request invalidRequest = wsTester.newGetRequest(CONTROLLER_KEY, ACTION_KEY);
@@ -109,20 +104,18 @@ public class UninstallPluginsWsActionTest {
@Test
public void IAE_is_raised_when_plugin_is_not_installed() throws Exception {
expectedException.expect(IllegalArgumentException.class);
- expectedException.expectMessage("No plugin with key 'pluginKey'");
+ expectedException.expectMessage("Plugin [findbugs] is not installed");
underTest.handle(validRequest, response);
}
@Test
public void if_plugin_is_installed_uninstallation_is_triggered() throws Exception {
- when(pluginRepository.getMetadata()).thenReturn(ImmutableList.of(
- DefaultPluginMetadata.create(PLUGIN_KEY)
- ));
+ when(pluginRepository.hasPlugin(PLUGIN_KEY)).thenReturn(true);
underTest.handle(validRequest, response);
- verify(pluginJarsInstaller).uninstall(PLUGIN_KEY);
+ verify(pluginRepository).uninstall(PLUGIN_KEY);
assertThat(response.outputAsString()).isEmpty();
}
diff --git a/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
index cc96395bc62..8af9cb1eb7c 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/startup/GeneratePluginIndexTest.java
@@ -25,9 +25,8 @@ import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
-import org.sonar.api.platform.PluginMetadata;
-import org.sonar.api.platform.PluginRepository;
-import org.sonar.core.plugins.DefaultPluginMetadata;
+import org.sonar.core.platform.PluginInfo;
+import org.sonar.core.platform.PluginRepository;
import org.sonar.server.platform.DefaultServerFileSystem;
import java.io.File;
@@ -45,8 +44,8 @@ public class GeneratePluginIndexTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
- private DefaultServerFileSystem fileSystem;
- private File index;
+ DefaultServerFileSystem fileSystem;
+ File index;
@Before
public void createIndexFile() {
@@ -58,9 +57,9 @@ public class GeneratePluginIndexTest {
@Test
public void shouldWriteIndex() throws IOException {
PluginRepository repository = mock(PluginRepository.class);
- PluginMetadata sqale = newMetadata("sqale");
- PluginMetadata checkstyle = newMetadata("checkstyle");
- when(repository.getMetadata()).thenReturn(Arrays.asList(sqale, checkstyle));
+ PluginInfo sqale = newInfo("sqale");
+ PluginInfo checkstyle = newInfo("checkstyle");
+ when(repository.getPluginInfos()).thenReturn(Arrays.asList(sqale, checkstyle));
new GeneratePluginIndex(fileSystem, repository).start();
@@ -70,11 +69,7 @@ public class GeneratePluginIndexTest {
assertThat(lines.get(1), containsString("checkstyle"));
}
- private PluginMetadata newMetadata(String pluginKey) throws IOException {
- PluginMetadata plugin = mock(DefaultPluginMetadata.class);
- when(plugin.getKey()).thenReturn(pluginKey);
- File pluginFile = temp.newFile(pluginKey + ".jar");
- when(plugin.getFile()).thenReturn(pluginFile);
- return plugin;
+ private PluginInfo newInfo(String pluginKey) throws IOException {
+ return new PluginInfo(pluginKey).setFile(temp.newFile(pluginKey + ".jar"));
}
}
diff --git a/server/sonar-server/src/test/projects/.gitignore b/server/sonar-server/src/test/projects/.gitignore
new file mode 100644
index 00000000000..a945b8525e6
--- /dev/null
+++ b/server/sonar-server/src/test/projects/.gitignore
@@ -0,0 +1,7 @@
+# see README.txt
+!*/target/
+*/target/classes/
+*/target/maven-archiver/
+*/target/maven-status/
+*/target/test-*/
+
diff --git a/server/sonar-server/src/test/projects/README.txt b/server/sonar-server/src/test/projects/README.txt
new file mode 100644
index 00000000000..c53a66d52f2
--- /dev/null
+++ b/server/sonar-server/src/test/projects/README.txt
@@ -0,0 +1,3 @@
+This directory provides the fake plugins used by tests. These tests are rarely changed, so generated
+artifacts are stored in Git repository (see .gitignore). It avoids from adding unnecessary modules
+to build.
diff --git a/server/sonar-server/src/test/projects/pom.xml b/server/sonar-server/src/test/projects/pom.xml
new file mode 100644
index 00000000000..f244768228e
--- /dev/null
+++ b/server/sonar-server/src/test/projects/pom.xml
@@ -0,0 +1,19 @@
+
+
+ 4.0.0
+ org.codehaus.sonar.tests
+ parent
+ 0.1-SNAPSHOT
+ pom
+
+ test-base-plugin
+ test-base-plugin-v2
+ test-core-plugin
+ test-extend-plugin
+ test-libs-plugin
+ test-require-plugin
+ test-requirenew-plugin
+
+
+
diff --git a/server/sonar-server/src/test/projects/test-base-plugin-v2/pom.xml b/server/sonar-server/src/test/projects/test-base-plugin-v2/pom.xml
new file mode 100644
index 00000000000..21f7ed5558d
--- /dev/null
+++ b/server/sonar-server/src/test/projects/test-base-plugin-v2/pom.xml
@@ -0,0 +1,36 @@
+
+
+ 4.0.0
+ org.codehaus.sonar.tests
+ test-base-plugin
+ 0.2-SNAPSHOT
+ sonar-plugin
+ Base Plugin
+ Simple standalone plugin. Used by other fake plugins.
+
+
+
+ org.codehaus.sonar
+ sonar-plugin-api
+ 4.5.4
+ provided
+
+
+
+ src
+
+
+ org.codehaus.sonar
+ sonar-packaging-maven-plugin
+ 1.13
+ true
+
+ testbase
+ BasePlugin
+
+
+
+
+
+
diff --git a/server/sonar-server/src/test/projects/test-base-plugin-v2/src/BasePlugin.java b/server/sonar-server/src/test/projects/test-base-plugin-v2/src/BasePlugin.java
new file mode 100644
index 00000000000..57b4a5dfb98
--- /dev/null
+++ b/server/sonar-server/src/test/projects/test-base-plugin-v2/src/BasePlugin.java
@@ -0,0 +1,11 @@
+import org.sonar.api.SonarPlugin;
+
+import java.util.Collections;
+import java.util.List;
+
+public class BasePlugin extends SonarPlugin {
+
+ public List getExtensions() {
+ return Collections.emptyList();
+ }
+}
diff --git a/server/sonar-server/src/test/projects/test-base-plugin-v2/target/test-base-plugin-0.2-SNAPSHOT.jar b/server/sonar-server/src/test/projects/test-base-plugin-v2/target/test-base-plugin-0.2-SNAPSHOT.jar
new file mode 100644
index 0000000000000000000000000000000000000000..9bd9e0e0fc16800409d159b122de1ae62a804034
GIT binary patch
literal 2437
zcmWIWW@h1H0D;YWm%4!&P=XUk`?`iW>U#RQ=?6fSaWHHGDvJpAEw*N4U@&E3U=Tr8
z=IiL^>E;?7qUY-tJMna0vw?`qcaJ}Z?}kUTw5&>+eq5>ciK64CwK6rrYb2z9I$inl
zI@`(FQ+0jX8so$E=NLu(lH?(+?RfNLRu&}RZJyIhzSyt^7i;?UN
zxSMBqLy*fN=HM09D2q>@*FUXZ(D`-C6%#Y}Tai;8^v_AHJkoT1hJ#4oBZVA)vEIp^
zb9&~_KcrDoy39=f3G3VV(`yf{Z+npUBkuId=9{LBRuR`X7#z1t&h%RIgOh!uypg-F
z^HTSA9j;4XOJ?htN50Q{b5i%~+1;8o`lhq^U-?9wQER@bx5DS?8OHxpR~$U^=}*tM
z!X}JhLC(J(?^QyS*U4ljG
zhGVb%3|czR94*~Cqw~?U^G35=I%j>(ImMm%D=p(?b)?acldSqjeZ!U-*{5;@E!3Ca
zdcu2U(W1o%4JsdK%Y{d!6)JiAD&;R)m87?BiKTkZU**MLE;hei@IG0dF*ADB!vN9D
zm{oJc4dYK$B$e66?C+Iats`8#cVDBnnD^h5Nrf$Xe;XrZ4TPOv1c(QxhEyM4pP+BL
z*D>R|v$WpA{bC0WtA$pXH663bI&)3h>iqvb@8s8ieSLRT=tFZ@0AeO;e&p1en^=~b
z2T$6ViZw75=NF~xCFiH4W+avt>lNqcB^K$Gq!yPH<1xjO1XDm}=_UaqShoNi!*F9c
z7?^i0b$h?2Gw&BL8C+*!U~r(s*n<3Ay^7qNsUauxZW{>f{jJS$*Y%bsmk8V01>9X?
zzEaG7O>vo7I-4hYG%nfmr`9uT
zHl@G+UcG03-=5VkOWf-2-(UOZ@TBEIk;kVtq%`e1Z$IbujJ=nec3a%t9eZ)V1p92$
z1gXj!$2mWr%~)xyF>m(Q$TNXPhnO_Ycde^SITL;;PU^;|TS==$&2Kc8PMg#xc;k-6
z&WDG78l+q{-991Ab*{JTNd?IrYd_?4t-o}2z5nT}-`m~2%9`)hwTr)-|4hg14fos+
z2ZHvne7kAxblOaQ;)GKNxRh5fP>4RcNsEDXqj6jY>k7^Gi<4V<*7kes)|J@uP(Sum
zl2@Cmn(tYzkY}|@2RBu07A{G7D|mlGu1drcz7olIGe3FF>lXXD`(KLuhqE3VXKZr{
zKd>jW+^D%{l3Jdc>f=E6!ri>F*EeNE2d>Umez4l)-rrj?GffXow`P{k(vGlq5#D_7
z@_*$MuAaLlFYleIU;WBwf#kf{{g;kEfB3+G^^8$^)`Ap#%}=!^6GH1vZ7PSsBZ+Nn`1j*e~o~m?o!W
zwJth%(IM}C$@d=>XIYigG%P+QJ}r+h*mNxPPSMj-1$WD;FK=b9vx+{x^_Jwegk=+w
z)-MtJR#5&X55t<(4#;g!0^@)L?cyoFm2c?JCI@!SkkBtB;neSsy>9p5EIZ#aIki;2~&WX
z8NED5=*BFOK*bjX2r&Hre-&8$!tI9@W-!NNY6cZ&2+d5)7@F}EZn#Ya6>kVrJxDMW
zOJN7I277S_vuH`
+
+ 4.0.0
+ org.codehaus.sonar.tests
+ test-base-plugin
+ 0.1-SNAPSHOT
+ sonar-plugin
+ Base Plugin
+ Simple standalone plugin. Used by other fake plugins.
+
+
+
+ org.codehaus.sonar
+ sonar-plugin-api
+ 4.5.4
+ provided
+
+
+
+ src
+
+
+ org.codehaus.sonar
+ sonar-packaging-maven-plugin
+ 1.13
+ true
+
+ testbase
+ BasePlugin
+
+
+
+
+
+
diff --git a/server/sonar-server/src/test/projects/test-base-plugin/src/BasePlugin.java b/server/sonar-server/src/test/projects/test-base-plugin/src/BasePlugin.java
new file mode 100644
index 00000000000..57b4a5dfb98
--- /dev/null
+++ b/server/sonar-server/src/test/projects/test-base-plugin/src/BasePlugin.java
@@ -0,0 +1,11 @@
+import org.sonar.api.SonarPlugin;
+
+import java.util.Collections;
+import java.util.List;
+
+public class BasePlugin extends SonarPlugin {
+
+ public List getExtensions() {
+ return Collections.emptyList();
+ }
+}
diff --git a/server/sonar-server/src/test/projects/test-base-plugin/target/test-base-plugin-0.1-SNAPSHOT.jar b/server/sonar-server/src/test/projects/test-base-plugin/target/test-base-plugin-0.1-SNAPSHOT.jar
new file mode 100644
index 0000000000000000000000000000000000000000..2a6148ce7b6ff0fbc0476907036dac5109496b06
GIT binary patch
literal 2437
zcmWIWW@h1H0D;YWm%4!&P=XUk`?`iW>U#RQ=?6fSaWHHGDiglBliiw;fx(oCfk6aW
znXjXtr<-eVh@P)o?8MV~%?2Va-#z{uz8fCV(y}UP`f;V&CyI`n*2>ffuaS`c>2&4G
z>ue`yPu2BlYm6U%?8>fmS=DSCHOWogAZg+I+|c4lRuKwM!@|Cv^+=7>WLdRSEJm_7
z;BKDb4M8r8n1feXqbxpsUjMXuLFd;kS4_;@Z$(aZ&_5@&@<`M584e_+zU@KYkGRt-n{S#jT18ynU~t?nIn!&+4^H-t@<#5y
z&P(0fb3~TBerXn)x9Q&98-Bwm#{-9;ARrs8kH)k^EKB_M6&WkUUPLKMy$06b)
zcb3HFj-z)=lq!UufBPIC@k8>cZQfb$HyH)Tf8OIq4eZ6*cP9!11A7(_3jhP#DX};;
zAg44vGfyu$C$YFVHejRQVFMAH?U_zaP6`16%F(AUbVwd?U=41W(6wv#Q7*U47*#*k
zdRF0%M*atj1KnGy$Uc=LXraFR
z))U?6BB{(iW`D2bY8~O?z55!q#k~KfOe$>A``Z{PYas0WB0xMiHKh9Z`UHK`
zy^a~zou%~_?iV|7SS_^5tm&9d)|qS4R_Fikc_+XA>+8F#LLZvL0uVD%^CPF$+{Cif
zJb2Q^RIGuiIKL=eFF8LYH6yXKSg$xgFR@6kB(=Dt7>_BIB$xs+OE(D^!MX+D7=|0m
z!N9z0soR6gcUyh|lfiWs1_lR8j4jB|)vL(OnHq9B@3w)!p5NLWcU@2Ua*42=UBEA*
zJIjP^j>z6iSEACCCo*}S{C0o(61i!z+g(d<-+XA7#=fY$eU*!@g!a}bwcjxU3<4r*}1FSPFnYx4()lC#2zubX^oux3Egu}Q%}Um
ztdY6-`>)*le5Lok)6Ske-~M}ReZ(zpr-y%0KX&k$
z>!f1iHroK$w{_WKWq{$6f6+LpXf
zST3R~{D9pLw~VXLm4$*VBN9_&MN+aOuc|IkC_Cr9mZ9{iLDjYii_Z#XZf9=Z_9xXN
z(&yxsBWqSiHlNz-uM#1aU)I#O#{N-!inXv#k@_y5?>?2f`mWu7%4}{%*HkZy@QGfS
zop5i<+B1sFmvry;Jo$6Q=
z#moPKPgbcMK6&}vsq40-iWg+g=f}N#{JEfD0aMycW8MHGMn9c}SHr}5AHI9@b;liZ
z3%AMa{nh?`H?p4{pA;>vYh_W=HQ#E|YF+kk1&2DXO!@MBR^^W0-WzY`!*U3uJonDw
znNSZ*Ppv@gPHDO-D9SHLEh@=OEfzg>nycABz~N%;zN7=CPFI$3A9U4R`gRe&M_s}d
zL*6Map9KE=zIFF$!D{Wwt{It4iOuZmozmP;V*%5CUun-cDxvl(!BdH~KQP71(y-^|S&L_Xe=^G(2*&R_YIVLY
zz?+dtgc*0`2J|Qp2r#^L1kp%U9ZVbc$_}I$1eP?a14+0xq^b{LF~kJ)5*(}@Y{C>^
zW=1d15xOx;BvA1M0RjyF|6c`Gzi|6ug&EB8n3_Sw8A3A?Glph7g&S^DLB$)wR1XqN
z#ZuV8tifK~!7N(RIG+-0poJeIoUvyDgl*qhi4Q6aPl0ShWQhQ8RyL5q96&e)7=5?F
FJOGOmlmGw#
literal 0
HcmV?d00001
diff --git a/server/sonar-server/src/test/projects/test-core-plugin/pom.xml b/server/sonar-server/src/test/projects/test-core-plugin/pom.xml
new file mode 100644
index 00000000000..fc3f082f6ec
--- /dev/null
+++ b/server/sonar-server/src/test/projects/test-core-plugin/pom.xml
@@ -0,0 +1,36 @@
+
+
+ 4.0.0
+ org.codehaus.sonar.tests
+ test-core-plugin
+ 0.1-SNAPSHOT
+ sonar-plugin
+ Test Core Plugin
+ Fake core plugin used by tests
+
+
+
+ org.codehaus.sonar
+ sonar-plugin-api
+ 4.5.4
+ provided
+
+
+
+ src
+
+
+ org.codehaus.sonar
+ sonar-packaging-maven-plugin
+ 1.13
+ true
+
+ core
+ CorePlugin
+
+
+
+
+
+
diff --git a/server/sonar-server/src/test/projects/test-core-plugin/src/CorePlugin.java b/server/sonar-server/src/test/projects/test-core-plugin/src/CorePlugin.java
new file mode 100644
index 00000000000..910204d87ea
--- /dev/null
+++ b/server/sonar-server/src/test/projects/test-core-plugin/src/CorePlugin.java
@@ -0,0 +1,11 @@
+import org.sonar.api.SonarPlugin;
+
+import java.util.Collections;
+import java.util.List;
+
+public class CorePlugin extends SonarPlugin {
+
+ public List getExtensions() {
+ return Collections.emptyList();
+ }
+}
diff --git a/server/sonar-server/src/test/projects/test-core-plugin/target/test-core-plugin-0.1-SNAPSHOT.jar b/server/sonar-server/src/test/projects/test-core-plugin/target/test-core-plugin-0.1-SNAPSHOT.jar
new file mode 100644
index 0000000000000000000000000000000000000000..62eba2aa80fbabc8e20007e90d94a07ec6fd9c50
GIT binary patch
literal 2412
zcmWIWW@h1H0D;YWm%4!&P=XUk`?`iW>U#RQ=?6fSaWHHGDw}-pm#Q8k1A`0`1A_>%
zGG9kOPdC@#5ItYF*oofRhYbW;-n0F0-z(02wA1_J*+Pw%%KRH{X9q6vReI#%w#oN$
zeEdztRlyfB+vZ-Z+nwa~?xNX6bt&zwUWrZ_5;?2CDTHouINiHu_Q8rYb(VF!PPRKP
zM2T$J?eO^7+WDF#*%r6>gO@%yTB)tQF@blPz-3RNi~pv6G?}aV^2^7@V^^l8waKTp
zKYOo#n`?hGx4AOUuQ_V7;)6C=AD&|IYVZ4qSH{=gG%UVVelqap#z%`aXSy7Zg%`d$|J&t7
z_?#a*Os%aQmhOHiz`6a5!4Fo{@D(@7`7Q(u-&sH`01RK}{G!x=oYM5nJiX+c#Ny)E
zfQ^2K4Mc3VXF54~DFg^8N1wjXA$dgTporplR~>*?>U!Jc*@{R%f5;$i&iYr>fk#*`N=%{
z4=b)2^$SI23RN11PG7lW%Ck8OKKu7xI?k}Id18MpkLT@+JngH_Eb4rIL#3qQ2ClGNgoVmzi;l3)tREZt;a{OT5fBN%Qh2Lto2rEZV)!+qWZ
zQ^7$N1_lR8j4jB|)vL(OnHq9B@3w)!p5NLWcU^D!a*42=UEm?OCF7FBhUhP+0;Fg5
zx^SsHJ$`>WzvnbrbH~!#HzVJ@d*kpWEj~Ph`p#w=bhoK_W0{2O!s>HJO7
zW(#{S);m}{xp_)&Q*ZIvITL2(J0{*M*vPoX*K=Aw{}UiiM^4_HnrDZ88l)62LtCko
zCI2UVArkphKfBHI7WsMmp@aO#ubp;=xvhB(mEC!%jI64fev>C%4s#4r!n9g+e!<$!A?dW!wLcp
z5BDyYI1y#&`BrIytLD
+
+ 4.0.0
+ org.codehaus.sonar.tests
+ test-extend-plugin
+ 0.1-SNAPSHOT
+ sonar-plugin
+ Test Extend Plugin
+ Fake plugin that extends the plugin with key "base"
+
+
+
+ org.codehaus.sonar
+ sonar-plugin-api
+ 4.5.4
+ provided
+
+
+
+ src
+
+
+ org.codehaus.sonar
+ sonar-packaging-maven-plugin
+ 1.13
+ true
+
+ testextend
+ ExtendPlugin
+ testbase
+
+
+
+
+
+
diff --git a/server/sonar-server/src/test/projects/test-extend-plugin/src/ExtendPlugin.java b/server/sonar-server/src/test/projects/test-extend-plugin/src/ExtendPlugin.java
new file mode 100644
index 00000000000..826e1842bbb
--- /dev/null
+++ b/server/sonar-server/src/test/projects/test-extend-plugin/src/ExtendPlugin.java
@@ -0,0 +1,11 @@
+import org.sonar.api.SonarPlugin;
+
+import java.util.Collections;
+import java.util.List;
+
+public class ExtendPlugin extends SonarPlugin {
+
+ public List getExtensions() {
+ return Collections.emptyList();
+ }
+}
diff --git a/server/sonar-server/src/test/projects/test-extend-plugin/target/test-extend-plugin-0.1-SNAPSHOT.jar b/server/sonar-server/src/test/projects/test-extend-plugin/target/test-extend-plugin-0.1-SNAPSHOT.jar
new file mode 100644
index 0000000000000000000000000000000000000000..7a81fdf0cce220427d4d14840577b4ec0dc1578b
GIT binary patch
literal 2481
zcmWIWW@h1H0D;YWm%4!&P=XUk`?`iW>U#RQ=?6fSaWHHGDhvN~#Ket}fgzNMfk6aW
znXjXtr<-eVh@P)o?8MV~%?2Va-#z{`Pt9d5@>ro&R=LGXzS84D-fHO`oMK1Zws}sj
ztL0VUTY03-_|loLXM`+nFT8Y5-0LVy|A`CB*KO8(ZTtA1OsH1m(&&e*Tq>d30&|`{a0n~mbX4+cG1
z<-f-CYkSw(%I&4EtSd-Dg^SWx$Ct%7&^O5ZCF}YJ2?a?XRJ&si$2{jp4fjHi5A7nr
za9;q#Lcnl$ttd&&O9{v+P0!5JOU_9wE{+Y@=y%vapf>MvOUss}jZG3aquQd*C`{v8
z#-S)$S{|$|v9oZh#fm?oJ_>Ah4gF3xG8;Jf-@m)Jx4it*^XtFa8;oUG3pR)+G@LlR
zubr9cyHko&ywDc+$Ci!8q27KM`dyFZpSs%gygWydSNNz_V$r#x#MiS*zU40_UVw6=PY-pyzbI!In|>e)%%$J+lh>otxGm)Y!PHN4t>VCU##rX0?Wjtt%_C!
zwmi->o(mkY})pd^LLa&
z*+j?lAy-_IlAeDNeX=y`y+n!jrPh3dJ6r+@*IkMwc5ST;nmyNi%e?=Uf7`DA{Q6BR
z^uIJL3^9{9KXTg5O)N{zgC}%M#TuB3^NZ5;lJiqiGZIUS^@{WJ5{vXoQj1H9@t9&w
zf+--gbW_1`tXlw%WVpc`49vThy2Z-HncIZvUAcPDt0b)}3~4tFV(<4L8^4Y2B&`
zxl%^eRVv#nWOlhcOUZwF;NIJ^igMw~{9}*1{?ydW_9HX8n3
zz*=6oyWY>WKIu*F+yh3RHuQ5pYrojLF7dCj?fj!Ms_795rf%hLr_cK7cf*_6`(9+u
z)<}BtscLlz>epp&^Nx2*ln-ZP3)SaBYVUB}aswauC&vfTIN&sC8%HN3U&p8T@tin=SwD8KZhe4`nA
zTJMLqdIwy5wQnbOiflf3>%a1eP|shxr=Prdy|+{`pmp`^@LxY__EaCxDPG3xW%S^P
zP}JTN%Z%^L4dw6u&DH+m>l3B?vy8Q8|8978ZBxpndy6a8)^i;5o^!*lWlG9}Oy{D~
zqx*{MKfN*8=Wq4?#Nn6cdwc7cVR;5p-tXm0f&pV`;;z}I>{e%UC1pMC~l|lv+;&cvcn{!nIgZxU%UIXV70M}gZG0U*-y=P
zJETn2-0<{N_R-tx?yuZ;`}1Q@>3i3DleiQ3H2JpXwCt0g_v@3vnPjccS9v-2%1mzF
zt{eGGMAxgyXJ+IQgPRk#J?CfI$8xdrXK+o&8}>*4IBf(Uxg&@o#?8P9=swIu
+
+ 4.0.0
+ org.codehaus.sonar.tests
+ test-libs-plugin
+ 0.1-SNAPSHOT
+ sonar-plugin
+ Test Libs Plugin
+ Fake plugin that embeds some libraries
+
+
+
+
+ commons-email
+ commons-email
+ 20030310.165926
+
+
+ commons-daemon
+ commons-daemon
+ 1.0.15
+
+
+
+ org.codehaus.sonar
+ sonar-plugin-api
+ 4.5.4
+ provided
+
+
+
+
+ src
+
+
+ org.codehaus.sonar
+ sonar-packaging-maven-plugin
+ 1.13
+ true
+
+ testlibs
+ LibsPlugin
+
+
+
+
+
+
diff --git a/server/sonar-server/src/test/projects/test-libs-plugin/src/LibsPlugin.java b/server/sonar-server/src/test/projects/test-libs-plugin/src/LibsPlugin.java
new file mode 100644
index 00000000000..965c9ac7496
--- /dev/null
+++ b/server/sonar-server/src/test/projects/test-libs-plugin/src/LibsPlugin.java
@@ -0,0 +1,11 @@
+import org.sonar.api.SonarPlugin;
+
+import java.util.Collections;
+import java.util.List;
+
+public class LibsPlugin extends SonarPlugin {
+
+ public List getExtensions() {
+ return Collections.emptyList();
+ }
+}
diff --git a/server/sonar-server/src/test/projects/test-libs-plugin/target/test-libs-plugin-0.1-SNAPSHOT.jar b/server/sonar-server/src/test/projects/test-libs-plugin/target/test-libs-plugin-0.1-SNAPSHOT.jar
new file mode 100644
index 0000000000000000000000000000000000000000..85e4772f474d918c90f1771244aeb500f419bca7
GIT binary patch
literal 40097
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 |