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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 + + 4.0.0 + org.codehaus.sonar.tests + test-require-plugin + 0.1-SNAPSHOT + sonar-plugin + Test Require Plugin + This fake plugin depends on test-base-plugin + + + + org.codehaus.sonar + sonar-plugin-api + 4.5.4 + provided + + + + src + + + org.codehaus.sonar + sonar-packaging-maven-plugin + 1.13 + true + + testrequire + RequirePlugin + testbase:0.1 + + + + + + diff --git a/server/sonar-server/src/test/projects/test-require-plugin/src/RequirePlugin.java b/server/sonar-server/src/test/projects/test-require-plugin/src/RequirePlugin.java new file mode 100644 index 00000000000..440f73bfb58 --- /dev/null +++ b/server/sonar-server/src/test/projects/test-require-plugin/src/RequirePlugin.java @@ -0,0 +1,11 @@ +import org.sonar.api.SonarPlugin; + +import java.util.Collections; +import java.util.List; + +public class RequirePlugin extends SonarPlugin { + + public List getExtensions() { + return Collections.emptyList(); + } +} diff --git a/server/sonar-server/src/test/projects/test-require-plugin/target/test-require-plugin-0.1-SNAPSHOT.jar b/server/sonar-server/src/test/projects/test-require-plugin/target/test-require-plugin-0.1-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..ac1f9f68e46a7d4e6e11731b44e2c6a73d12fb92 GIT binary patch literal 2488 zcmWIWW@h1H0D;YWm%4!&P=XUk`?`iW>U#RQ=?6fSaWHHGD%(@JWSKJ~149fG1A_>% zGG9kOPdC@#5ItYF*h${`%?1K3?>+vo-`7@lYKkg6{dj`iqyB{n9F1t+U|{tgCkIl&W0WA$x=~qPBEq zi_{jCG?Q$rBXI&B?iIJ3*%HC9H*utHj}l`Ksuzf@zj-TG9TnSqfP{hs2?a~wR|vhnw$64 zi~qkG^T=7BPniDpl-rLS(I(?*Qx#_axpn&G4-Vz2RSxBAj;woI?aflnRr$6su6q&7 zHbL}=l6ec!nefewlr?GZXR(^qu2XU$cHe4Mu`drIj%y#!}A|Kw7!4${qD_wKK}g1_kg2XPA8}8hSQ6L z+XwlYYn6&u`b*yWd$8r8)Jo}t7kX3m^j6&r5xHJ7D^Rs3^w4Hb{Us-7FIt+Pn)yPz zF#e=dOj@Yc=j1j8(O+*IyZ21dQdfGz|HyjZgTSS!Vn;V!VW~_G745W`{`JpPL1*UQ zFAkJl_`I>ZA#I%i&+kG%=jmR$V%-PgM5y&=;&UG(oi?7A6aKkKiB!)&|5 zbHrxcba@pZ7MLPp-L9W-zO=gc%DQf|n4o&j1Bb0#-6}84Xz#sQ<@);N{Of-oZ>`%S zZF==D6D$}plQ=(e+RaTYOU;8PbWFt>n2Pg@()E(_Q&KY$ON;f2^Yapm^h#2TON#NB zVnc!{AhUFfAknN_0FGz4$s7#KyOz48+q(2}05hRDD+7Zwg(er|=jv7D=1dKV?Z0dw zQ1gEMhEH<4pYm~ruJh+;akx_`s@oAhf3xq|tz{pq{{HxGQ8sDWUZzF)_vYTcId|@q z`?A}lH!-*0niXlb@NrK=;I<3P=A_=ACjZI&=3I%7r*6()V8fZwygBD>t!ZkGc6q{z zHA|wHx2{Y1e4M?^`?&A)jyZAx-(UQ#^Z#xx_&v*J-UfO8@7)1ArtUO*u{3Z(ug7=p z*5IC-)vK5EU;aCJ>L1R+14^u)I~G5j+v#v@`?iL&-?!}RusnAziFZZGwsm%*N+pXN zr6NvCX+|l_er=Gmd@aBmWwky$=;w!=O*@<8x0X!^pS|l!+h)gojoWW^8_s>wt)KtA zyT9N2Tp9DP4aYX~EM3|$r*?7h9!JTYtQ&b+o@6Vjd=?Wo=3T-N;uqdKBWdR~kFZaY z9$D^OD-MWwtWhj{qu#lEX8Y8|S5`T)FVAogTwlf|>sozmKS#M*?I(&`{=wX<@e|B1r~n=_L%_N;E)?D(K1 z=-Qf+Nb8s9SN(o!ci8aOp3=&KRQWp2y6qD?7JW@mKcAQt@ud0`IAH5}lIq2FH5z7d$fI)K=V3JI;CD8C@Js3bGB zSoD-H*C7J|hljO(f-kHTb9tQ>atj8E|8pp6+&K4GgG%Wnk*ey~?>>H9Wz42>!s65Q zQ~4Vb5~k|zczSB;GvC{9ORm5B`7uQ&KVfwJPfE z7LTO8CTX%M%rty@q+!$igcG+vvpfFKofmLs{-c@%`v7l7CJ|=bbs*5UKp?>I))7P_ z)rc@{*y}`)Vh~u;s179I+K_5UgvF3BMlayO+QBBw0T%gKihG0sn57k{XoCO&hX4N` z1Iq}w6JP}$%>9^}K}8)xvj8)OW;_KSZc{-;AHviy5=_Nj5W;N2UKGOYTGF_ILYtrk pBO + + 4.0.0 + org.codehaus.sonar.tests + test-requirenew-plugin + 0.1-SNAPSHOT + sonar-plugin + Test Require New Plugin + This fake plugin requires a version of test-base-plugin that is not installed + + + + org.codehaus.sonar + sonar-plugin-api + 4.5.4 + provided + + + + src + + + org.codehaus.sonar + sonar-packaging-maven-plugin + 1.13 + true + + testrequire + RequirePlugin + testbase:0.2 + + + + + + diff --git a/server/sonar-server/src/test/projects/test-requirenew-plugin/src/RequirePlugin.java b/server/sonar-server/src/test/projects/test-requirenew-plugin/src/RequirePlugin.java new file mode 100644 index 00000000000..440f73bfb58 --- /dev/null +++ b/server/sonar-server/src/test/projects/test-requirenew-plugin/src/RequirePlugin.java @@ -0,0 +1,11 @@ +import org.sonar.api.SonarPlugin; + +import java.util.Collections; +import java.util.List; + +public class RequirePlugin extends SonarPlugin { + + public List getExtensions() { + return Collections.emptyList(); + } +} diff --git a/server/sonar-server/src/test/projects/test-requirenew-plugin/target/test-requirenew-plugin-0.1-SNAPSHOT.jar b/server/sonar-server/src/test/projects/test-requirenew-plugin/target/test-requirenew-plugin-0.1-SNAPSHOT.jar new file mode 100644 index 0000000000000000000000000000000000000000..3437dfee71c1c77810f61a9db63cb0eec5a65f2b GIT binary patch literal 2553 zcmWIWW@h1H0D;YWm%4!&P=XUk`?`iW>U#RQ=?6fSaWHHGDx0`y-tS0828I?U1_lvi zWxkGno^GzeA$q=Uu|X&E4l9W4iT-52r#jVTPQ==p(?uTBbynSt(@wHKuE7#>;N_>U zM+=?jdU<#l8+@LTcvNNmg&QwKw?6b*W}sxkvu)AcVoBW#BD2iD`8+W-57@};RGjAc zxcaoIR6@b4{N_~$%CfA}46+U}{F=NiP3%tY$~$VksZI$mV~wu9VmjL`rdd&Ntte1$ z#!-ZEB~KP&!{SI<_vz{H zzUW=saEyP)kM@K7h^PR?nD&aYaxq|3ECym>U{nO97M5lfr3U1brf25qCFdj-7spQ6 z?|0Zh;F$gPOeZHN#Q*{2=+hTEB##6g6j5{uEH{rhsL|{FDC9$!L8C+kgB(|(-{JX> z9$MeO`+oQ4KOcX7<9onSEvJ)Hb;IdJ!tH~6&9zF!EBz&J{XN)nP-><0!3({qdU~sF zhKO9RnH8wo6MASfr~ZvM9Og6OX|j@^5vXsIi`;eTYk z??K?wRI#I*uCP=lhl+MuO#k|4s-QFT?-vKkE_~kD-H^6UfamurkHtYx_jLS-aZ4`# z{qAes^xlwZoi6(KA9mf0v7hzV!eO@E;W=V6Zo0e*5DQEZv2NE-IA2=bdu3g>Sxitp z=Yhjku5OhVX0-R-ta5$*a{l$dkGIzCkv6^hmkAb(m*krD5GUZObK@ zR7(^?cb?d{*6vyU=4i8pTQAx>T0FUUN^etd@z*&MbniDF+Vd=lIb!xC8{2!6rEPd) zm5TOlFLKzP+kN|W$L?8v=}(UTy?aSKuTpPnX$#;aY@Zw^>_d-ii= zlYRHlRD?CYVyUaluOgtV-F-V{0i>FMF!%bzYkE~>iwB1_$5@psb|b1yAq7@ zdnyiP7AIWexc^RP#)8ZgYo3Ysati+c=O_xCbWg0(=x^%%xtu14H@^=5^~2_k>ut@q)(B~=mcxMd!lU-)_Y_ICnxymj$~*^%jw?(KhkWyg`8M6*dO z%g-(U(KTuFlft;7!Uyw~njd%(Fs0=9ydMR>%u^=chh-@aa9wi4&F*+JFzI!Jk{@-F zU_nuSL26M+W@@o$?_I7#3K98{L2 z2Gf4#9PjA!zj)~Mlq11BY?Wz3j8otp1>Y-O=Q^^*8_Mon!yR9N^8!B*Kil z?gV-n2m~13I)Z3;21IDXUYCLtgTRtTbs!1XhE%g6n}Dqf0BZ-EunAZxV5txg24I$F zpu!IV1Q`DR{|PKY;7))Qg}A*9Dhv^t)tE6f<1BaZISEuaB23L8!BiZj8NxE`g(b|o zC5Y`I3UN26gYk$W-Mv6KrsU|C^+D8 z4lxsZZFubkO{npl810B95!*nnU(qd0*^#mDHIle!`vGfdMQk{uv z4@cq0Cx2r09%*Wt6S8;0l`f&{d!GE6a%NA-f|N|pS?gN2F5K8TapBITt(;(A=B$#` ziwF8_HHt5Tu=vu~HN;WZ)6Wg;%bHoAytOfW`LY%@oJD}XglqG4^z(Fc4Gz)sb%TXd zD$o>AIF(K~>vz~dpymCvKYVqJyX`#`eohl%Y+BMQ@}NtndUC{)*C)!PPFjQ z*V#8`UVO%t%c}UP{KG-3DLKm5rl`c3e`ERA)h)K~*MYRgNpsmn4}SQf6M0GVF2m`o zoD(K(*`7P6BD0?_c>M*Fb-zS;>dod)pBBCG-;FzgL0f8bC%%soZAzJ1tQ0OLUiaav z#mDHQg-2)St7a#?d6H$y)6Bs)m4DrLsg}=Q=bJz7=w3Z1ru#ALx!~(e?-nwfG~7D0 zBWYQ6Da%Q@IkO$pOcNv`zeS|HX`7_^=hxD;H$D~37kPU5Vd=?#?n|GrgVNR0HS9BA z0z})o0GMlvK_LT^rr&ocK86%S@Ga_FgXEb&O z9-s;^a9Gj^qTyMC1D;O%w%8v*G{yKUC4M57B5F_Er{)H;J1!~+0=_^Dn1 diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/sonar-build-breaker-plugin-0.1.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginClassLoadersTest/sonar-build-breaker-plugin-0.1.jar deleted file mode 100644 index 0eb5f40bb8c6256519c648884da43a61687105c1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5027 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 imNY6eBDn`)7`B*4_AV8H2$>`&fb8ATWZfZ9YD zIDpV#>vj}vBFNf&9sNArT!TaOeBD4MFirmH{Re0Y$b{Spr~M8a2)KTq_J{vp@(dZcaXONY*`h`NA68yoL_}+n>*yxpS7+!d!(TZzMG$`h(VdQcah> zG}o5t?}J+hZwnkLKX7xJgR4Pg;iRO?+IJgQToIhGIcbBQUrj#09CuKL)%_Yawc9hV zf1bL!=I5qe&(@SVXB*zO5^WOc{a(3L=iHx(w?aO~9=-CYbB;)C&)yiFV})m$-CYmz zFH!t{C2o$V>bYxmo6;W3`CVQsdB=f2gJEmym84_*uNZ>v{1ftC&FI|pG&Z)&H2w0g z`uD$!{yUws0|pQ~$AQJaIqm~PGnbJ89AJ=eHbx6DP&miO`#L)N2S=XZEIC0mIXxjM;e)SF*a!Zwj(`TH2@=fe0@93y>H^I_ zc1w0J^(iVk^2Bq!SzwjMu+eCikXy@+jH0@Aad}BafpvLFo>?2?ijo4eIGs(!Pl%rU zX}NS~CD%5twzO*rjdRZI2@$w{V%BOgaNs=?Y}L#J2HtKU4)A7V5@p7fOnIOQmcikz zBZ!F<7##3K3o!x_6v%NS!hkShNuxQk5h$U-0Z+il4#JF2glT^rGk|fA6uS^3Krw{R z2q7c~{dJs=Yy^tuU@?vEIpnaDM>1?GWSaz2@+N3XXk zcBwzM_2_rSj`@t~*8Kv3=|?@ga`{&|2Obpc+?-@!cCIF$uaAp=iS_jw-jcHQ_m}C1 z|NK>2d^R$-{aV1b%>qp#y~UNRZDRkP+!p!BoaFWK&03Mzp1m<^j}@M2R{n61H!ksA z&F}YqT!xXqZ$|a7+nn7mR?uua$t{R2xQo^P5aZGve^sZsDYmkl-n~m?`kb@R&)dfO ze68JGub4hRz?+?;YTsGw2f*OWWn=({7$l(i&_WCp(DCuUj?VtU5#WFZ1rrFv0vfwE zA!KbR5rwWFdql}&)vpJ12+lZ5P7qB_Pe@Am;Oi6ifj_Jxpn++E1hcw;G-IK{>$OoHKhu1g@W$wOR}qSnM1H>_7Hq0t0V15C?cOGKn(dN~b(P6=2}- z))7P_1qKH^)k2Iw1O;;3h%g|GSkh>YYy?VZaKKYAvV$<=6Jgq4$4p?{BgHPn2v7{+ zGeQW-L4O?=ARB?=Iao}idk#75`zuUP>`|$VI@%OGA0lY0Q`bB AXaE2J diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/not-a-plugin.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/not-a-plugin.jar deleted file mode 100644 index f35e77146cc72b716e5c623cba596be0e8e4b201..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 960 zcmWIWW@h1H0D+*oO*UW#l;C6#VDNPfan$wnbJGuv;9-cG@Y7p+_9ySJj3NvHKy4xn z96-pkWjl&C5oB$?j((nQuE8OCzHT5B9so_a0mL8^KKq_I?c=ShcahgySL@uF^P7VV zt{6XfTBPH3?u3rFt`i6QlO6kIY_YpsY3x6owcAkiA((?Oqs zGG4~TUI!i>oVoL7rJrkOrKaT1Oo2|3*!5F#Cg`SBv3+cNwkHPYT6T`cQAX@xG4E{=pGoPlKEc!Z1%`*CvFl4JFXf^NfDXYKe#r@< z$>|A62_JlY!aneabp$jpO^{$#7m#Kwv=(UoVJ+Fg)TgNE$P@p`b%9kH!$zf9LT)WP zGK%Wf#pNXx1=i&yd1h^lD@qE?;&e6@KOuVZr{&U}m0a7n*sdki`K85aO-Ro=8_Eq1 zJI=g${TaY;+X)UkMkY~aTuFupr~(We-a3M4q_E(CC!hdtkP(QGK#m#_280ny8qJZ7 zKnV;Ecw&N>hCN<5kxcvR_!VdxQshF607Vc!BZN?l&|yR}0>yK%h(`Aua@ffunYE-b a2di0Vk&f(7RyI(OvI1cxP^%#mhz9@?aQKe^ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/old-plugin.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataLoaderTest/old-plugin.jar deleted file mode 100644 index abb19c057b571183859ce67014784c8c0e7db3ee..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 980 zcmWIWW@h1H0D+*oO*UW#l;C6#VDNPfan$wnbJGuv;9;nl^~qa%_9ySJj3NvHKy4xn z96)$%^L7+%BFNf&9sNArT!TaOeBD4ML{0eV9RkE46I^?}xeghKFnn*kqbTl?Y0h=i z^e$&((}OU%v)z>vlPCFKy#3qx&bb3UjUIg7Q`OJ328ATOs5*CSPsJ^R{~u*O-0S+m zd2U%xz(udW0ZB25A2oP#BrGrYI{p2id^hNd>W3tg>MY@Mh;=oOsW^0qEUXKn(H`Bp^;D7*m1B77# zfnA#rvNn`}MAwf!Amy>@*8@5PXP71@h$g2eBqeV+2UDM-q9afIC)Wj5X$%{cW(m2q?8qppTNjs?R1{d3m*knXF|H^nFpJaKRQ!bK z$)A=>cUE$3<6^s(Q0JExr!^rx?QAGFFs#@)o*&enn*j{Bo#3!zWD;e@m5g|RD!{}kqa>b z6hZim5JEA+0oe!?&%q)Z-E+ucCy!*-lEwzCW}!tovOihbKtak1gq1+8u1p{v0Fe*{ AS^xk5 diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataTest/foo-plugin.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/PluginMetadataTest/foo-plugin.jar deleted file mode 100644 index 7bcf027151a0535acc23461c714b73e66685f7b5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1460 zcmWIWW@Zs#-~d9ux=l6=NPwL|fFUhEU$3O1Bs7ADL2vdaZ)G6<&M3m*efs=)c7_0N zc8>ZFubkO{npl810B95!*nnU(qd0*^#mDHIle!`vGfdMQk{uv z4@cq0Cx2r09%*Wt6S8;0l`f&{d!GE6a%NA-f|N|pS?gN2F5K8TapBITt(;(A=B$#` ziwF8_HHt5Tu=vu~HN;WZ)6Wg;%bHoAytOfW`LY%@oJD}XglqG4^z(Fc4Gz)sb%TXd zD$o>AIF(K~>vz~dpymCvKYVqJyX`#`eohl%Y+BMQ@}NtndUC{)*C)!PPFjQ z*V#8`UVO%t%c}UP{KG-3DLKm5rl`c3e`ERA)h)K~*MYRgNpsmn4}SQf6M0GVF2m`o zoD(K(*`7P6BD0?_c>M*Fb-zS;>dod)pBBCG-;FzgL0f8bC%%soZAzJ1tQ0OLUiaav z#mDHQg-2)St7a#?d6H$y)6Bs)m4DrLsg}=Q=bJz7=w3Z1ru#ALx!~(e?-nwfG~7D0 zBWYQ6Da%Q@IkO$pOcNv`zeS|HX`7_^=hxD;H$D~37kPU5Vd=?#?n|GrgVNR0HS9BA z0z})o0GMlvK_LT^rr&ocK86%S@Ga_FgXEb&O z9-s;^a9Gj^qTyMC1D;O%w%8v*G{yKUC4M57B5F_Er{)H;J1!~+0=_^Dn1 diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/bar-plugin-1.0.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/bar-plugin-1.0.jar deleted file mode 100644 index acf4fa40269eb6889890502d9f3721b222e4e97c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 714 zcmWIWW@h1H00F*&xi$Aj6Q9Sfp1{Q4$)$$-t~U`;)gG5SLbPGcdBe zU4dX>hYbW;-cS3(SJ$}1-b3N% zG!ae~Pif5qqPi!|HhNsYcq>!G-(Sk*6yv)4XWsOfF>k8=qFCkl?b^gZ?Y&c!o^9Ib z{)GEn^q&`de+e$RVc>K?%gfL0sO%KpRXlwjK`MK54hJIrp?iBCTK0W^k_%Yv1p40fhYBov%FR84}H<|zdBXq^3T)r zuT{y4-nP(xSMFc@LO!%hKfs%jNsbv;{7Hag0T_e~OBz8;cx*(j{<{BKL=j)a`;iA_O0|D3j%s<=zIBed{#Z`HGokqs$){9HR{R7xqezJiA>C}(h zHIIQokjuyb3^Z5>hhT&-1B0GRaC~q{eo^Y%85^?>8;H2RFSeCnT6?VTS7XbTcm*e; zlz047mTY11xpQ>)_PVm{udz+d!Wqk^e3ScV(fmcFa^e@B17dGaGYc?;6g*~DUn!x? zb<5_L<-NegN2SYd%%7L-a>Y?OFSL`#&wOIKuO}^lpxM>E?%O?~7{cJ|7ls5pd!# zJQ8#D$*$W1PJf&}{SN1Aj)S|^#ksdF)-CYfwTM|3BrX?q?9t&Jn>IRa z1j@yJIqCFAIxj}?{(tQ`YyY|%#@MuQX2J$ene?T5afJUHKpxzJgW(D~f=p`10mq2*(j{<{BKL=j)a`;iA_O0|D3j%s<=zIPBid#Z`Ix-J%6+#k%&Ln&5Lg!A#S0 zSLxrX$xVSruFu^0{f;%S#MQ<8N42fiuX&ggo~N1l)kY)uir0dVCn9U_UwboaQdXFi z$ko=(eskBXl##H!Wyd}5t^lvSqRVRW<(AUUGk&CP*71J1mht)9H!Rbx@kUNQ8Qxh_ zz`^+R)D3?Hw-nwv7oU7?W_xe>dabT>fczYG)w>x z+wZ>Je=&LQl6TiO{Cvde_ffR`+Ow0&ao1F)yqPO;RkEHjx>tU4`h9LMjh{@QKr-6C z*Znat2yz)2fPn@JVJnOfW?;~B362jg$uCNMJHwjqkb!_pdM~%PiJs=&0*1T<*OQz( z7+Av>e7~r8OK_7&P=IPv<)p7+UGq_pFNjr7O8qMc#T~|^pJ)hmGEZHdbg_rsEjqwYV=f3a}-*xHiS?ALdc0JnC!d-@~ z5er|3uh%(pzo6jh&)@cmrvJGW>m<4#m&$zLV>!*YzU=j@wy)j}ALh3%&&ye;efQ2T z4vvkY{veNLF?8f^0(z810@1Ie`fe7%y267SRJ&$ELQQvHWl&TJs9 zKpf!B$Rxsmnhs%!43rL00X$)WQYE@pA8peRE26DLADkd*|C nPI!bNYXf;0**_o;BY-#1D^TwTc(a214D=EU!%HCD2KFTY*t>oc8h7)w{^+ zt*dqJ%=yhh23L$9JT21kI(I_HTi1z${mGKBDPr0OR4R7P=?)V14hk-rtF=}{hFfT| zagb<@@adq>Kp8LNVy^>_4$j>9v(nGCvr<#?XQn`>NbLG4ITLi#s@OiZJ=+rlbS)#3 zC^If^@<2Vz;PBQF#60)PV^00G{tY#^(cfN&y^ehT6+ F007}zZHxc_ diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/require-sq-2.5.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginJarsInstallerTest/require-sq-2.5.jar deleted file mode 100644 index 8044dff8988779308c809c2e64c45afb8ee5430b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 12689 zcmcI~1yr0_wk`=FXay%gkOX&k4ek=$CAhl-vE2f!aDR$zF>~pu!R+O1m!=Xsea3;^DMj4;`;18QwOr*3BvWmuvOR>=6Kl>Z$EY06Ziws%D$`S$q3M8 zt;@_NK8Hkn`=`w%O8b2Uctek%`-wE02W67+ z8PQB){72lf-c2cW*K64j`i?tEIbu39gE5N6>iMCjTB5`7ZqNw}(ANSWrPn%rW!1B3 zGmdsM)hU_opQ-J?Aw0_x8LB9H0NX~BkRFy^3pI09I9hpjZ4l~6n3;(m7~sp}xO`!( zk#q~sVY`L?t?eNG(RP1q;ulXJR<=gJx9(phesql=5_VRWy0-s=9Lm4QS(`f;nONHW z5Aq~`D{tpyVy|axWoYPVVr8ytZ({Wa8$WwqlQ$VW>Qm3_e7czbV;cfbhU|4M?d@pv z%ysST9HL}oy51x8ENAjV3B8~{5e_wCN#|yM0YFygkLpHMO{t?XaTIG_aXMRpJ{G`& zzMsZyk-WSzTyE($KAdQR=Jj>6}>COXeHls;TGTy`WcW3vU`x4Qa3EIakj+ zsA}k%luQH*J)t)jFR)+eRts(j4@gpt8D!hjoEjN`NKwI*2686ooZS&&BiD<|o~a^P z#Ia8V{>HVE=ljnPV|n}5I8kpT_uTQ zlPIAa6R+Sk49d~3y(k~Mwlp3ge(NJYYq^{ya@YzI0)prz1O)egT+6b$_Vxz0mPC>! zmIiXVmPQ6YYdcLr(+*V;(?dGiFvY$KzqTq7#taad8w+)x87b!nhZ2fb=IiTJ(7$`R zvSn^~Wv*`6A{#*I3DmG&_a71sp@Y17K=bswO_x%WEE&=TQ3{X+ z?z+Mi1A*_1N_p465JoM^iu+0ML=f;5!+(8yzCb3+W#8me_9IDnS2XuA zq;RXx&h2V3vX&X@*(o4c?)`EL+qyTCg z&16Bx_NnpSsa`Z5oy41zj3fAnJ7s>_Lzuh@i_e17=Ygvi3Br^dv0s|8CEBU^frRt$|CyHlGaS&PVrkJz`*`###NJ8y^JT;Ux(&XwN^RMEC6IHxgZR2c9qD>XQKL zqNpuF$(SVhf`cr!oPF(u==gfMN}mw!wu!(@X;yC~j08`ZYy;^7q*6f`+{#xreoWG| zh_kIpnMpF%Rep?p2xB>HQ(%XfLt(kX?>klAa;=;AQ=DKk^Il`t8Ak8W)huSPYh2roh>}5Mm5N{Du@dF z0R}d92KE_bScl_V5^2=T0~Nf1m{8_J#JubN;cDP?FKfmgdT)`Qt*M*2@f*(UesjMO! zBh9r@Ktnb5oexK$A+koM4IU%L@~Fnm=qkv&2{m^S+tQL=skzqhNzQn^3s2zwA(uAj zkdooPdjBri*iEr#8buT?ykh+gfxCRNRsXSKR2#KUTTF-3NpbK|(RG_oolT8S>IA%t zfOkAK8iLW_Scp(gp-zq-+Cg*-WBQclv^5;u%U%o84Dvas#r~ZT!~Bh8C3?HLT0^@g z#y93~K8zg3mKG}wX2VypdPuitx{4=}gErgh$QiXq7NZ5WzndwGe9scnPcrXhJ_Rz` z#!|qR_#|Ltluj3Qsn?3T)W{B^(I#73E~Te5>&(R;CbMim*Z;1&)3&+L62-}TsHZqY zVT{WRpYN*BnXUjxB%h{-zlrKi>NJ5GrFWf*6g4rZuBFps8~(S^z?zvNY~U4%a)HSga|Av(rEW zC_@(d41l7!V$JyoZbGIHMvZpo7WSE{<*4TTs+tDkJ9AZa@dOOYO?xZux{E_ub3W3y zQ0_%dbCG81gObWR;i3fd{1b4Wtg17bxyK)hgk6f6knHX!t|jC?!a1^BQH7+B_qnX2 z0N9qcaQBWj+3%Emn@EM2RbIsxB8#R6G@urrFKXmO-D0zbbhp*VezgWo;Ze9Ma$N;%#hj|tPKIpI?uiCYA2$K{a+wj{m92DgQgNJ|p`!Cn z9y7LNt!-m_cxwlbYnxm%d*rN10TOI9KvpN5fOfDSkXJ{w2cP*K)^jRTPZ*@`tPVPJ z<^yRTD+7*Jf`N=|C0U`YOd3#xi)u2;5%GPzFY!U>fD&hA5c9oMa=V$c4Dc~>3vEgH zLexzngu1s~qWZ4dWK|KEpJMa*WlgUNYGA=`}s~zDZD4B9-q%Rw;$&C8$gDZ1g zk@vH{=GXQm`l*S8aWF?Lwl6Yr)$H&9U;Dnc@R6(;zE}F_P{|(Tz&wQVwM8qHNlTw- z)Mwg48o<)q<^BNbls7f#*4KxcmuL3qy^kp!nHqY-5GS*zIJINtOp+4qh20Vb7aa!o zxpbm8EY7H$bCL^eQ?nvHAm+SDE~7U@)4(yHp8lMFp)MT~qd350lqPv_HLt*rYC50# zp2B$Y_3Nfr4y~Ih#rZ$M>gvSJU5uE7yFCs_qPnh#LXNcUXa0LdIco zA#^FzG2b(FGDwZC=d~HzB6w{H48q!d*}CJP$y&Q_VD1!dDPqsz)XLwkIgQ$%!+qS% zLa){GPF~u(IH~70L#$qeQancz;++dB4r!=Ek*6Kqf0vJF<4|w)262^cMa&m7%P0y+ z_E{FYCkjFW*jm~%cJ$tz(thHb)ZrO|=>u?NWiROfz93$A#OuZ}ejIuk@U`tZh^El6G@rM2F#O z9rhzKeZ`RP)bGD%eV67!o_VAzWA4ROFAup>8E>d@W{~w@^UBS4-6-9Q;u6jSn?)98 z!J^AlC3f$#ku;F>Y8l}hc#5v6cLHt1wkSBzB=JDNju#}Ik;YQ-b@HUvUN||fT5^RMaj676SFxG86RX%N@G1PB zBsb&0}E}IME4;>9Ti`gmN1hoQKMg4^c=DI>e$rY44Hae zd{tTKfk^aZ=v_m`Y6dvwoJB~mz%*E&Npg{_e`qiNK> zac9(qS)bo-D$c91B%JcxGi!U-+Jew@;_mE$1m0^IKsjzH)X4}RzNT|{=`mMTOGinL z>-DR#Wsj{3_AO>{1$QkiLahr0I(&Gz3SZVDpCo*SXImkBcmV>R@}n1ggS?)Nnhqh4e{0XEPzCeNWFetU_} zH%1@1L*hUp?n~_}%Nhy-T%{p9_EN@2Ak{>ylhsbJ2oS3dMWMift&!U%QN zn6`eNy1P{7eSn@iVq~1fp|arT$!MA8I=XU6KRRm}zB@d7biKs(YCb+#<2f=t1s;Rb?+)lcK;2B@EZ2PmO-~I>v=2f{up~B=XTYFklnDOFh6>^|c%U&^!Uek?Ynd#OX5c4mnIjxUtV3RB!f+vj0&*WKnectZ#ChmZW!7SB+~8j8pYJAJBk)ju-B!?0IJv=WLju1(A+9SKwJ1t4g&f6Ge6aEn3h4+?##|N1M~ae zC1~N_;Y=ls7^lPS=e|yj<_JfLujP*$uqpK)qHnR2i`fdg)1p(AVX9aaZ6y0^H19TS zI2an5jKyHXXbAv;J)y2v!tJxH+peGC72t7~Y?=21D zz$|=2)!xZYf7qr>cc_FDesn%oFv(~>)XiVTzrtJ!ehIj%RPIq`*613?o~xANb5#4n z!nKhafac~l{ zo%ZUq$nH3(>WUqd4QbxHO|YK zaE8{G@H4T=g-bV^l-QPuIH;zfcs1o3!wCA?QxwFkF@0>|Ceq`??1s2JeGJJ}uo`e3 z1;k%>Nk8_5{HC}L?vFRzWwFI2#6rk|PUM;vz0%nBxe?bAXBOPmt&)tiIDo);% z_Y!+*MJl}QPZ=#;tH;*rK2o^X0^yGotVUeN1oN6*!#@(r3lRthN3n8$i`g>!{uIt9j<%?fhs_wxWm~_%4>y&iIIwYw z?9OhF68po+97GQB%S2Ze0;dT`)n_gXQ#dU%g-c_8GtQ zvA&Ap@)V0ZZ~{UQrp^E03`~Qc;+T&4T~G&u7!R(%HaSe)1aIUZZ4fbM#>_cN*_ZXz z5pi4Gjrg2d6XX(S0m#;u7T#sb_Iv!KHYT^@J{%OD*bz$Z)VFMUL2x*x%OjvB(_>AF z{nlT)G(}BLi7UT`WT`C_0Gnoybg#C0Q&-8~2xEDgsh}_mOh>fkF?=ZlFII7~72#?- zvD6?A2W?*fVq5)G#Q%9Un>ESV6tX5!bR8e<{$|vwA>|WJiL5OE7&B3zm7b{yS;tHy z!CEXZ7FexRZ@GIGfojc)vyah-^2$0^uc9&{mqx&44t2JVqa8a z)5yoISu**Ac>s~5X-6%#h-nGd8T}JOZ#wVoM#i#qsb*Y}Nyl}u^f6=S1P=}fofSpu zfHku6Y{-_uLicg$(NplbS%3j$6Wzo=OsCOtn&cU8f!6lF;HJ%wkI+&5*4T4JcS9%EWDTP5Cv~6 z>bI9onKGfkk1K47Y0}@gooRqlMcFslu{8d{a5Myq9QWODXb(a&ks#*x#Gq#{XZ6K6 z2gmLGOrceZg42Vbi?{+*wF32G#aED|6dJ30C6eg2=-i162<%Ds@+1`osW~V4hq{aC zwy)NoH8I8zD{e0#nH-noK9(gcpW=N0ituH~c-wNeOJwc6i+4+Gk@l~6Xig?55)>p0B+1Twn-X}7rs1~hzn^!o4nIKAURio+Cb7jZrE%4_+%4Y9 zutL5$`7B{6uCLn$WA5FljSp8^t_9-QfPAb+pd0u8$)r%c@J+DXoJ=LmfJON7?XFn& z`qeYq(mgI={)N`ad;H6>tb#!eM7y!i*NM@003 z#+$nduG>rO1FlPUhmBVe?H&*49g1wYBBYmK>&$0PD>?{Hy*958!`JZ^O1c6CdOYYu zd!ht|5!OGNu)GaP4Thyxr}*^kW?Po~6-bF53;a=c47uhFSbU>r+8uDbQUcR0&4(b; zZLQE#g_*x*jGhwKpv~s;e#TXnj0+6};=hZ+#6#Z$D{$d~QJv*_staB2!$ZR8Q&@bsrLre@m-8zRVrSfsEmT93abL2wFTaJzJ6gZ50 zYA_DDWO&k&rQRS5@kFTZ5i^%WX~>hSB+l1= z(p=vnBH*o0NP!d+bn`MNGplbsl4%6zV=aI*VGi9nJ|-uSt#Ev!+go>M92tgM6{P$4LcH3Zdf>V-%qKW#Nkv_g#bsgqaeq<9pkwtH{j!kJPVEpX+(pB3I@ zFo&{u%o=E+ODzL4ICdt$aXt>rbKRgQ6!v_RjZ9{puL58-JAW}niJsQ9>>NOIu}G^) zZH8*Zo}b2JAj5SWUMp<2e^BJgSykmKSP5?Q2Qj|oDsk2V!8?EGk-*mKrG&fPLZ884 z1l1gwIHOmXG+@;m&jjmMxoP&i!CvZChilq0kOR#-iwMARQ!Gtd<1E6(E52Ub@-Y~% z@HvcROB>vMT{tel`)Ri_Wu2pkS5C6Pd7b zJ;)o8jd{Tq*p4~{KpK0qYksewg!S0f)U~Qf0SSbd-t#K*hif@OhxNz(Jc_olW9By9 znubX4(JBE$Aa%ue?O|nOFW6*l#H}JDBlwt7oyCO%LqmDXoLz+>2Rp}pc+z-b z0%kB5$Bs*{g4*StL6E-CYnn$)GN2+UnJCe#{1SPV$W#NF&ePf86k2#;tVI?E zt}kq%ZcFwvUClJ)_&p%8armTxPWt7~@ZSCF+*vweAE)knp4Z2o<$v1-=&DFhrTH2< zj&Yo+BBgvgiuMSo}=n=7c|la?oxohb-@S|)3@{l z>x9dydF|fd9c)|scX2+5d&D=Wys@n+zY+I{EOB7K4fsJ|yC%TDvce}ay?XoU zkB2crW7=C-RS>8fb`R>N5k95j@Oa-`S*|cK!V&J82X_DSSszewKxpgftZ(?~l;D5m z;$;7E==)C>r?_nQh8bz8CXFrEQZ5=}?zxZ@8Ijbdgoj**&=9jOcoJ9#Fk|E@U)%Vs z=}^HNv4rkNbgoW1NKXh)G>|w$oz3EF=gPy{xuf~TBc(mw$J+x$hz7*r_*L8)O&?c@ z723`Nh8G4i>b<_u2F@x#5d-J_&W~XQ8q?flmKIK2WXwX{Tq+hPvAA2lG%*#%tb7A% z5`Ij(vi)B@^r?1vl<}C<_%ELm%Tf4J^}BK@dUI@Z?1!-NtP^AwLq_-hof zge5QVff{E-#x{A#?F$%<2(!}*8}55$N!KrTI>QoNif_Ce8rD=y2&McTE%6poi{lNs z*y0KFqB zT|}%G(Lq;v#^Q1OH_rC&ZF>l^p;nPW*=z6EkIpJ(NRYo~N%-lIc$884-ahMD3`{|= z%@a#NmV~||Ai#`|j!lbsQO{!%+zC;H{vlahDzZq1#r^bo`%00+2S0IP z&==f1eCbgNMUo%(P}V`=f(_4J*@bPpXp}1~rd@p<64o}Q$_zt*Ou^e7GF8xcDwQLs zVE~K!@*oG)J21*U+j+T=biN&KHWJ2>ZAVxSAN7GWBxwCmi#qdy-2M2QzFdo87R{Q% z@t~B1U;+uA+vz0+f)MsudNb9$RC9kGys;t(C9RK0Zelnk)Z?m|&!m z{Gp%=s%86GEGtXuSQksUsB3u{g6#yM@cZ`X37Ko1x2XhN4O@OhWJJ4?v&y7rc}cSV z@!O9sURlD4ZdmCGD|XJeo{0-r7b{i$s`_XFIfLdvg)I2G-gA)^^tkl65Z^De)MDyF-=PtAY$c=vEw`uNihA78 zIu?J4MN;)z4m^9^OQl@W6s)pfagthgh!Bl3ehOM`PZ^=6bX5-PzAfr0?mY z#;IZ)IIF7g#EEcqLp~#Yl%45Yr=wfXiW=r0be@H5MX?`Pdt-;Ps8YY98J`tVrfz?5 zpekfXU_cSo!q9vIMavXZSjsHUlK1hsgVzU=0qSfY45who(N%VMjd*llmBiD1)q7{n zGXjh9^B0fR1gN%PU7ZdXw%lyy$JaRwJgQXhCfWvz`50t7+hrFQ)kV6bR0sSVM!O8D zL)e_*Fv6-$BjxAO>F+RPmMf9bxpetIKfBFSXSQ6{aDSg*RZcrMh>PBwDtVGbH3x+pfzJKAN^Y;ZL{lKu2%#)YU+OHMJ2Yg+VdAuu2zRTV=e~CzQr(0IF>YW&w zIk`eDk2NEDz`7P=_PsZs3)BYs29yeQfBq(g*U1UjrGT0hkR7nqZLjs7kcWWq{&P)t zIIy@K*%IJ%4+CQ*BoZvvmq&L>*9P!^R}+o!fX+OC`#p+M($dOpYYD-Oc`EV(2orTH z33wrYE7nAq(6m~F{7&~Ak*zbq=Iv4VFy0hB@H`Xw?ZOp(y;WR;<*XzdD28V5F@Kh`IqV=Yl9kVou?t-vgBSYg#yarYN6`Bz-`dQF7n|elKT7c?8reP8}KMGjO zJur!nqSKqa>^A8BRgbw(DymT?u6W9(=TMuh#IxsUkpG@-`&9O)gamQG_v87y|NN!) z_w3sr!aq{3H|f*;ceOuoaerC(E5q{#2lQw90lfaitNh6b{gu@pjNjj6ems8`;K$n! z4X7s$@E;kX|6cD0NAzd=a z>Q7$) z$44%rKMdaAV&>m^?q8LEM9sgHP5!F<)H44*hW__{k6(cK@T8`ZwsmkE(wI z&-KP3{!4xQ4@LS9p#K?T|7ww+WA)GO=T{CKp6dAb>GWrd{O@z{%NfZ^Ks_BTKtLcr N{Scqt%qf3-{U5R(SC;?) diff --git a/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginRepositoryTest/sonar-artifact-size-plugin-0.2.jar b/server/sonar-server/src/test/resources/org/sonar/server/plugins/ServerPluginRepositoryTest/sonar-artifact-size-plugin-0.2.jar deleted file mode 100644 index 1953323458279d70a1cab0fd6a8d88b1654f0f1c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 7456 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 F{Rdna14sY> diff --git a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/updatecenter_controller.rb b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/updatecenter_controller.rb index 555eb77744d..74833832017 100644 --- a/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/updatecenter_controller.rb +++ b/server/sonar-web/src/main/webapp/WEB-INF/app/controllers/api/updatecenter_controller.rb @@ -48,7 +48,7 @@ class Api::UpdatecenterController < Api::ApiController hash={} hash['key']=plugin.getKey() hash['name']=plugin.getName() - hash['version']=plugin.getVersion() || '-' + hash['version']=plugin.getVersion().getName() hash end @@ -58,13 +58,13 @@ class Api::UpdatecenterController < Api::ApiController xml.plugin do xml.key(plugin.getKey()) xml.name(plugin.getName()) - xml.version(plugin.getVersion() || '-') + xml.version(plugin.getVersion().getName()) end end end end def user_plugins - java_facade.getPluginsMetadata().select{|plugin| !plugin.isCore()}.to_a.sort + java_facade.getPluginInfos().select{|plugin| !plugin.isCore()}.to_a.sort end end diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java index 0f1eea698ea..1575f757006 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchExtensionDictionnary.java @@ -32,7 +32,7 @@ import org.sonar.api.batch.postjob.PostJob; import org.sonar.api.batch.postjob.PostJobContext; import org.sonar.api.batch.sensor.Sensor; import org.sonar.api.batch.sensor.SensorContext; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.resources.Project; import org.sonar.api.utils.AnnotationUtils; import org.sonar.api.utils.dag.DirectAcyclicGraph; diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultPluginsRepository.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java similarity index 57% rename from sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultPluginsRepository.java rename to sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java index 2687ebc56df..6e2c5886c60 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/DefaultPluginsRepository.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginInstaller.java @@ -19,13 +19,14 @@ */ package org.sonar.batch.bootstrap; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import org.apache.commons.lang.CharUtils; import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.SonarPlugin; -import org.sonar.api.platform.PluginMetadata; +import org.sonar.api.Plugin; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; +import org.sonar.core.platform.PluginInfo; import org.sonar.core.plugins.RemotePlugin; import org.sonar.core.plugins.RemotePluginFile; import org.sonar.home.cache.FileCache; @@ -33,26 +34,52 @@ import org.sonar.home.cache.FileCache; import java.io.File; import java.io.IOException; import java.util.Collections; +import java.util.HashMap; import java.util.List; import java.util.Map; /** - * A {@link PluginsRepository} implementation that put downloaded plugins in a FS cache. + * Downloads the plugins installed on server and stores them in a local user cache + * (see {@link FileCacheProvider}). */ -public class DefaultPluginsRepository implements PluginsRepository { +public class BatchPluginInstaller implements PluginInstaller { - private static final Logger LOG = LoggerFactory.getLogger(DefaultPluginsRepository.class); + private static final Logger LOG = Loggers.get(BatchPluginInstaller.class); - private ServerClient server; - private FileCache fileCache; + private final ServerClient server; + private final FileCache fileCache; + private final BatchPluginPredicate pluginPredicate; - public DefaultPluginsRepository(FileCache fileCache, ServerClient server) { + public BatchPluginInstaller(ServerClient server, FileCache fileCache, BatchPluginPredicate pluginPredicate) { this.server = server; this.fileCache = fileCache; + this.pluginPredicate = pluginPredicate; } @Override - public File pluginFile(final RemotePlugin remote) { + public Map installRemotes() { + Map infosByKey = new HashMap<>(); + for (RemotePlugin remotePlugin : listRemotePlugins()) { + if (pluginPredicate.apply(remotePlugin.getKey())) { + File jarFile = download(remotePlugin); + PluginInfo info = PluginInfo.create(jarFile); + infosByKey.put(info.getKey(), info); + } + } + return infosByKey; + } + + /** + * Returns empty on purpose. This method is used only by tests. + * @see org.sonar.batch.mediumtest.BatchMediumTester + */ + @Override + public Map installLocals() { + return Collections.emptyMap(); + } + + @VisibleForTesting + File download(final RemotePlugin remote) { try { final RemotePluginFile file = remote.file(); return fileCache.get(file.getFilename(), file.getHash(), new FileCache.Downloader() { @@ -73,27 +100,24 @@ public class DefaultPluginsRepository implements PluginsRepository { } } - @Override - public List pluginList() { + /** + * Gets information about the plugins installed on server (filename, checksum) + */ + @VisibleForTesting + List listRemotePlugins() { String url = "/deploy/plugins/index.txt"; try { LOG.debug("Download index of plugins"); String indexContent = server.request(url); String[] rows = StringUtils.split(indexContent, CharUtils.LF); - List remoteLocations = Lists.newArrayList(); + List result = Lists.newArrayList(); for (String row : rows) { - remoteLocations.add(RemotePlugin.unmarshal(row)); + result.add(RemotePlugin.unmarshal(row)); } - return remoteLocations; + return result; } catch (Exception e) { - throw new IllegalStateException("Fail to download plugins index: " + url, e); + throw new IllegalStateException("Fail to download list of plugins: " + url, e); } } - - @Override - public Map localPlugins() { - return Collections.emptyMap(); - } - } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java new file mode 100644 index 00000000000..f283dcd7247 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginPredicate.java @@ -0,0 +1,121 @@ +/* + * 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.batch.bootstrap; + +import com.google.common.base.Joiner; +import com.google.common.base.Predicate; +import com.google.common.base.Splitter; +import com.google.common.collect.Lists; +import org.apache.commons.lang.StringUtils; +import org.sonar.api.BatchComponent; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Settings; +import org.sonar.api.utils.log.Logger; +import org.sonar.api.utils.log.Loggers; + +import javax.annotation.Nonnull; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.List; +import java.util.Set; + +import static com.google.common.collect.Lists.newArrayList; +import static com.google.common.collect.Sets.newHashSet; + +/** + * Filters the plugins to be enabled during analysis + */ +public class BatchPluginPredicate implements Predicate, BatchComponent { + + private static final Logger LOG = Loggers.get(BatchPluginPredicate.class); + + private static final String CORE_PLUGIN_KEY = "core"; + private static final String BUILDBREAKER_PLUGIN_KEY = "buildbreaker"; + private static final String PROPERTY_IS_DEPRECATED_MSG = "Property {0} is deprecated. Please use {1} instead."; + + private final Set whites = newHashSet(), blacks = newHashSet(); + private final DefaultAnalysisMode mode; + + public BatchPluginPredicate(Settings settings, DefaultAnalysisMode mode) { + this.mode = mode; + if (settings.hasKey(CoreProperties.BATCH_INCLUDE_PLUGINS)) { + whites.addAll(Arrays.asList(settings.getStringArray(CoreProperties.BATCH_INCLUDE_PLUGINS))); + } + if (settings.hasKey(CoreProperties.BATCH_EXCLUDE_PLUGINS)) { + blacks.addAll(Arrays.asList(settings.getStringArray(CoreProperties.BATCH_EXCLUDE_PLUGINS))); + } + if (mode.isPreview()) { + // These default values are not supported by Settings because the class CorePlugin + // is not loaded yet. + if (settings.hasKey(CoreProperties.DRY_RUN_INCLUDE_PLUGINS)) { + LOG.warn(MessageFormat.format(PROPERTY_IS_DEPRECATED_MSG, CoreProperties.DRY_RUN_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS)); + whites.addAll(propertyValues(settings, + CoreProperties.DRY_RUN_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS_DEFAULT_VALUE)); + } else { + whites.addAll(propertyValues(settings, + CoreProperties.PREVIEW_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS_DEFAULT_VALUE)); + } + if (settings.hasKey(CoreProperties.DRY_RUN_EXCLUDE_PLUGINS)) { + LOG.warn(MessageFormat.format(PROPERTY_IS_DEPRECATED_MSG, CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS)); + blacks.addAll(propertyValues(settings, + CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS_DEFAULT_VALUE)); + } else { + blacks.addAll(propertyValues(settings, + CoreProperties.PREVIEW_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS_DEFAULT_VALUE)); + } + } + if (!whites.isEmpty()) { + LOG.info("Include plugins: " + Joiner.on(", ").join(whites)); + } + if (!blacks.isEmpty()) { + LOG.info("Exclude plugins: " + Joiner.on(", ").join(blacks)); + } + } + + @Override + public boolean apply(@Nonnull String pluginKey) { + if (CORE_PLUGIN_KEY.equals(pluginKey)) { + return !mode.isMediumTest(); + } + + if (BUILDBREAKER_PLUGIN_KEY.equals(pluginKey) && mode.isPreview()) { + LOG.info("Build Breaker plugin is no more supported in preview/incremental mode"); + return false; + } + + // FIXME what happens if there are only white-listed plugins ? + List mergeList = newArrayList(blacks); + mergeList.removeAll(whites); + return mergeList.isEmpty() || !mergeList.contains(pluginKey); + } + + Set getWhites() { + return whites; + } + + Set getBlacks() { + return blacks; + } + + static List propertyValues(Settings settings, String key, String defaultValue) { + String s = StringUtils.defaultIfEmpty(settings.getString(key), defaultValue); + return Lists.newArrayList(Splitter.on(",").trimResults().split(s)); + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java index b8a44c0d97c..b20c85114ed 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginRepository.java @@ -19,174 +19,69 @@ */ package org.sonar.batch.bootstrap; -import com.google.common.base.Joiner; -import com.google.common.base.Splitter; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import org.apache.commons.lang.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.CoreProperties; +import org.picocontainer.Startable; import org.sonar.api.Plugin; -import org.sonar.api.SonarPlugin; -import org.sonar.api.config.Settings; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; -import org.sonar.core.plugins.PluginClassloaders; -import org.sonar.core.plugins.RemotePlugin; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginLoader; +import org.sonar.core.platform.PluginRepository; -import java.io.File; -import java.text.MessageFormat; -import java.util.*; +import java.util.Collection; +import java.util.Map; -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Sets.newHashSet; +public class BatchPluginRepository implements PluginRepository, Startable { -public class BatchPluginRepository implements PluginRepository { + private final PluginInstaller installer; + private final PluginLoader loader; - private static final Logger LOG = LoggerFactory.getLogger(BatchPluginRepository.class); - private static final String CORE_PLUGIN = "core"; + private Map pluginInstancesByKeys; + private Map infosByKeys; - private PluginsRepository pluginsReferential; - private Map pluginsByKey; - private Map metadataByKey; - private Settings settings; - private PluginClassloaders classLoaders; - private final DefaultAnalysisMode analysisMode; - private final BatchPluginJarInstaller pluginInstaller; - - public BatchPluginRepository(PluginsRepository pluginsReferential, Settings settings, DefaultAnalysisMode analysisMode, - BatchPluginJarInstaller pluginInstaller) { - this.pluginsReferential = pluginsReferential; - this.settings = settings; - this.analysisMode = analysisMode; - this.pluginInstaller = pluginInstaller; + public BatchPluginRepository(PluginInstaller installer, PluginLoader loader) { + this.installer = installer; + this.loader = loader; } + @Override public void start() { - LOG.info("Install plugins"); - doStart(pluginsReferential.pluginList()); - - Map localPlugins = pluginsReferential.localPlugins(); - if (!localPlugins.isEmpty()) { - LOG.info("Install local plugins"); - for (Map.Entry pluginByMetadata : localPlugins.entrySet()) { - metadataByKey.put(pluginByMetadata.getKey().getKey(), pluginByMetadata.getKey()); - pluginsByKey.put(pluginByMetadata.getKey().getKey(), pluginByMetadata.getValue()); - } - } - } + infosByKeys = installer.installRemotes(); + pluginInstancesByKeys = loader.load(infosByKeys); - void doStart(List remotePlugins) { - PluginFilter filter = new PluginFilter(settings, analysisMode); - metadataByKey = Maps.newHashMap(); - for (RemotePlugin remote : remotePlugins) { - if (filter.accepts(remote.getKey())) { - File pluginFile = pluginsReferential.pluginFile(remote); - PluginMetadata metadata = pluginInstaller.installToCache(pluginFile, remote.isCore()); - if (StringUtils.isBlank(metadata.getBasePlugin()) || filter.accepts(metadata.getBasePlugin())) { - metadataByKey.put(metadata.getKey(), metadata); - } else { - LOG.debug("Excluded plugin: " + metadata.getKey()); - } - } + // this part is only used by tests + for (Map.Entry entry : installer.installLocals().entrySet()) { + String pluginKey = entry.getKey(); + infosByKeys.put(pluginKey, new PluginInfo(pluginKey)); + pluginInstancesByKeys.put(pluginKey, entry.getValue()); } - classLoaders = new PluginClassloaders(Thread.currentThread().getContextClassLoader()); - pluginsByKey = classLoaders.init(metadataByKey.values()); } + @Override public void stop() { - if (classLoaders != null) { - classLoaders.clean(); - classLoaders = null; - } - } + // close plugin classloaders + loader.unload(pluginInstancesByKeys.values()); - @Override - public Plugin getPlugin(String key) { - return pluginsByKey.get(key); + pluginInstancesByKeys.clear(); + infosByKeys.clear(); } @Override - public Collection getMetadata() { - return metadataByKey.values(); + public Collection getPluginInfos() { + return infosByKeys.values(); } @Override - public PluginMetadata getMetadata(String pluginKey) { - return metadataByKey.get(pluginKey); + public PluginInfo getPluginInfo(String key) { + // TODO check null result + return infosByKeys.get(key); } - public Map getPluginsByMetadata() { - Map result = Maps.newHashMap(); - for (Map.Entry entry : metadataByKey.entrySet()) { - String pluginKey = entry.getKey(); - PluginMetadata metadata = entry.getValue(); - result.put(metadata, pluginsByKey.get(pluginKey)); - } - return result; + @Override + public Plugin getPluginInstance(String key) { + // TODO check null result + return pluginInstancesByKeys.get(key); } - static class PluginFilter { - private static final String BUILDBREAKER_PLUGIN_KEY = "buildbreaker"; - private static final String PROPERTY_IS_DEPRECATED_MSG = "Property {0} is deprecated. Please use {1} instead."; - Set whites = newHashSet(), blacks = newHashSet(); - private DefaultAnalysisMode mode; - - PluginFilter(Settings settings, DefaultAnalysisMode mode) { - this.mode = mode; - if (settings.hasKey(CoreProperties.BATCH_INCLUDE_PLUGINS)) { - whites.addAll(Arrays.asList(settings.getStringArray(CoreProperties.BATCH_INCLUDE_PLUGINS))); - } - if (settings.hasKey(CoreProperties.BATCH_EXCLUDE_PLUGINS)) { - blacks.addAll(Arrays.asList(settings.getStringArray(CoreProperties.BATCH_EXCLUDE_PLUGINS))); - } - if (mode.isPreview()) { - // These default values are not supported by Settings because the class CorePlugin - // is not loaded yet. - if (settings.hasKey(CoreProperties.DRY_RUN_INCLUDE_PLUGINS)) { - LOG.warn(MessageFormat.format(PROPERTY_IS_DEPRECATED_MSG, CoreProperties.DRY_RUN_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS)); - whites.addAll(propertyValues(settings, - CoreProperties.DRY_RUN_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS_DEFAULT_VALUE)); - } else { - whites.addAll(propertyValues(settings, - CoreProperties.PREVIEW_INCLUDE_PLUGINS, CoreProperties.PREVIEW_INCLUDE_PLUGINS_DEFAULT_VALUE)); - } - if (settings.hasKey(CoreProperties.DRY_RUN_EXCLUDE_PLUGINS)) { - LOG.warn(MessageFormat.format(PROPERTY_IS_DEPRECATED_MSG, CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS)); - blacks.addAll(propertyValues(settings, - CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS_DEFAULT_VALUE)); - } else { - blacks.addAll(propertyValues(settings, - CoreProperties.PREVIEW_EXCLUDE_PLUGINS, CoreProperties.PREVIEW_EXCLUDE_PLUGINS_DEFAULT_VALUE)); - } - } - if (!whites.isEmpty()) { - LOG.info("Include plugins: " + Joiner.on(", ").join(whites)); - } - if (!blacks.isEmpty()) { - LOG.info("Exclude plugins: " + Joiner.on(", ").join(blacks)); - } - } - - static List propertyValues(Settings settings, String key, String defaultValue) { - String s = StringUtils.defaultIfEmpty(settings.getString(key), defaultValue); - return Lists.newArrayList(Splitter.on(",").trimResults().split(s)); - } - - boolean accepts(String pluginKey) { - if (CORE_PLUGIN.equals(pluginKey)) { - return !mode.isMediumTest(); - } - - if (BUILDBREAKER_PLUGIN_KEY.equals(pluginKey) && mode.isPreview()) { - LOG.info("Build Breaker plugin is no more supported in preview/incremental mode"); - return false; - } - - List mergeList = newArrayList(blacks); - mergeList.removeAll(whites); - return mergeList.isEmpty() || !mergeList.contains(pluginKey); - } + @Override + public boolean hasPlugin(String key) { + return infosByKeys.containsKey(key); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginUnzipper.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginUnzipper.java new file mode 100644 index 00000000000..29f554ddc89 --- /dev/null +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginUnzipper.java @@ -0,0 +1,77 @@ +/* + * 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.batch.bootstrap; + +import org.apache.commons.io.FileUtils; +import org.sonar.api.BatchComponent; +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.home.cache.FileCache; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; + +public class BatchPluginUnzipper extends PluginUnzipper implements BatchComponent { + + private final FileCache fileCache; + + public BatchPluginUnzipper(FileCache fileCache) { + this.fileCache = fileCache; + } + + @Override + public UnzippedPlugin unzip(PluginInfo info) { + try { + File dir = unzipFile(info.getFile()); + return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), dir); + } catch (Exception e) { + throw new IllegalStateException(String.format("Fail to open plugin [%s]: %s", info.getKey(), info.getFile().getAbsolutePath()), e); + } + } + + private File unzipFile(File cachedFile) throws IOException { + String filename = cachedFile.getName(); + File destDir = new File(cachedFile.getParentFile(), filename + "_unzip"); + File lockFile = new File(cachedFile.getParentFile(), filename + "_unzip.lock"); + if (!destDir.exists()) { + FileOutputStream out = new FileOutputStream(lockFile); + try { + java.nio.channels.FileLock lock = out.getChannel().lock(); + try { + // Recheck in case of concurrent processes + if (!destDir.exists()) { + File tempDir = fileCache.createTempDir(); + ZipUtils.unzip(cachedFile, tempDir, newLibFilter()); + FileUtils.moveDirectory(tempDir, destDir); + } + } finally { + lock.release(); + } + } finally { + out.close(); + FileUtils.deleteQuietly(lockFile); + } + } + return destDir; + } +} diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java index 86599f96774..165aa83e649 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/ExtensionInstaller.java @@ -21,22 +21,22 @@ package org.sonar.batch.bootstrap; import org.sonar.api.ExtensionProvider; import org.sonar.api.Plugin; -import org.sonar.api.platform.ComponentContainer; -import org.sonar.api.platform.PluginMetadata; import org.sonar.batch.bootstrapper.EnvironmentInformation; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import javax.annotation.Nullable; import java.util.List; -import java.util.Map; public class ExtensionInstaller { - private final BatchPluginRepository pluginRepository; + private final PluginRepository pluginRepository; private final EnvironmentInformation env; private final DefaultAnalysisMode analysisMode; - public ExtensionInstaller(BatchPluginRepository pluginRepository, EnvironmentInformation env, DefaultAnalysisMode analysisMode) { + public ExtensionInstaller(PluginRepository pluginRepository, EnvironmentInformation env, DefaultAnalysisMode analysisMode) { this.pluginRepository = pluginRepository; this.env = env; this.analysisMode = analysisMode; @@ -50,11 +50,10 @@ public class ExtensionInstaller { } // plugin extensions - for (Map.Entry entry : pluginRepository.getPluginsByMetadata().entrySet()) { - PluginMetadata metadata = entry.getKey(); - Plugin plugin = entry.getValue(); + for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) { + Plugin plugin = pluginRepository.getPluginInstance(pluginInfo.getKey()); for (Object extension : plugin.getExtensions()) { - doInstall(container, matcher, metadata, extension); + doInstall(container, matcher, pluginInfo, extension); } } List providers = container.getComponentsByType(ExtensionProvider.class); @@ -71,13 +70,13 @@ public class ExtensionInstaller { return this; } - private void doInstall(ComponentContainer container, ExtensionMatcher matcher, @Nullable PluginMetadata metadata, Object extension) { + private void doInstall(ComponentContainer container, ExtensionMatcher matcher, @Nullable PluginInfo pluginInfo, Object extension) { if (ExtensionUtils.supportsEnvironment(extension, env) && (analysisMode.isDb() || !ExtensionUtils.requiresDB(extension)) && matcher.accept(extension)) { - container.addExtension(metadata, extension); + container.addExtension(pluginInfo, extension); } else { - container.declareExtension(metadata, extension); + container.declareExtension(pluginInfo, extension); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java index 7aaf0f6be32..09d217122af 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/GlobalContainer.java @@ -21,28 +21,44 @@ package org.sonar.batch.bootstrap; import org.sonar.api.Plugin; import org.sonar.api.config.EmailSettings; -import org.sonar.api.platform.ComponentContainer; -import org.sonar.api.platform.PluginMetadata; import org.sonar.api.utils.Durations; -import org.sonar.core.util.DefaultHttpDownloader; import org.sonar.api.utils.System2; import org.sonar.api.utils.UriReader; import org.sonar.api.utils.internal.TempFolderCleaner; import org.sonar.batch.components.PastSnapshotFinder; -import org.sonar.batch.deprecated.components.*; +import org.sonar.batch.deprecated.components.PastSnapshotFinderByDate; +import org.sonar.batch.deprecated.components.PastSnapshotFinderByDays; +import org.sonar.batch.deprecated.components.PastSnapshotFinderByPreviousAnalysis; +import org.sonar.batch.deprecated.components.PastSnapshotFinderByPreviousVersion; +import org.sonar.batch.deprecated.components.PastSnapshotFinderByVersion; import org.sonar.batch.issue.tracking.DefaultServerLineHashesLoader; import org.sonar.batch.issue.tracking.ServerLineHashesLoader; import org.sonar.batch.platform.DefaultServer; -import org.sonar.batch.repository.*; +import org.sonar.batch.repository.DefaultGlobalRepositoriesLoader; +import org.sonar.batch.repository.DefaultProjectRepositoriesLoader; +import org.sonar.batch.repository.DefaultServerIssuesLoader; +import org.sonar.batch.repository.GlobalRepositoriesLoader; +import org.sonar.batch.repository.GlobalRepositoriesProvider; +import org.sonar.batch.repository.ProjectRepositoriesLoader; +import org.sonar.batch.repository.ServerIssuesLoader; import org.sonar.batch.repository.user.UserRepository; import org.sonar.core.cluster.NullQueue; import org.sonar.core.config.Logback; import org.sonar.core.i18n.DefaultI18n; import org.sonar.core.i18n.RuleI18nManager; -import org.sonar.core.persistence.*; +import org.sonar.core.persistence.DaoUtils; +import org.sonar.core.persistence.DatabaseVersion; +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.PluginInfo; +import org.sonar.core.platform.PluginLoader; +import org.sonar.core.platform.PluginRepository; import org.sonar.core.purge.PurgeProfiler; import org.sonar.core.rule.CacheRuleFinder; import org.sonar.core.user.HibernateUserFinder; +import org.sonar.core.util.DefaultHttpDownloader; import org.sonar.jpa.dao.MeasuresDao; import org.sonar.jpa.session.DefaultDatabaseConnector; import org.sonar.jpa.session.JpaDatabaseSession; @@ -79,11 +95,15 @@ public class GlobalContainer extends ComponentContainer { private void addBootstrapComponents() { add( + // plugins BatchPluginRepository.class, - BatchPluginJarInstaller.class, + PluginLoader.class, + BatchPluginUnzipper.class, + BatchPluginPredicate.class, + ExtensionInstaller.class, + GlobalSettings.class, ServerClient.class, - ExtensionInstaller.class, Logback.class, DefaultServer.class, new TempFolderProvider(), @@ -95,20 +115,16 @@ public class GlobalContainer extends ComponentContainer { DefaultI18n.class, new GlobalRepositoriesProvider(), UserRepository.class); - if (getComponentByType(PluginsRepository.class) == null) { - add(DefaultPluginsRepository.class); - } - if (getComponentByType(GlobalRepositoriesLoader.class) == null) { - add(DefaultGlobalRepositoriesLoader.class); - } - if (getComponentByType(ProjectRepositoriesLoader.class) == null) { - add(DefaultProjectRepositoriesLoader.class); - } - if (getComponentByType(ServerIssuesLoader.class) == null) { - add(DefaultServerIssuesLoader.class); - } - if (getComponentByType(ServerLineHashesLoader.class) == null) { - add(DefaultServerLineHashesLoader.class); + addIfMissing(BatchPluginInstaller.class, PluginInstaller.class); + addIfMissing(DefaultGlobalRepositoriesLoader.class, GlobalRepositoriesLoader.class); + addIfMissing(DefaultProjectRepositoriesLoader.class, ProjectRepositoriesLoader.class); + addIfMissing(DefaultServerIssuesLoader.class, ServerIssuesLoader.class); + addIfMissing(DefaultServerLineHashesLoader.class, ServerLineHashesLoader.class); + } + + public void addIfMissing(Object object, Class objectType) { + if (getComponentByType(objectType) == null) { + add(object); } } @@ -147,10 +163,10 @@ public class GlobalContainer extends ComponentContainer { } private void installPlugins() { - for (Map.Entry entry : getComponentByType(BatchPluginRepository.class).getPluginsByMetadata().entrySet()) { - PluginMetadata metadata = entry.getKey(); - Plugin plugin = entry.getValue(); - addExtension(metadata, plugin); + PluginRepository pluginRepository = getComponentByType(PluginRepository.class); + for (PluginInfo pluginInfo : pluginRepository.getPluginInfos()) { + Plugin instance = pluginRepository.getPluginInstance(pluginInfo.getKey()); + addExtension(pluginInfo, instance); } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginsRepository.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java similarity index 63% rename from sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginsRepository.java rename to sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java index 58473fd6af1..97eb513d4a6 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginsRepository.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/PluginInstaller.java @@ -19,33 +19,24 @@ */ package org.sonar.batch.bootstrap; -import org.sonar.api.SonarPlugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.core.plugins.RemotePlugin; +import org.sonar.api.BatchComponent; +import org.sonar.api.Plugin; +import org.sonar.core.platform.PluginInfo; -import java.io.File; -import java.util.List; import java.util.Map; -/** - * Plugin referential. - * @since 4.4 - */ -public interface PluginsRepository { +public interface PluginInstaller extends BatchComponent { /** - * Return list of remote plugins to be installed + * Gets the list of plugins installed on server and downloads them if not + * already in local cache. + * @return information about all installed plugins, grouped by key */ - List pluginList(); + Map installRemotes(); /** - * Return location of a given plugin on the local FS. + * Used only by tests. + * @see org.sonar.batch.mediumtest.BatchMediumTester */ - File pluginFile(RemotePlugin remote); - - /** - * Return the list of local plugins to be installed - */ - Map localPlugins(); - + Map installLocals(); } diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java index b3fd7e46392..fce09a6cd1c 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/TaskContainer.java @@ -23,7 +23,7 @@ import org.sonar.batch.components.PastMeasuresLoader; import org.apache.commons.lang.StringUtils; import org.sonar.api.CoreProperties; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.resources.ResourceTypes; import org.sonar.api.task.Task; import org.sonar.api.task.TaskComponent; diff --git a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java index e274375076b..09871955a38 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java +++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/BatchMediumTester.java @@ -28,27 +28,31 @@ import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.batch.debt.internal.DefaultDebtModel; import org.sonar.api.measures.CoreMetrics; import org.sonar.api.measures.Metric; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.batch.bootstrap.PluginsRepository; import org.sonar.batch.bootstrap.TaskProperties; import org.sonar.batch.bootstrapper.Batch; import org.sonar.batch.bootstrapper.EnvironmentInformation; import org.sonar.batch.issue.tracking.ServerLineHashesLoader; -import org.sonar.batch.protocol.input.*; +import org.sonar.batch.protocol.input.ActiveRule; import org.sonar.batch.protocol.input.BatchInput.ServerIssue; +import org.sonar.batch.protocol.input.FileData; +import org.sonar.batch.protocol.input.GlobalRepositories; +import org.sonar.batch.protocol.input.ProjectRepositories; import org.sonar.batch.report.ReportPublisher; import org.sonar.batch.repository.GlobalRepositoriesLoader; import org.sonar.batch.repository.ProjectRepositoriesLoader; import org.sonar.batch.repository.ServerIssuesLoader; import org.sonar.core.component.ComponentKeys; -import org.sonar.core.plugins.DefaultPluginMetadata; -import org.sonar.core.plugins.RemotePlugin; import java.io.File; import java.io.FileInputStream; import java.io.InputStreamReader; import java.io.Reader; -import java.util.*; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; /** * Main utility class for writing batch medium tests. @@ -70,22 +74,22 @@ public class BatchMediumTester { public static class BatchMediumTesterBuilder { private final FakeGlobalRepositoriesLoader globalRefProvider = new FakeGlobalRepositoriesLoader(); private final FakeProjectRepositoriesLoader projectRefProvider = new FakeProjectRepositoriesLoader(); - private final FakePluginsRepository pluginsReferential = new FakePluginsRepository(); + private final FakePluginInstaller pluginInstaller = new FakePluginInstaller(); private final FakeServerIssuesLoader serverIssues = new FakeServerIssuesLoader(); private final FakeServerLineHashesLoader serverLineHashes = new FakeServerLineHashesLoader(); - private final Map bootstrapProperties = new HashMap(); + private final Map bootstrapProperties = new HashMap<>(); public BatchMediumTester build() { return new BatchMediumTester(this); } public BatchMediumTesterBuilder registerPlugin(String pluginKey, File location) { - pluginsReferential.addPlugin(pluginKey, location); + pluginInstaller.add(pluginKey, location); return this; } public BatchMediumTesterBuilder registerPlugin(String pluginKey, SonarPlugin instance) { - pluginsReferential.addPlugin(pluginKey, instance); + pluginInstaller.add(pluginKey, instance); return this; } @@ -164,7 +168,7 @@ public class BatchMediumTester { .setEnableLoggingConfiguration(true) .addComponents( new EnvironmentInformation("mediumTest", "1.0"), - builder.pluginsReferential, + builder.pluginInstaller, builder.globalRefProvider, builder.projectRefProvider, builder.serverIssues, @@ -280,41 +284,6 @@ public class BatchMediumTester { } - private static class FakePluginsRepository implements PluginsRepository { - - private List pluginList = new ArrayList(); - private Map pluginFiles = new HashMap(); - Map localPlugins = new HashMap(); - - @Override - public List pluginList() { - return pluginList; - } - - @Override - public File pluginFile(RemotePlugin remote) { - return pluginFiles.get(remote); - } - - public FakePluginsRepository addPlugin(String pluginKey, File location) { - RemotePlugin plugin = new RemotePlugin(pluginKey, false); - pluginList.add(plugin); - pluginFiles.put(plugin, location); - return this; - } - - public FakePluginsRepository addPlugin(String pluginKey, SonarPlugin pluginInstance) { - localPlugins.put(DefaultPluginMetadata.create(pluginKey), pluginInstance); - return this; - } - - @Override - public Map localPlugins() { - return localPlugins; - } - - } - private static class FakeServerIssuesLoader implements ServerIssuesLoader { private List serverIssues = new ArrayList<>(); diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarInstaller.java b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java similarity index 52% rename from sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarInstaller.java rename to sonar-batch/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java index 8866cf7fc0f..cbd837c66c9 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BatchPluginJarInstaller.java +++ b/sonar-batch/src/main/java/org/sonar/batch/mediumtest/FakePluginInstaller.java @@ -17,33 +17,38 @@ * 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.batch.bootstrap; +package org.sonar.batch.mediumtest; -import org.sonar.api.BatchComponent; -import org.sonar.core.plugins.DefaultPluginMetadata; -import org.sonar.core.plugins.PluginJarInstaller; -import org.sonar.home.cache.FileCache; +import org.sonar.api.Plugin; +import org.sonar.batch.bootstrap.PluginInstaller; +import org.sonar.core.platform.PluginInfo; import java.io.File; -import java.io.IOException; +import java.util.HashMap; +import java.util.Map; -public class BatchPluginJarInstaller extends PluginJarInstaller implements BatchComponent { +public class FakePluginInstaller implements PluginInstaller { - private FileCache cache; + private final Map infosByKeys = new HashMap<>(); + private final Map instancesByKeys = new HashMap<>(); - public BatchPluginJarInstaller(FileCache cache) { - this.cache = cache; + public FakePluginInstaller add(String pluginKey, File jarFile) { + infosByKeys.put(pluginKey, PluginInfo.create(jarFile)); + return this; } - public DefaultPluginMetadata installToCache(File pluginFile, boolean isCore) { - DefaultPluginMetadata metadata = extractMetadata(pluginFile, isCore); - install(metadata, null, pluginFile); - return metadata; + public FakePluginInstaller add(String pluginKey, Plugin instance) { + instancesByKeys.put(pluginKey, instance); + return this; } @Override - protected File extractPluginDependencies(File pluginFile, File pluginBasedir) throws IOException { - return cache.unzip(pluginFile); + public Map installRemotes() { + return infosByKeys; } + @Override + public Map installLocals() { + return instancesByKeys; + } } diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java index 75281a9e961..f8312429543 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ModuleScanContainer.java @@ -27,7 +27,7 @@ import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.fs.internal.FileMetadata; import org.sonar.api.batch.rule.CheckFactory; import org.sonar.api.checks.NoSonarFilter; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.resources.Project; import org.sonar.api.scan.filesystem.FileExclusions; import org.sonar.batch.ProjectTree; diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java index ebf3d5c2d87..12a23bf62e4 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ProjectScanContainer.java @@ -26,7 +26,7 @@ import org.sonar.api.batch.InstantiationStrategy; import org.sonar.api.batch.bootstrap.ProjectBootstrapper; import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.config.Settings; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.resources.Languages; import org.sonar.api.resources.Project; import org.sonar.api.scan.filesystem.PathResolver; diff --git a/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java b/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java index 0064d028ea8..bb3f0f8a021 100644 --- a/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java +++ b/sonar-batch/src/main/java/org/sonar/batch/scan/ScanTask.java @@ -20,7 +20,7 @@ package org.sonar.batch.scan; import org.sonar.api.CoreProperties; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.task.Task; import org.sonar.api.task.TaskDefinition; import org.sonar.batch.DefaultProjectTree; diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchExtensionDictionnaryTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchExtensionDictionnaryTest.java index 14fde3895ef..6f7e0c11e32 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchExtensionDictionnaryTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchExtensionDictionnaryTest.java @@ -24,7 +24,7 @@ import org.junit.Test; import org.sonar.api.BatchExtension; import org.sonar.api.batch.*; import org.sonar.api.batch.postjob.PostJobContext; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.resources.Project; import org.sonar.batch.postjob.PostJobOptimizer; import org.sonar.batch.sensor.DefaultSensorContext; diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DefaultPluginsRepositoryTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java similarity index 66% rename from sonar-batch/src/test/java/org/sonar/batch/bootstrap/DefaultPluginsRepositoryTest.java rename to sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java index 57e940ab7ae..03c3707919b 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/DefaultPluginsRepositoryTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginInstallerTest.java @@ -36,7 +36,7 @@ import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -public class DefaultPluginsRepositoryTest { +public class BatchPluginInstallerTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); @@ -44,34 +44,30 @@ public class DefaultPluginsRepositoryTest { @Rule public ExpectedException thrown = ExpectedException.none(); + FileCache fileCache = mock(FileCache.class); + BatchPluginPredicate pluginPredicate = mock(BatchPluginPredicate.class); + @Test - public void should_request_list_of_plugins() { - FileCache cache = mock(FileCache.class); + public void listRemotePlugins() { + ServerClient server = mock(ServerClient.class); - when(server.request("/deploy/plugins/index.txt")).thenReturn("checkstyle,true\nsqale,false"); - DefaultPluginsRepository downloader = new DefaultPluginsRepository(cache, server); - - List plugins = downloader.pluginList(); - assertThat(plugins).hasSize(2); - assertThat(plugins.get(0).getKey()).isEqualTo("checkstyle"); - assertThat(plugins.get(0).isCore()).isTrue(); - assertThat(plugins.get(1).getKey()).isEqualTo("sqale"); - assertThat(plugins.get(1).isCore()).isFalse(); + when(server.request("/deploy/plugins/index.txt")).thenReturn("checkstyle,false\nsqale,false"); + BatchPluginInstaller installer = new BatchPluginInstaller(server, fileCache, pluginPredicate); + + List remotePlugins = installer.listRemotePlugins(); + assertThat(remotePlugins).extracting("key").containsOnly("checkstyle", "sqale"); } @Test public void should_download_plugin() throws Exception { - FileCache cache = mock(FileCache.class); - File pluginJar = temp.newFile(); - when(cache.get(eq("checkstyle-plugin.jar"), eq("fakemd5_1"), any(FileCache.Downloader.class))).thenReturn(pluginJar); + when(fileCache.get(eq("checkstyle-plugin.jar"), eq("fakemd5_1"), any(FileCache.Downloader.class))).thenReturn(pluginJar); ServerClient server = mock(ServerClient.class); - DefaultPluginsRepository downloader = new DefaultPluginsRepository(cache, server); + BatchPluginInstaller installer = new BatchPluginInstaller(server, fileCache, pluginPredicate); - RemotePlugin plugin = new RemotePlugin("checkstyle", true) - .setFile("checkstyle-plugin.jar", "fakemd5_1"); - File file = downloader.pluginFile(plugin); + RemotePlugin remote = new RemotePlugin("checkstyle", true).setFile("checkstyle-plugin.jar", "fakemd5_1"); + File file = installer.download(remote); assertThat(file).isEqualTo(pluginJar); } @@ -83,6 +79,6 @@ public class DefaultPluginsRepositoryTest { ServerClient server = mock(ServerClient.class); doThrow(new IllegalStateException()).when(server).request("/deploy/plugins/index.txt"); - new DefaultPluginsRepository(mock(FileCache.class), server).pluginList(); + new BatchPluginInstaller(server, fileCache, pluginPredicate).installRemotes(); } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java new file mode 100644 index 00000000000..9dcebd6d07f --- /dev/null +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginPredicateTest.java @@ -0,0 +1,157 @@ +/* + * 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.batch.bootstrap; + +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.CoreProperties; +import org.sonar.api.config.Settings; +import org.sonar.home.cache.FileCache; +import org.sonar.home.cache.FileCacheBuilder; + +import java.io.File; +import java.io.IOException; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class BatchPluginPredicateTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + DefaultAnalysisMode mode = mock(DefaultAnalysisMode.class); + FileCache cache; + File userHome; + + @Before + public void before() throws IOException { + userHome = temp.newFolder(); + cache = new FileCacheBuilder().setUserHome(userHome).build(); + } + + @Test + public void shouldAlwaysAcceptIfNoWhiteListAndBlackList() { + BatchPluginPredicate predicate = new BatchPluginPredicate(new Settings(), mode); + assertThat(predicate.apply("pmd")).isTrue(); + assertThat(predicate.apply("buildbreaker")).isTrue(); + } + + @Test + public void shouldBlackListBuildBreakerInPreviewMode() { + when(mode.isPreview()).thenReturn(true); + BatchPluginPredicate predicate = new BatchPluginPredicate(new Settings(), mode); + assertThat(predicate.apply("buildbreaker")).isFalse(); + } + + @Test + public void whiteListShouldTakePrecedenceOverBlackList() { + Settings settings = new Settings() + .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs") + .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura,pmd"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("pmd")).isTrue(); + } + + @Test + public void corePluginShouldAlwaysBeInWhiteList() { + Settings settings = new Settings() + .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("core")).isTrue(); + } + + @Test + public void corePluginShouldNeverBeInBlackList() { + Settings settings = new Settings() + .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "core,findbugs"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("core")).isTrue(); + } + + @Test + public void check_white_list_with_black_list() { + Settings settings = new Settings() + .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs") + .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("checkstyle")).isTrue(); + assertThat(predicate.apply("pmd")).isTrue(); + assertThat(predicate.apply("cobertura")).isFalse(); + } + + @Test + public void check_white_list_when_plugin_is_in_both_list() { + Settings settings = new Settings() + .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "cobertura,checkstyle,pmd,findbugs") + .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("checkstyle")).isTrue(); + assertThat(predicate.apply("pmd")).isTrue(); + assertThat(predicate.apply("cobertura")).isTrue(); + } + + @Test + public void check_black_list_if_no_white_list() { + Settings settings = new Settings() + .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd,findbugs"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("checkstyle")).isFalse(); + assertThat(predicate.apply("pmd")).isFalse(); + assertThat(predicate.apply("cobertura")).isTrue(); + } + + @Test + public void should_concatenate_preview_predicates() { + Settings settings = new Settings() + .setProperty(CoreProperties.PREVIEW_INCLUDE_PLUGINS, "cockpit") + .setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "views") + .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd"); + when(mode.isPreview()).thenReturn(true); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.getWhites()).containsOnly("cockpit"); + assertThat(predicate.getBlacks()).containsOnly("views", "checkstyle", "pmd"); + } + + @Test + public void should_concatenate_deprecated_dry_run_predicates() { + Settings settings = new Settings() + .setProperty(CoreProperties.DRY_RUN_INCLUDE_PLUGINS, "cockpit") + .setProperty(CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, "views") + .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd"); + when(mode.isPreview()).thenReturn(true); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.getWhites()).containsOnly("cockpit"); + assertThat(predicate.getBlacks()).containsOnly("views", "checkstyle", "pmd"); + } + + @Test + public void inclusions_and_exclusions_should_be_trimmed() { + Settings settings = new Settings() + .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle, pmd, findbugs") + .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura, pmd"); + BatchPluginPredicate predicate = new BatchPluginPredicate(settings, mode); + assertThat(predicate.apply("pmd")).isTrue(); + } + +} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java index 66a499b01c0..7c82edbb64f 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginRepositoryTest.java @@ -17,219 +17,237 @@ * 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.batch.bootstrap; - -import com.google.common.io.Resources; -import org.apache.commons.io.FileUtils; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; -import org.sonar.api.CoreProperties; -import org.sonar.api.config.Settings; -import org.sonar.core.plugins.RemotePlugin; -import org.sonar.home.cache.FileCache; -import org.sonar.home.cache.FileCacheBuilder; - -import java.io.File; -import java.io.IOException; -import java.util.Arrays; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class BatchPluginRepositoryTest { - - @Rule - public TemporaryFolder temp = new TemporaryFolder(); - - private BatchPluginRepository repository; - private DefaultAnalysisMode mode; - private FileCache cache; - private File userHome; - - @Before - public void before() throws IOException { - mode = mock(DefaultAnalysisMode.class); - when(mode.isPreview()).thenReturn(false); - userHome = temp.newFolder(); - cache = new FileCacheBuilder().setUserHome(userHome).build(); - } - - @After - public void tearDown() { - if (repository != null) { - repository.stop(); - } - } - - @Test - public void shouldLoadPlugin() throws Exception { - RemotePlugin checkstyle = new RemotePlugin("checkstyle", true); - - DefaultPluginsRepository downloader = mock(DefaultPluginsRepository.class); - when(downloader.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar")); - - repository = new BatchPluginRepository(downloader, new Settings(), mode, new BatchPluginJarInstaller(cache)); - - repository.doStart(Arrays.asList(checkstyle)); - - assertThat(repository.getPlugin("checkstyle")).isNotNull(); - assertThat(repository.getMetadata()).hasSize(1); - assertThat(repository.getMetadata("checkstyle").getName()).isEqualTo("Checkstyle"); - assertThat(repository.getMetadata("checkstyle").getDeployedFiles()).hasSize(4); // plugin + 3 dependencies - } - - @Test - public void shouldLoadPluginExtension() throws Exception { - RemotePlugin checkstyle = new RemotePlugin("checkstyle", true); - RemotePlugin checkstyleExt = new RemotePlugin("checkstyleextensions", false); - - DefaultPluginsRepository downloader = mock(DefaultPluginsRepository.class); - when(downloader.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar")); - when(downloader.pluginFile(checkstyleExt)).thenReturn(fileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar")); - - repository = new BatchPluginRepository(downloader, new Settings(), mode, new BatchPluginJarInstaller(cache)); - - repository.doStart(Arrays.asList(checkstyle, checkstyleExt)); - - assertThat(repository.getPlugin("checkstyle")).isNotNull(); - assertThat(repository.getPlugin("checkstyleextensions")).isNotNull(); - assertThat(repository.getMetadata()).hasSize(2); - assertThat(repository.getMetadata("checkstyle").getName()).isEqualTo("Checkstyle"); - assertThat(repository.getMetadata("checkstyleextensions").getVersion()).isEqualTo("0.1-SNAPSHOT"); - } - - @Test - public void shouldExcludePluginAndItsExtensions() throws Exception { - RemotePlugin checkstyle = new RemotePlugin("checkstyle", true); - RemotePlugin checkstyleExt = new RemotePlugin("checkstyleextensions", false); - - DefaultPluginsRepository downloader = mock(DefaultPluginsRepository.class); - when(downloader.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar")); - when(downloader.pluginFile(checkstyleExt)).thenReturn(fileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar")); - - Settings settings = new Settings(); - settings.setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle"); - repository = new BatchPluginRepository(downloader, settings, mode, new BatchPluginJarInstaller(cache)); - - repository.doStart(Arrays.asList(checkstyle, checkstyleExt)); - - assertThat(repository.getMetadata()).isEmpty(); - } - - private File fileFromCache(String filename) throws Exception { - File file = new File(Resources.getResource("org/sonar/batch/bootstrap/BatchPluginRepositoryTest/" + filename).toURI()); - File destDir = new File(userHome, "cache/foomd5"); - FileUtils.forceMkdir(destDir); - FileUtils.copyFileToDirectory(file, destDir); - return new File(destDir, filename); - } - - @Test - public void shouldAlwaysAcceptIfNoWhiteListAndBlackList() { - BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(new Settings(), mode); - assertThat(filter.accepts("pmd")).isTrue(); - assertThat(filter.accepts("buildbreaker")).isTrue(); - } - - @Test - public void shouldBlackListBuildBreakerInPreviewMode() { - when(mode.isPreview()).thenReturn(true); - BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(new Settings(), mode); - assertThat(filter.accepts("buildbreaker")).isFalse(); - } - - @Test - public void whiteListShouldTakePrecedenceOverBlackList() { - Settings settings = new Settings() - .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs") - .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura,pmd"); - BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); - assertThat(filter.accepts("pmd")).isTrue(); - } - - @Test - public void corePluginShouldAlwaysBeInWhiteList() { - Settings settings = new Settings() - .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs"); - BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); - assertThat(filter.accepts("core")).isTrue(); - } - - @Test - public void corePluginShouldNeverBeInBlackList() { - Settings settings = new Settings() - .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "core,findbugs"); - BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); - assertThat(filter.accepts("core")).isTrue(); - } - - @Test - public void check_white_list_with_black_list() { - Settings settings = new Settings() - .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs") - .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura"); - BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); - assertThat(filter.accepts("checkstyle")).isTrue(); - assertThat(filter.accepts("pmd")).isTrue(); - assertThat(filter.accepts("cobertura")).isFalse(); - } - - @Test - public void check_white_list_when_plugin_is_in_both_list() { - Settings settings = new Settings() - .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "cobertura,checkstyle,pmd,findbugs") - .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura"); - BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); - assertThat(filter.accepts("checkstyle")).isTrue(); - assertThat(filter.accepts("pmd")).isTrue(); - assertThat(filter.accepts("cobertura")).isTrue(); - } - - @Test - public void check_black_list_if_no_white_list() { - Settings settings = new Settings() - .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd,findbugs"); - BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); - assertThat(filter.accepts("checkstyle")).isFalse(); - assertThat(filter.accepts("pmd")).isFalse(); - assertThat(filter.accepts("cobertura")).isTrue(); - } - - @Test - public void should_concatenate_preview_filters() { - Settings settings = new Settings() - .setProperty(CoreProperties.PREVIEW_INCLUDE_PLUGINS, "cockpit") - .setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "views") - .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd"); - when(mode.isPreview()).thenReturn(true); - BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); - assertThat(filter.whites).containsOnly("cockpit"); - assertThat(filter.blacks).containsOnly("views", "checkstyle", "pmd"); - } - - @Test - public void should_concatenate_deprecated_dry_run_filters() { - Settings settings = new Settings() - .setProperty(CoreProperties.DRY_RUN_INCLUDE_PLUGINS, "cockpit") - .setProperty(CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, "views") - .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd"); - when(mode.isPreview()).thenReturn(true); - BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); - assertThat(filter.whites).containsOnly("cockpit"); - assertThat(filter.blacks).containsOnly("views", "checkstyle", "pmd"); - } - - @Test - public void inclusions_and_exclusions_should_be_trimmed() { - Settings settings = new Settings() - .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle, pmd, findbugs") - .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura, pmd"); - BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); - assertThat(filter.accepts("pmd")).isTrue(); - } - -} +///* +// * 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.batch.bootstrap; +// +//import com.google.common.io.Resources; +//import org.apache.commons.io.FileUtils; +//import org.junit.After; +//import org.junit.Before; +//import org.junit.Rule; +//import org.junit.Test; +//import org.junit.rules.TemporaryFolder; +//import org.sonar.api.CoreProperties; +//import org.sonar.api.config.Settings; +//import org.sonar.core.plugins.RemotePlugin; +//import org.sonar.home.cache.FileCache; +//import org.sonar.home.cache.FileCacheBuilder; +// +//import java.io.File; +//import java.io.IOException; +//import java.util.Arrays; +// +//import static org.mockito.Mockito.mock; +//import static org.mockito.Mockito.when; +// +//public class BatchPluginRepositoryTest { +// +// @Rule +// public TemporaryFolder temp = new TemporaryFolder(); +// +// private BatchPluginRepository repository; +// private DefaultAnalysisMode mode; +// private FileCache cache; +// private File userHome; +// +// @Before +// public void before() throws IOException { +// mode = mock(DefaultAnalysisMode.class); +// when(mode.isPreview()).thenReturn(false); +// userHome = temp.newFolder(); +// cache = new FileCacheBuilder().setUserHome(userHome).build(); +// } +// +// @After +// public void tearDown() { +// if (repository != null) { +// repository.stop(); +// } +// } +// +// @Test +// public void shouldLoadPlugin() throws Exception { +// RemotePlugin checkstyle = new RemotePlugin("checkstyle", true); +// +// DefaultPluginRepository installer = mock(DefaultPluginsRepository.class); +// when(installer.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar")); +// +// repository = new BatchPluginRepository(installer, new Settings(), mode, new BatchPluginJarInstaller(cache)); +// +// repository.doStart(Arrays.asList(checkstyle)); +// +// assertThat(repository.getPlugin("checkstyle")).isNotNull(); +// assertThat(repository.getMetadata()).hasSize(1); +// assertThat(repository.getMetadata("checkstyle").getName()).isEqualTo("Checkstyle"); +// assertThat(repository.getMetadata("checkstyle").getDeployedFiles()).hasSize(4); // plugin + 3 dependencies +// } +// +// @Test +// public void shouldLoadPluginExtension() throws Exception { +// RemotePlugin checkstyle = new RemotePlugin("checkstyle", true); +// RemotePlugin checkstyleExt = new RemotePlugin("checkstyleextensions", false); +// +// DefaultPluginsRepository downloader = mock(DefaultPluginsRepository.class); +// when(downloader.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar")); +// when(downloader.pluginFile(checkstyleExt)).thenReturn(fileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar")); +// +// repository = new BatchPluginRepository(downloader, new Settings(), mode, new BatchPluginJarInstaller(cache)); +// +// repository.doStart(Arrays.asList(checkstyle, checkstyleExt)); +// +// assertThat(repository.getPlugin("checkstyle")).isNotNull(); +// assertThat(repository.getPlugin("checkstyleextensions")).isNotNull(); +// assertThat(repository.getMetadata()).hasSize(2); +// assertThat(repository.getMetadata("checkstyle").getName()).isEqualTo("Checkstyle"); +// assertThat(repository.getMetadata("checkstyleextensions").getVersion()).isEqualTo("0.1-SNAPSHOT"); +// } +// +// @Test +// public void shouldExcludePluginAndItsExtensions() throws Exception { +// RemotePlugin checkstyle = new RemotePlugin("checkstyle", true); +// RemotePlugin checkstyleExt = new RemotePlugin("checkstyleextensions", false); +// +// DefaultPluginsRepository downloader = mock(DefaultPluginsRepository.class); +// when(downloader.pluginFile(checkstyle)).thenReturn(fileFromCache("sonar-checkstyle-plugin-2.8.jar")); +// when(downloader.pluginFile(checkstyleExt)).thenReturn(fileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar")); +// +// Settings settings = new Settings(); +// settings.setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle"); +// repository = new BatchPluginRepository(downloader, settings, mode, new BatchPluginJarInstaller(cache)); +// +// repository.doStart(Arrays.asList(checkstyle, checkstyleExt)); +// +// assertThat(repository.getMetadata()).isEmpty(); +// } +// +// private File fileFromCache(String filename) throws Exception { +// File file = new File(Resources.getResource("org/sonar/batch/bootstrap/BatchPluginRepositoryTest/" + filename).toURI()); +// File destDir = new File(userHome, "cache/foomd5"); +// FileUtils.forceMkdir(destDir); +// FileUtils.copyFileToDirectory(file, destDir); +// return new File(destDir, filename); +// } +// +// @Test +// public void shouldAlwaysAcceptIfNoWhiteListAndBlackList() { +// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(new Settings(), mode); +// assertThat(filter.accepts("pmd")).isTrue(); +// assertThat(filter.accepts("buildbreaker")).isTrue(); +// } +// +// @Test +// public void shouldBlackListBuildBreakerInPreviewMode() { +// when(mode.isPreview()).thenReturn(true); +// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(new Settings(), mode); +// assertThat(filter.accepts("buildbreaker")).isFalse(); +// } +// +// @Test +// public void whiteListShouldTakePrecedenceOverBlackList() { +// Settings settings = new Settings() +// .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs") +// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura,pmd"); +// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); +// assertThat(filter.accepts("pmd")).isTrue(); +// } +// +// @Test +// public void corePluginShouldAlwaysBeInWhiteList() { +// Settings settings = new Settings() +// .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs"); +// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); +// assertThat(filter.accepts("core")).isTrue(); +// } +// +// @Test +// public void corePluginShouldNeverBeInBlackList() { +// Settings settings = new Settings() +// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "core,findbugs"); +// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); +// assertThat(filter.accepts("core")).isTrue(); +// } +// +// @Test +// public void check_white_list_with_black_list() { +// Settings settings = new Settings() +// .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle,pmd,findbugs") +// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura"); +// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); +// assertThat(filter.accepts("checkstyle")).isTrue(); +// assertThat(filter.accepts("pmd")).isTrue(); +// assertThat(filter.accepts("cobertura")).isFalse(); +// } +// +// @Test +// public void check_white_list_when_plugin_is_in_both_list() { +// Settings settings = new Settings() +// .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "cobertura,checkstyle,pmd,findbugs") +// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura"); +// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); +// assertThat(filter.accepts("checkstyle")).isTrue(); +// assertThat(filter.accepts("pmd")).isTrue(); +// assertThat(filter.accepts("cobertura")).isTrue(); +// } +// +// @Test +// public void check_black_list_if_no_white_list() { +// Settings settings = new Settings() +// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd,findbugs"); +// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); +// assertThat(filter.accepts("checkstyle")).isFalse(); +// assertThat(filter.accepts("pmd")).isFalse(); +// assertThat(filter.accepts("cobertura")).isTrue(); +// } +// +// @Test +// public void should_concatenate_preview_filters() { +// Settings settings = new Settings() +// .setProperty(CoreProperties.PREVIEW_INCLUDE_PLUGINS, "cockpit") +// .setProperty(CoreProperties.PREVIEW_EXCLUDE_PLUGINS, "views") +// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd"); +// when(mode.isPreview()).thenReturn(true); +// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); +// assertThat(filter.whites).containsOnly("cockpit"); +// assertThat(filter.blacks).containsOnly("views", "checkstyle", "pmd"); +// } +// +// @Test +// public void should_concatenate_deprecated_dry_run_filters() { +// Settings settings = new Settings() +// .setProperty(CoreProperties.DRY_RUN_INCLUDE_PLUGINS, "cockpit") +// .setProperty(CoreProperties.DRY_RUN_EXCLUDE_PLUGINS, "views") +// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "checkstyle,pmd"); +// when(mode.isPreview()).thenReturn(true); +// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); +// assertThat(filter.whites).containsOnly("cockpit"); +// assertThat(filter.blacks).containsOnly("views", "checkstyle", "pmd"); +// } +// +// @Test +// public void inclusions_and_exclusions_should_be_trimmed() { +// Settings settings = new Settings() +// .setProperty(CoreProperties.BATCH_INCLUDE_PLUGINS, "checkstyle, pmd, findbugs") +// .setProperty(CoreProperties.BATCH_EXCLUDE_PLUGINS, "cobertura, pmd"); +// BatchPluginRepository.PluginFilter filter = new BatchPluginRepository.PluginFilter(settings, mode); +// assertThat(filter.accepts("pmd")).isTrue(); +// } +// +//} diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java similarity index 68% rename from sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest.java rename to sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java index 2209c57431a..06a25148775 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BatchPluginUnzipperTest.java @@ -24,7 +24,9 @@ import org.junit.Before; import org.junit.ClassRule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import org.sonar.core.plugins.DefaultPluginMetadata; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.UnzippedPlugin; +import org.sonar.home.cache.FileCache; import org.sonar.home.cache.FileCacheBuilder; import java.io.File; @@ -32,35 +34,37 @@ import java.io.IOException; import static org.assertj.core.api.Assertions.assertThat; -public class BatchPluginJarInstallerTest { - - private BatchPluginJarInstaller extractor; +public class BatchPluginUnzipperTest { @ClassRule - public static TemporaryFolder temporaryFolder = new TemporaryFolder(); + public static TemporaryFolder temp = new TemporaryFolder(); - private File userHome; + File userHome; + BatchPluginUnzipper underTest; @Before public void setUp() throws IOException { - userHome = temporaryFolder.newFolder(); - extractor = new BatchPluginJarInstaller(new FileCacheBuilder().setUserHome(userHome).build()); + userHome = temp.newFolder(); + FileCache fileCache = new FileCacheBuilder().setUserHome(userHome).build(); + underTest = new BatchPluginUnzipper(fileCache); } @Test - public void should_copy_and_extract_dependencies() throws IOException { + public void copy_and_extract_libs() throws IOException { File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar"); - DefaultPluginMetadata metadata = extractor.installToCache(fileFromCache, true); + UnzippedPlugin unzipped = underTest.unzip(PluginInfo.create(fileFromCache)); - assertThat(metadata.getKey()).isEqualTo("checkstyle"); + assertThat(unzipped.getKey()).isEqualTo("checkstyle"); + assertThat(unzipped.getMain()).isFile().exists(); + assertThat(unzipped.getLibs()).extracting("name").containsOnly("antlr-2.7.6.jar", "checkstyle-5.1.jar", "commons-cli-1.0.jar"); assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar")).exists(); assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/META-INF/lib/checkstyle-5.1.jar")).exists(); } @Test - public void should_extract_only_dependencies() throws IOException { + public void extract_only_libs() throws IOException { File fileFromCache = getFileFromCache("sonar-checkstyle-plugin-2.8.jar"); - extractor.installToCache(fileFromCache, true); + underTest.unzip(PluginInfo.create(fileFromCache)); assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar")).exists(); assertThat(new File(fileFromCache.getParent(), "sonar-checkstyle-plugin-2.8.jar_unzip/META-INF/MANIFEST.MF")).doesNotExist(); @@ -68,7 +72,7 @@ public class BatchPluginJarInstallerTest { } File getFileFromCache(String filename) throws IOException { - File src = FileUtils.toFile(BatchPluginJarInstallerTest.class.getResource("/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest/" + filename)); + File src = FileUtils.toFile(BatchPluginUnzipperTest.class.getResource("/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/" + filename)); File destFile = new File(new File(userHome, "" + filename.hashCode()), filename); FileUtils.copyFile(src, destFile); return destFile; diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java index 0647b0e22ec..b01040b29cd 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/ExtensionInstallerTest.java @@ -19,7 +19,6 @@ */ package org.sonar.batch.bootstrap; -import com.google.common.collect.Maps; import org.apache.commons.lang.ClassUtils; import org.junit.Before; import org.junit.Test; @@ -28,13 +27,12 @@ import org.sonar.api.ExtensionProvider; import org.sonar.api.Plugin; import org.sonar.api.SonarPlugin; import org.sonar.api.batch.SupportedEnvironment; -import org.sonar.api.platform.ComponentContainer; -import org.sonar.api.platform.PluginMetadata; import org.sonar.batch.bootstrapper.EnvironmentInformation; +import org.sonar.core.platform.ComponentContainer; +import org.sonar.core.platform.PluginInfo; import java.util.Arrays; import java.util.List; -import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; @@ -42,19 +40,15 @@ import static org.mockito.Mockito.when; public class ExtensionInstallerTest { - private DefaultAnalysisMode mode; - PluginMetadata metadata = mock(PluginMetadata.class); + DefaultAnalysisMode mode; + BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); - Map newPlugin(final Object... extensions) { - Map result = Maps.newHashMap(); - result.put(metadata, - new SonarPlugin() { - public List getExtensions() { - return Arrays.asList(extensions); - } + private static Plugin newPluginInstance(final Object... extensions) { + return new SonarPlugin() { + public List getExtensions() { + return Arrays.asList(extensions); } - ); - return result; + }; } @Before @@ -64,8 +58,9 @@ public class ExtensionInstallerTest { @Test public void should_filter_extensions_to_install() { - BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); - when(pluginRepository.getPluginsByMetadata()).thenReturn(newPlugin(Foo.class, Bar.class)); + when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(new PluginInfo("foo"))); + when(pluginRepository.getPluginInstance("foo")).thenReturn(newPluginInstance(Foo.class, Bar.class)); + ComponentContainer container = new ComponentContainer(); ExtensionInstaller installer = new ExtensionInstaller(pluginRepository, new EnvironmentInformation("ant", "1.7"), mode); installer.install(container, new FooMatcher()); @@ -76,8 +71,8 @@ public class ExtensionInstallerTest { @Test public void should_execute_extension_provider() { - BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); - when(pluginRepository.getPluginsByMetadata()).thenReturn(newPlugin(new FooProvider(), new BarProvider())); + when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(new PluginInfo("foo"))); + when(pluginRepository.getPluginInstance("foo")).thenReturn(newPluginInstance(new FooProvider(), new BarProvider())); ComponentContainer container = new ComponentContainer(); ExtensionInstaller installer = new ExtensionInstaller(pluginRepository, new EnvironmentInformation("ant", "1.7"), mode); @@ -89,8 +84,8 @@ public class ExtensionInstallerTest { @Test public void should_provide_list_of_extensions() { - BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); - when(pluginRepository.getPluginsByMetadata()).thenReturn(newPlugin(new FooBarProvider())); + when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(new PluginInfo("foo"))); + when(pluginRepository.getPluginInstance("foo")).thenReturn(newPluginInstance(new FooBarProvider())); ComponentContainer container = new ComponentContainer(); ExtensionInstaller installer = new ExtensionInstaller(pluginRepository, new EnvironmentInformation("ant", "1.7"), mode); @@ -102,9 +97,8 @@ public class ExtensionInstallerTest { @Test public void should_not_install_on_unsupported_environment() { - BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); - when(pluginRepository.getPluginsByMetadata()).thenReturn(newPlugin(Foo.class, MavenExtension.class, AntExtension.class, new BarProvider())); - + when(pluginRepository.getPluginInfos()).thenReturn(Arrays.asList(new PluginInfo("foo"))); + when(pluginRepository.getPluginInstance("foo")).thenReturn(newPluginInstance(Foo.class, MavenExtension.class, AntExtension.class, new BarProvider())); ComponentContainer container = new ComponentContainer(); ExtensionInstaller installer = new ExtensionInstaller(pluginRepository, new EnvironmentInformation("ant", "1.7"), mode); diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalContainerTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalContainerTest.java index 9fce2c7a951..ac87c52fa51 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalContainerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/GlobalContainerTest.java @@ -19,25 +19,15 @@ */ package org.sonar.batch.bootstrap; -import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import org.junit.Test; import org.sonar.api.BatchExtension; -import org.sonar.api.Plugin; -import org.sonar.api.SonarPlugin; -import org.sonar.api.platform.PluginMetadata; import org.sonar.api.utils.TempFolder; import org.sonar.core.config.Logback; -import java.util.Arrays; import java.util.Collections; -import java.util.List; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.doNothing; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; public class GlobalContainerTest { @Test @@ -58,22 +48,6 @@ public class GlobalContainerTest { assertThat(container.getComponentByType(Bar.class)).isNotNull(); } - @Test - public void should_install_plugins() { - PluginMetadata metadata = mock(PluginMetadata.class); - FakePlugin plugin = new FakePlugin(); - BatchPluginRepository pluginRepository = mock(BatchPluginRepository.class); - when(pluginRepository.getPluginsByMetadata()).thenReturn(ImmutableMap.of( - metadata, plugin - )); - - GlobalContainer container = spy(GlobalContainer.create(Collections.emptyMap(), Lists.newArrayList(pluginRepository))); - doNothing().when(container).executeTask(Collections.emptyMap()); - container.doAfterStart(); - - assertThat(container.getComponentsByType(Plugin.class)).containsOnly(plugin); - } - public static class Foo implements BatchExtension { } @@ -82,10 +56,4 @@ public class GlobalContainerTest { } - public static class FakePlugin extends SonarPlugin { - - public List getExtensions() { - return Arrays.asList(Foo.class, Bar.class); - } - } } diff --git a/sonar-batch/src/test/java/org/sonar/batch/deprecated/decorator/DecoratorsSelectorTest.java b/sonar-batch/src/test/java/org/sonar/batch/deprecated/decorator/DecoratorsSelectorTest.java index fd9a9ed3727..3b7340689db 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/deprecated/decorator/DecoratorsSelectorTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/deprecated/decorator/DecoratorsSelectorTest.java @@ -25,7 +25,7 @@ import org.sonar.api.batch.Decorator; import org.sonar.api.batch.DecoratorContext; import org.sonar.api.batch.DependedUpon; import org.sonar.api.measures.*; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.resources.Project; import org.sonar.api.resources.Resource; import org.sonar.batch.bootstrap.BatchExtensionDictionnary; diff --git a/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java b/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java index 5c98be19187..b4f7dc49ce3 100644 --- a/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java +++ b/sonar-batch/src/test/java/org/sonar/batch/scan/ProjectScanContainerTest.java @@ -30,7 +30,7 @@ import org.sonar.api.batch.bootstrap.ProjectDefinition; import org.sonar.api.batch.bootstrap.ProjectReactor; import org.sonar.api.config.PropertyDefinitions; import org.sonar.api.config.Settings; -import org.sonar.api.platform.ComponentContainer; +import org.sonar.core.platform.ComponentContainer; import org.sonar.api.task.TaskExtension; import org.sonar.api.utils.System2; import org.sonar.api.utils.TempFolder; diff --git a/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest/sonar-checkstyle-plugin-2.8.jar b/sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/sonar-checkstyle-plugin-2.8.jar similarity index 100% rename from sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginJarInstallerTest/sonar-checkstyle-plugin-2.8.jar rename to sonar-batch/src/test/resources/org/sonar/batch/bootstrap/BatchPluginUnzipperTest/sonar-checkstyle-plugin-2.8.jar diff --git a/sonar-check-api/src/main/java/org/sonar/check/package-info.java b/sonar-check-api/src/main/java/org/sonar/check/package-info.java index b1f3232a62f..bd348edb5c0 100644 --- a/sonar-check-api/src/main/java/org/sonar/check/package-info.java +++ b/sonar-check-api/src/main/java/org/sonar/check/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.check; diff --git a/sonar-colorizer/src/main/java/org/sonar/colorizer/package-info.java b/sonar-colorizer/src/main/java/org/sonar/colorizer/package-info.java index 551a67c296f..d0abbe987a4 100644 --- a/sonar-colorizer/src/main/java/org/sonar/colorizer/package-info.java +++ b/sonar-colorizer/src/main/java/org/sonar/colorizer/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.colorizer; diff --git a/sonar-core/pom.xml b/sonar-core/pom.xml index 41ae4b71bff..ea0fd0be8f8 100644 --- a/sonar-core/pom.xml +++ b/sonar-core/pom.xml @@ -19,15 +19,13 @@ jsr305 provided + + org.codehaus.sonar + sonar-classloader + ${project.groupId} sonar-plugin-api - - - classworlds - classworlds - - org.mybatis @@ -89,10 +87,6 @@ commons-dbutils commons-dbutils - - org.codehaus.plexus - plexus-classworlds - com.googlecode.json-simple json-simple diff --git a/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java b/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java index 02281c51f9e..8190b0f6e3e 100644 --- a/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java +++ b/sonar-core/src/main/java/org/sonar/core/i18n/DefaultI18n.java @@ -20,41 +20,49 @@ package org.sonar.core.i18n; import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Maps; +import com.google.common.base.Preconditions; import org.apache.commons.io.IOUtils; import org.picocontainer.Startable; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.BatchExtension; -import org.sonar.api.ServerExtension; import org.sonar.api.i18n.I18n; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; import org.sonar.api.utils.SonarException; import org.sonar.api.utils.System2; +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.CheckForNull; import javax.annotation.Nullable; +import java.io.Closeable; import java.io.IOException; import java.io.InputStream; import java.text.DateFormat; import java.text.DecimalFormat; import java.text.MessageFormat; import java.text.NumberFormat; -import java.util.*; - -public class DefaultI18n implements I18n, ServerExtension, BatchExtension, Startable { - - private static final Logger LOG = LoggerFactory.getLogger(DefaultI18n.class); - - public static final String BUNDLE_PACKAGE = "org.sonar.l10n."; +import java.util.Collection; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.MissingResourceException; +import java.util.ResourceBundle; +import java.util.Set; + +public class DefaultI18n implements I18n, Startable { + + private static final Logger LOG = Loggers.get(DefaultI18n.class); + private static final String BUNDLE_PACKAGE = "org.sonar.l10n."; + + private final PluginRepository pluginRepository; + private final ResourceBundle.Control control; + private final System2 system2; - private PluginRepository pluginRepository; + // the following fields are available after startup private ClassLoader classloader; private Map propertyToBundles; - private final ResourceBundle.Control control; - private final System2 system2; public DefaultI18n(PluginRepository pluginRepository) { this(pluginRepository, System2.INSTANCE); @@ -68,9 +76,7 @@ public class DefaultI18n implements I18n, ServerExtension, BatchExtension, Start this.control = new ResourceBundle.Control() { @Override public Locale getFallbackLocale(String baseName, Locale locale) { - if (baseName == null) { - throw new NullPointerException(); - } + Preconditions.checkNotNull(baseName); Locale defaultLocale = Locale.ENGLISH; return locale.equals(defaultLocale) ? null : defaultLocale; } @@ -85,16 +91,16 @@ public class DefaultI18n implements I18n, ServerExtension, BatchExtension, Start @VisibleForTesting void doStart(ClassLoader classloader) { this.classloader = classloader; - propertyToBundles = Maps.newHashMap(); - Collection metadata = pluginRepository.getMetadata(); - if (metadata.isEmpty()) { + this.propertyToBundles = new HashMap<>(); + Collection infos = pluginRepository.getPluginInfos(); + if (infos.isEmpty()) { addPlugin("core"); } else { - for (PluginMetadata plugin : pluginRepository.getMetadata()) { + for (PluginInfo plugin : infos) { addPlugin(plugin.getKey()); } } - LOG.debug(String.format("Loaded %d properties from l10n bundles", propertyToBundles.size())); + LOG.debug("Loaded {} properties from l10n bundles", propertyToBundles.size()); } private void addPlugin(String pluginKey) { @@ -113,6 +119,9 @@ public class DefaultI18n implements I18n, ServerExtension, BatchExtension, Start @Override public void stop() { + if (classloader instanceof Closeable) { + IOUtils.closeQuietly((Closeable)classloader); + } classloader = null; propertyToBundles = null; } diff --git a/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java b/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java index d0eb0510763..9647eb784ca 100644 --- a/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java +++ b/sonar-core/src/main/java/org/sonar/core/i18n/I18nClassloader.java @@ -19,35 +19,30 @@ */ package org.sonar.core.i18n; +import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.Lists; import org.sonar.api.Plugin; -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.net.URL; import java.net.URLClassLoader; import java.util.List; +/** + * Aggregation of all plugin and core classloaders, used to search for all l10n bundles + */ class I18nClassloader extends URLClassLoader { - private static List classLoadersFromPlugin(PluginRepository pluginRepository) { - List list = Lists.newArrayList(); - for (PluginMetadata metadata : pluginRepository.getMetadata()) { - Plugin plugin = pluginRepository.getPlugin(metadata.getKey()); - list.add(plugin.getClass().getClassLoader()); - } - return list; - } - - private ClassLoader[] pluginClassloaders; + private final ClassLoader[] pluginClassloaders; public I18nClassloader(PluginRepository pluginRepository) { - this(classLoadersFromPlugin(pluginRepository)); + this(allPluginClassloaders(pluginRepository)); } + @VisibleForTesting I18nClassloader(List pluginClassloaders) { super(new URL[0]); - pluginClassloaders.add(getClass().getClassLoader()); this.pluginClassloaders = pluginClassloaders.toArray(new ClassLoader[pluginClassloaders.size()]); } @@ -59,7 +54,7 @@ class I18nClassloader extends URLClassLoader { return url; } } - return null; + return getClass().getClassLoader().getResource(name); } @Override @@ -71,4 +66,15 @@ class I18nClassloader extends URLClassLoader { public String toString() { return "i18n-classloader"; } + + private static List allPluginClassloaders(PluginRepository pluginRepository) { + // accepted limitation: some plugins extend base plugins, sharing the same classloader, so + // there may be duplicated classloaders in the list. + List list = Lists.newArrayList(); + for (PluginInfo info : pluginRepository.getPluginInfos()) { + Plugin plugin = pluginRepository.getPluginInstance(info.getKey()); + list.add(plugin.getClass().getClassLoader()); + } + return list; + } } diff --git a/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java b/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java index 2d51c65fac8..f4dc7f69eca 100644 --- a/sonar-core/src/main/java/org/sonar/core/issue/workflow/package-info.java +++ b/sonar-core/src/main/java/org/sonar/core/issue/workflow/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.core.issue.workflow; diff --git a/sonar-core/src/main/java/org/sonar/core/notification/package-info.java b/sonar-core/src/main/java/org/sonar/core/notification/package-info.java index de0f088a8f3..c53b6d4b186 100644 --- a/sonar-core/src/main/java/org/sonar/core/notification/package-info.java +++ b/sonar-core/src/main/java/org/sonar/core/notification/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.core.notification; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentContainer.java b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java similarity index 94% rename from sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentContainer.java rename to sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java index e8555ed4e35..868208b12a0 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentContainer.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/ComponentContainer.java @@ -17,7 +17,7 @@ * 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.api.platform; +package org.sonar.core.platform; import com.google.common.collect.Iterables; import org.picocontainer.Characteristics; @@ -36,9 +36,6 @@ import javax.annotation.Nullable; import java.util.Collection; import java.util.List; -/** - * @since 2.12 - */ public class ComponentContainer implements BatchComponent, ServerComponent { // no need for multiple children @@ -183,14 +180,14 @@ public class ComponentContainer implements BatchComponent, ServerComponent { return this; } - public ComponentContainer addExtension(@Nullable PluginMetadata plugin, Object extension) { + public ComponentContainer addExtension(@Nullable PluginInfo pluginInfo, Object extension) { Object key = componentKeys.of(extension); try { pico.as(Characteristics.CACHE).addComponent(key, extension); } catch (Throwable t) { throw new IllegalStateException("Unable to register extension " + getName(extension), t); } - declareExtension(plugin, extension); + declareExtension(pluginInfo, extension); return this; } @@ -201,8 +198,8 @@ public class ComponentContainer implements BatchComponent, ServerComponent { return getName(extension.getClass()); } - public void declareExtension(@Nullable PluginMetadata plugin, Object extension) { - propertyDefinitions.addComponent(extension, plugin != null ? plugin.getName() : ""); + public void declareExtension(@Nullable PluginInfo pluginInfo, Object extension) { + propertyDefinitions.addComponent(extension, pluginInfo != null ? pluginInfo.getName() : ""); } public ComponentContainer addPicoAdapter(ComponentAdapter adapter) { @@ -234,7 +231,7 @@ public class ComponentContainer implements BatchComponent, ServerComponent { return new ComponentContainer(this); } - static MutablePicoContainer createPicoContainer() { + public static MutablePicoContainer createPicoContainer() { ReflectionLifecycleStrategy lifecycleStrategy = new ReflectionLifecycleStrategy(new NullComponentMonitor(), "start", "stop", "close"); return new DefaultPicoContainer(new OptInCaching(), lifecycleStrategy, null); } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentKeys.java b/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java similarity index 93% rename from sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentKeys.java rename to sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java index 56de51f17e1..be315cd9cf7 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/ComponentKeys.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/ComponentKeys.java @@ -17,9 +17,8 @@ * 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.api.platform; +package org.sonar.core.platform; -import com.google.common.annotations.VisibleForTesting; import org.sonar.api.utils.internal.Uuids; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; @@ -28,19 +27,15 @@ import java.util.HashSet; import java.util.Set; import java.util.regex.Pattern; -/** - * @since 3.7.1 - */ class ComponentKeys { private static final Pattern IDENTITY_HASH_PATTERN = Pattern.compile(".+@[a-f0-9]+"); - private final Set objectsWithoutToString = new HashSet(); + private final Set objectsWithoutToString = new HashSet<>(); Object of(Object component) { return of(component, Loggers.get(ComponentKeys.class)); } - @VisibleForTesting Object of(Object component, Logger log) { if (component instanceof Class) { return component; diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PicoUtils.java b/sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java similarity index 96% rename from sonar-plugin-api/src/main/java/org/sonar/api/platform/PicoUtils.java rename to sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java index b14d886196b..ee4e1ae42a5 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PicoUtils.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PicoUtils.java @@ -17,7 +17,7 @@ * 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.api.platform; +package org.sonar.core.platform; import com.google.common.base.Throwables; import org.picocontainer.PicoLifecycleException; @@ -25,6 +25,7 @@ import org.picocontainer.PicoLifecycleException; class PicoUtils { private PicoUtils() { + // only static methods } static Throwable sanitize(Throwable t) { diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java new file mode 100644 index 00000000000..e5509bbc7e7 --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginInfo.java @@ -0,0 +1,353 @@ +/* + * 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.core.platform; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import org.apache.commons.lang.StringUtils; +import org.sonar.updatecenter.common.PluginManifest; +import org.sonar.updatecenter.common.Version; + +import javax.annotation.CheckForNull; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public class PluginInfo implements Comparable { + + public static class RequiredPlugin { + private final String key; + private final Version minimalVersion; + + public RequiredPlugin(String key, Version minimalVersion) { + this.key = key; + this.minimalVersion = minimalVersion; + } + + public String getKey() { + return key; + } + + public Version getMinimalVersion() { + return minimalVersion; + } + + public static RequiredPlugin parse(String s) { + if (!s.matches("\\w+:.+")) { + throw new IllegalArgumentException("Manifest field does not have correct format: " + s); + } + String[] fields = StringUtils.split(s, ':'); + return new RequiredPlugin(fields[0], Version.create(fields[1]).removeQualifier()); + } + } + + private File file; + private String key; + private String name; + private Version version; + private Version minimalSqVersion; + private String mainClass; + private String description; + private String organizationName; + private String organizationUrl; + private String license; + private String homepageUrl; + private String issueTrackerUrl; + private boolean useChildFirstClassLoader; + private String basePlugin; + private boolean core; + private String implementationBuild; + private final List requiredPlugins = new ArrayList<>(); + + public PluginInfo() { + } + + /** + * For tests only + */ + public PluginInfo(String key) { + this.key = key; + } + + public File getFile() { + return file; + } + + public String getKey() { + return key; + } + + public String getName() { + return name; + } + + @CheckForNull + public Version getVersion() { + return version; + } + + @CheckForNull + public Version getMinimalSqVersion() { + return minimalSqVersion; + } + + public String getMainClass() { + return mainClass; + } + + @CheckForNull + public String getDescription() { + return description; + } + + @CheckForNull + public String getOrganizationName() { + return organizationName; + } + + @CheckForNull + public String getOrganizationUrl() { + return organizationUrl; + } + + @CheckForNull + public String getLicense() { + return license; + } + + @CheckForNull + public String getHomepageUrl() { + return homepageUrl; + } + + @CheckForNull + public String getIssueTrackerUrl() { + return issueTrackerUrl; + } + + public boolean isUseChildFirstClassLoader() { + return useChildFirstClassLoader; + } + + @CheckForNull + public String getBasePlugin() { + return basePlugin; + } + + public boolean isCore() { + return core; + } + + @CheckForNull + public String getImplementationBuild() { + return implementationBuild; + } + + public List getRequiredPlugins() { + return requiredPlugins; + } + + /** + * Required + */ + public PluginInfo setFile(File file) { + this.file = file; + return this; + } + + /** + * Required + */ + public PluginInfo setKey(String key) { + this.key = key; + return this; + } + + /** + * Required + */ + public PluginInfo setName(String name) { + this.name = name; + return this; + } + + /** + * Required + */ + public PluginInfo setVersion(Version version) { + this.version = version; + return this; + } + + public PluginInfo setMinimalSqVersion(@Nullable Version v) { + this.minimalSqVersion = v; + return this; + } + + /** + * Required + */ + public PluginInfo setMainClass(String mainClass) { + this.mainClass = mainClass; + return this; + } + + public PluginInfo setDescription(@Nullable String description) { + this.description = description; + return this; + } + + public PluginInfo setOrganizationName(@Nullable String s) { + this.organizationName = s; + return this; + } + + public PluginInfo setOrganizationUrl(@Nullable String s) { + this.organizationUrl = s; + return this; + } + + public PluginInfo setLicense(@Nullable String license) { + this.license = license; + return this; + } + + public PluginInfo setHomepageUrl(@Nullable String s) { + this.homepageUrl = s; + return this; + } + + public PluginInfo setIssueTrackerUrl(@Nullable String s) { + this.issueTrackerUrl = s; + return this; + } + + public PluginInfo setUseChildFirstClassLoader(boolean b) { + this.useChildFirstClassLoader = b; + return this; + } + + public PluginInfo setBasePlugin(@Nullable String s) { + this.basePlugin = s; + return this; + } + + public PluginInfo setCore(boolean b) { + this.core = b; + return this; + } + + public PluginInfo setImplementationBuild(@Nullable String implementationBuild) { + this.implementationBuild = implementationBuild; + return this; + } + + public PluginInfo addRequiredPlugin(RequiredPlugin p) { + this.requiredPlugins.add(p); + return this; + } + + /** + * Find out if this plugin is compatible with a given version of SonarQube. + * The version of SQ must be greater than or equal to the minimal version + * needed by the plugin. + */ + public boolean isCompatibleWith(String sqVersion) { + if (null == this.minimalSqVersion) { + // no constraint defined on the plugin + return true; + } + + Version effectiveMin = Version.create(minimalSqVersion.getName()).removeQualifier(); + Version actualVersion = Version.create(sqVersion).removeQualifier(); + return actualVersion.compareTo(effectiveMin) >= 0; + } + + @Override + public String toString() { + return String.format("[%s]", Joiner.on(" / ").skipNulls().join(key, version, implementationBuild)); + } + + @Override + public int compareTo(PluginInfo other) { + int cmp = name.compareTo(other.name); + if (cmp != 0) { + return cmp; + } + return version.compareTo(other.version); + } + + public static PluginInfo create(File jarFile) { + try { + PluginManifest manifest = new PluginManifest(jarFile); + return create(jarFile, manifest); + + } catch (Exception e) { + throw new IllegalStateException("Fail to extract plugin metadata from file: " + jarFile, e); + } + } + + @VisibleForTesting + static PluginInfo create(File jarFile, PluginManifest manifest) { + PluginInfo info = new PluginInfo(); + + // required fields + info.setKey(manifest.getKey()); + info.setFile(jarFile); + info.setName(manifest.getName()); + info.setMainClass(manifest.getMainClass()); + info.setVersion(Version.create(manifest.getVersion())); + + // optional fields + info.setDescription(manifest.getDescription()); + info.setLicense(manifest.getLicense()); + info.setOrganizationName(manifest.getOrganization()); + info.setOrganizationUrl(manifest.getOrganizationUrl()); + String minSqVersion = manifest.getSonarVersion(); + if (minSqVersion != null) { + info.setMinimalSqVersion(Version.create(minSqVersion)); + } + info.setHomepageUrl(manifest.getHomepage()); + info.setIssueTrackerUrl(manifest.getIssueTrackerUrl()); + info.setUseChildFirstClassLoader(manifest.isUseChildFirstClassLoader()); + info.setBasePlugin(manifest.getBasePlugin()); + info.setImplementationBuild(manifest.getImplementationBuild()); + String[] requiredPlugins = manifest.getRequirePlugins(); + if (requiredPlugins != null) { + for (String s : requiredPlugins) { + info.addRequiredPlugin(RequiredPlugin.parse(s)); + } + } + return info; + } + + public enum JarToPluginInfo implements Function { + INSTANCE; + + @Override + public PluginInfo apply(@Nonnull File jarFile) { + return create(jarFile); + } + }; +} diff --git a/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java new file mode 100644 index 00000000000..336c4904bcf --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginLoader.java @@ -0,0 +1,199 @@ +/* + * 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.core.platform; + +import com.google.common.base.Strings; +import org.apache.commons.lang.SystemUtils; +import org.sonar.api.BatchComponent; +import org.sonar.api.Plugin; +import org.sonar.api.ServerComponent; +import org.sonar.api.utils.log.Loggers; +import org.sonar.classloader.ClassloaderBuilder; +import org.sonar.classloader.ClassloaderBuilder.LoadingOrder; +import org.sonar.classloader.Mask; + +import java.io.Closeable; +import java.io.File; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.sonar.classloader.ClassloaderBuilder.LoadingOrder.SELF_FIRST; + +/** + * Loads the plugin JAR files by creating the appropriate classloaders and by instantiating + * the entry point classes as defined in manifests. It assumes that JAR files are compatible with current + * environment (minimal sonarqube version, compatibility between plugins, ...). + *

+ * Standard plugins have their own isolated classloader. Some others can extend a "base" plugin. + * In this case they share the same classloader then the base plugin. + *

+ * This class is stateless. It does not keep classloaders and {@link Plugin} in memory. + */ +public class PluginLoader implements BatchComponent, ServerComponent { + + private static final String[] DEFAULT_SHARED_RESOURCES = {"org/sonar/plugins/", "com/sonar/plugins/", "com/sonarsource/plugins/"}; + + /** + * Information about the classloader to be created for a set of plugins. + */ + static class ClassloaderDef { + final String basePluginKey; + final Map mainClassesByPluginKey = new HashMap<>(); + final List files = new ArrayList<>(); + final Mask mask = new Mask(); + boolean selfFirstStrategy = false; + ClassLoader classloader = null; + + public ClassloaderDef(String basePluginKey) { + this.basePluginKey = basePluginKey; + } + } + + private final PluginUnzipper unzipper; + + public PluginLoader(PluginUnzipper unzipper) { + this.unzipper = unzipper; + } + + public Map load(Map infoByKeys) { + Collection defs = defineClassloaders(infoByKeys).values(); + buildClassloaders(defs); + return instantiatePluginInstances(defs); + } + + /** + * Step 1 - define the different classloaders to be created. Number of classloaders can be + * different than number of plugins. + */ + Map defineClassloaders(Map infoByKeys) { + Map classloadersByBasePlugin = new HashMap<>(); + + for (PluginInfo info : infoByKeys.values()) { + String baseKey = basePluginKey(info, infoByKeys); + ClassloaderDef def = classloadersByBasePlugin.get(baseKey); + if (def == null) { + def = new ClassloaderDef(baseKey); + classloadersByBasePlugin.put(baseKey, def); + } + UnzippedPlugin unzippedPlugin = unzipper.unzip(info); + def.files.add(unzippedPlugin.getMain()); + def.files.addAll(unzippedPlugin.getLibs()); + def.mainClassesByPluginKey.put(info.getKey(), info.getMainClass()); + for (String defaultSharedResource : DEFAULT_SHARED_RESOURCES) { + def.mask.addInclusion(defaultSharedResource + info.getKey() + "/"); + } + if (Strings.isNullOrEmpty(info.getBasePlugin())) { + // The plugins that extend other plugins can only add some files to classloader. + // They can't change ordering strategy. + def.selfFirstStrategy = info.isUseChildFirstClassLoader(); + } + } + return classloadersByBasePlugin; + } + + /** + * Step 2 - create classloaders with appropriate constituents and metadata + */ + void buildClassloaders(Collection defs) { + ClassloaderBuilder builder = new ClassloaderBuilder(); + for (ClassloaderDef def : defs) { + builder + .newClassloader(def.basePluginKey, getClass().getClassLoader()) + .setExportMask(def.basePluginKey, def.mask) + .setLoadingOrder(def.basePluginKey, def.selfFirstStrategy ? SELF_FIRST : LoadingOrder.PARENT_FIRST); + for (File file : def.files) { + builder.addURL(def.basePluginKey, fileToUrl(file)); + } + } + Map classloadersByBasePluginKey = builder.build(); + for (ClassloaderDef def : defs) { + def.classloader = classloadersByBasePluginKey.get(def.basePluginKey); + } + } + + /** + * Step 3 - instantiate plugin instances ({@link Plugin} + * + * @return the instances grouped by plugin key + * @throws IllegalStateException if at least one plugin can't be correctly loaded + */ + Map instantiatePluginInstances(Collection defs) { + // instantiate plugins + Map instancesByPluginKey = new HashMap<>(); + for (ClassloaderDef def : defs) { + // the same classloader can be used by multiple plugins + for (Map.Entry entry : def.mainClassesByPluginKey.entrySet()) { + String pluginKey = entry.getKey(); + String mainClass = entry.getValue(); + try { + instancesByPluginKey.put(pluginKey, (Plugin) def.classloader.loadClass(mainClass).newInstance()); + } catch (UnsupportedClassVersionError e) { + throw new IllegalStateException(String.format("The plugin [%s] does not support Java %s", + pluginKey, SystemUtils.JAVA_VERSION_TRIMMED), e); + } catch (Exception e) { + throw new IllegalStateException(String.format( + "Fail to instantiate class [%s] of plugin [%s]", mainClass, pluginKey), e); + } + } + } + return instancesByPluginKey; + } + + public void unload(Collection plugins) { + for (Plugin plugin : plugins) { + ClassLoader classLoader = plugin.getClass().getClassLoader(); + if (classLoader instanceof Closeable && classLoader != getClass().getClassLoader()) { + try { + ((Closeable) classLoader).close(); + } catch (Exception e) { + Loggers.get(getClass()).error("Fail to close classloader " + classLoader.toString(), e); + } + } + } + } + + /** + * Get the root key of a tree of plugins. For example if plugin C depends on B, which depends on A, then + * B and C must be attached to the classloader of A. The method returns A in the three cases. + */ + static String basePluginKey(PluginInfo plugin, Map allPluginsPerKey) { + String base = plugin.getKey(); + String parentKey = plugin.getBasePlugin(); + while (!Strings.isNullOrEmpty(parentKey)) { + PluginInfo parentPlugin = allPluginsPerKey.get(parentKey); + base = parentPlugin.getKey(); + parentKey = parentPlugin.getBasePlugin(); + } + return base; + } + + private URL fileToUrl(File file) { + try { + return file.toURI().toURL(); + } catch (MalformedURLException e) { + throw new IllegalStateException(e); + } + } +} diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java similarity index 64% rename from sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java rename to sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java index ad26ec2dae3..1b3b170a938 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginRepository.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginRepository.java @@ -17,32 +17,27 @@ * 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.api.platform; +package org.sonar.core.platform; import org.sonar.api.BatchComponent; import org.sonar.api.Plugin; import org.sonar.api.ServerComponent; -import javax.annotation.CheckForNull; import java.util.Collection; +/** + * Provides information about the plugins installed in the dependency injection container + */ public interface PluginRepository extends BatchComponent, ServerComponent { - @CheckForNull - Plugin getPlugin(String key); - /** - * Metadata of installed plugins. Metadata includes all the fields available in update center - * (plugin key, name, version, description, license, ...) and some technical information like - * list of embedded libraries and classloader strategy. - * - * @since 2.9 - */ - Collection getMetadata(); + Collection getPluginInfos(); + + PluginInfo getPluginInfo(String key); /** - * Search for an installed plugin. Returns null if the plugin is not installed. - * @since 2.9 + * @return the instance of {@link Plugin} for the given plugin key. Never return null. */ - @CheckForNull - PluginMetadata getMetadata(String pluginKey); + Plugin getPluginInstance(String key); + + boolean hasPlugin(String key); } diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/ResourcesClassloaderTest.java b/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java similarity index 60% rename from sonar-core/src/test/java/org/sonar/core/plugins/ResourcesClassloaderTest.java rename to sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java index e2f6dadd7a0..5ce1ca08da7 100644 --- a/sonar-core/src/test/java/org/sonar/core/plugins/ResourcesClassloaderTest.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/PluginUnzipper.java @@ -17,24 +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.platform; -package org.sonar.core.plugins; +import org.sonar.api.utils.ZipUtils; -import org.junit.Test; +import java.util.zip.ZipEntry; -import java.net.URL; -import java.util.Arrays; -import java.util.List; +public abstract class PluginUnzipper { -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.notNullValue; + protected static final String LIB_RELATIVE_PATH_IN_JAR = "META-INF/lib"; -public class ResourcesClassloaderTest { + public abstract UnzippedPlugin unzip(PluginInfo info); - @Test - public void test() throws Exception { - List urls = Arrays.asList(new URL("http://localhost:9000/deploy/plugins/checkstyle/extension.xml")); - ResourcesClassloader classLoader = new ResourcesClassloader(urls, null); - assertThat(classLoader.findResource("extension.xml"), notNullValue()); + protected ZipUtils.ZipEntryFilter newLibFilter() { + return new ZipUtils.ZipEntryFilter() { + @Override + public boolean accept(ZipEntry entry) { + return entry.getName().startsWith(LIB_RELATIVE_PATH_IN_JAR); + } + }; } } diff --git a/sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java b/sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java new file mode 100644 index 00000000000..3c73b8d658b --- /dev/null +++ b/sonar-core/src/main/java/org/sonar/core/platform/UnzippedPlugin.java @@ -0,0 +1,62 @@ +/* + * 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.core.platform; + +import java.io.File; +import java.util.Collection; +import java.util.Collections; + +import static org.apache.commons.io.FileUtils.listFiles; + +public class UnzippedPlugin { + + private final String key; + private final File main; + private final Collection libs; + + public UnzippedPlugin(String key, File main, Collection libs) { + this.key = key; + this.main = main; + this.libs = libs; + } + + public String getKey() { + return key; + } + + public File getMain() { + return main; + } + + public Collection getLibs() { + return libs; + } + + public static UnzippedPlugin createFromUnzippedDir(String pluginKey, File jarFile, File unzippedDir) { + File libDir = new File(unzippedDir, PluginUnzipper.LIB_RELATIVE_PATH_IN_JAR); + Collection libs; + if (libDir.isDirectory() && libDir.exists()) { + libs = listFiles(libDir, null, false); + } else { + libs = Collections.emptyList(); + } + return new UnzippedPlugin(pluginKey, jarFile, libs); + } +} diff --git a/server/sonar-server/src/test/java/org/sonar/server/platform/SonarHomeTest.java b/sonar-core/src/main/java/org/sonar/core/platform/package-info.java similarity index 80% rename from server/sonar-server/src/test/java/org/sonar/server/platform/SonarHomeTest.java rename to sonar-core/src/main/java/org/sonar/core/platform/package-info.java index 66a4069fba1..d93af63a5ba 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/platform/SonarHomeTest.java +++ b/sonar-core/src/main/java/org/sonar/core/platform/package-info.java @@ -17,13 +17,11 @@ * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ -package org.sonar.server.platform; -import org.junit.Test; - -public class SonarHomeTest { - @Test - public void iDontKnowHowToSimplyTestThisClass() { +/** + * Provides support of DI (Dependency Injection) container and management of plugins. + */ +@ParametersAreNonnullByDefault +package org.sonar.core.platform; - } -} +import javax.annotation.ParametersAreNonnullByDefault; diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/DefaultPluginMetadata.java b/sonar-core/src/main/java/org/sonar/core/plugins/DefaultPluginMetadata.java deleted file mode 100644 index a65036b7d94..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/plugins/DefaultPluginMetadata.java +++ /dev/null @@ -1,310 +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.core.plugins; - -import com.google.common.collect.ImmutableList; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.builder.ToStringBuilder; -import org.apache.commons.lang.builder.ToStringStyle; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.updatecenter.common.Version; - -import java.io.File; -import java.util.List; - -import static com.google.common.collect.Lists.newArrayList; - -public class DefaultPluginMetadata implements PluginMetadata, Comparable { - private File file; - private List deployedFiles; - private List pathsToInternalDeps; - private String key; - private String version; - private String sonarVersion; - private String name; - private String mainClass; - private String description; - private String organization; - private String organizationUrl; - private String license; - private String homepage; - private String issueTrackerUrl; - private boolean useChildFirstClassLoader; - private String basePlugin; - private boolean core; - private String implementationBuild; - private List requiredPlugins; - - private DefaultPluginMetadata() { - deployedFiles = newArrayList(); - pathsToInternalDeps = newArrayList(); - requiredPlugins = newArrayList(); - } - - public static DefaultPluginMetadata create(File file) { - return new DefaultPluginMetadata().setFile(file); - } - - public static DefaultPluginMetadata create(String key) { - return new DefaultPluginMetadata().setKey(key); - } - - @Override - public File getFile() { - return file; - } - - public DefaultPluginMetadata setFile(File file) { - this.file = file; - return this; - } - - @Override - public List getDeployedFiles() { - return deployedFiles; - } - - public DefaultPluginMetadata addDeployedFile(File f) { - this.deployedFiles.add(f); - return this; - } - - public List getPathsToInternalDeps() { - return ImmutableList.copyOf(pathsToInternalDeps); - } - - public DefaultPluginMetadata setPathsToInternalDeps(List pathsToInternalDeps) { - this.pathsToInternalDeps = ImmutableList.copyOf(pathsToInternalDeps); - return this; - } - - @Override - public String getKey() { - return key; - } - - public DefaultPluginMetadata setKey(String key) { - this.key = key; - return this; - } - - @Override - public String getName() { - return name; - } - - public DefaultPluginMetadata setName(String name) { - this.name = name; - return this; - } - - @Override - public String getMainClass() { - return mainClass; - } - - public DefaultPluginMetadata setMainClass(String mainClass) { - this.mainClass = mainClass; - return this; - } - - @Override - public String getDescription() { - return description; - } - - public DefaultPluginMetadata setDescription(String description) { - this.description = description; - return this; - } - - @Override - public String getOrganization() { - return organization; - } - - public DefaultPluginMetadata setOrganization(String organization) { - this.organization = organization; - return this; - } - - @Override - public String getOrganizationUrl() { - return organizationUrl; - } - - public DefaultPluginMetadata setOrganizationUrl(String organizationUrl) { - this.organizationUrl = organizationUrl; - return this; - } - - @Override - public String getLicense() { - return license; - } - - public DefaultPluginMetadata setLicense(String license) { - this.license = license; - return this; - } - - @Override - public String getVersion() { - return version; - } - - public DefaultPluginMetadata setVersion(String version) { - this.version = version; - return this; - } - - public String getSonarVersion() { - return sonarVersion; - } - - public DefaultPluginMetadata setSonarVersion(String sonarVersion) { - this.sonarVersion = sonarVersion; - return this; - } - - @Override - public List getRequiredPlugins() { - return ImmutableList.copyOf(requiredPlugins); - } - - public DefaultPluginMetadata setRequiredPlugins(List requiredPlugins) { - this.requiredPlugins = ImmutableList.copyOf(requiredPlugins); - return this; - } - - /** - * Find out if this plugin is compatible with a given version of Sonar. - * The version of sonar must be greater than or equal to the minimal version - * needed by the plugin. - * - * @param sonarVersion - * @return true if the plugin is compatible - */ - public boolean isCompatibleWith(String sonarVersion) { - if (null == this.sonarVersion) { - // Plugins without sonar version are so old, they are compatible with a version containing this code - return true; - } - - Version minimumVersion = Version.create(this.sonarVersion).removeQualifier(); - Version actualVersion = Version.create(sonarVersion).removeQualifier(); - return actualVersion.compareTo(minimumVersion) >= 0; - } - - @Override - public String getHomepage() { - return homepage; - } - - public DefaultPluginMetadata setHomepage(String homepage) { - this.homepage = homepage; - return this; - } - - @Override - public String getIssueTrackerUrl() { - return issueTrackerUrl; - } - - public DefaultPluginMetadata setIssueTrackerUrl(String issueTrackerUrl) { - this.issueTrackerUrl = issueTrackerUrl; - return this; - } - - public DefaultPluginMetadata setUseChildFirstClassLoader(boolean use) { - this.useChildFirstClassLoader = use; - return this; - } - - @Override - public boolean isUseChildFirstClassLoader() { - return useChildFirstClassLoader; - } - - public DefaultPluginMetadata setBasePlugin(String key) { - this.basePlugin = key; - return this; - } - - @Override - public String getBasePlugin() { - return basePlugin; - } - - @Override - public boolean isCore() { - return core; - } - - public DefaultPluginMetadata setCore(boolean b) { - this.core = b; - return this; - } - - @Override - public String getImplementationBuild() { - return implementationBuild; - } - - public DefaultPluginMetadata setImplementationBuild(String implementationBuild) { - this.implementationBuild = implementationBuild; - return this; - } - - @Override - public String getParent() { - return null; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (o == null || getClass() != o.getClass()) { - return false; - } - DefaultPluginMetadata that = (DefaultPluginMetadata) o; - return key == null ? that.key == null : key.equals(that.key); - } - - @Override - public int hashCode() { - return key != null ? key.hashCode() : 0; - } - - @Override - public String toString() { - return new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE) - .append("key", key) - .append("version", StringUtils.defaultIfEmpty(version, "-")) - .toString(); - } - - @Override - public int compareTo(PluginMetadata other) { - return name.compareTo(other.getName()); - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/PluginClassloaders.java b/sonar-core/src/main/java/org/sonar/core/plugins/PluginClassloaders.java deleted file mode 100644 index 8a6b4b88ef1..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/plugins/PluginClassloaders.java +++ /dev/null @@ -1,253 +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.core.plugins; - -import com.google.common.annotations.VisibleForTesting; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import org.apache.commons.lang.StringUtils; -import org.apache.commons.lang.SystemUtils; -import org.codehaus.plexus.classworlds.ClassWorld; -import org.codehaus.plexus.classworlds.realm.ClassRealm; -import org.codehaus.plexus.classworlds.realm.NoSuchRealmException; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.sonar.api.Plugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.utils.SonarException; - -import java.io.File; -import java.net.URL; -import java.util.Collection; -import java.util.List; -import java.util.Map; - -/** - * Encapsulates manipulations with ClassLoaders, such as creation and establishing dependencies. Current implementation based on - * {@link ClassWorld}. - *

- *

IMPORTANT

- *

- * If we have pluginA , then all classes and resources from package and subpackages of org.sonar.plugins.pluginA.api will be visible - * for all other plugins even if they are located in dependent library. - *

- *

- *

Search order for {@link ClassRealm} :

- *
    - *
  • parent class loader (passed via the constructor) if there is one
  • - *
  • imports
  • - *
  • realm's constituents
  • - *
  • parent realm
  • - *
- */ -public class PluginClassloaders { - - private static final String[] PREFIXES_TO_EXPORT = {"org.sonar.plugins.", "com.sonar.plugins.", "com.sonarsource.plugins."}; - private static final Logger LOG = LoggerFactory.getLogger(PluginClassloaders.class); - - private ClassWorld world; - private ClassLoader baseClassloader; - private boolean done = false; - - public PluginClassloaders(ClassLoader baseClassloader) { - this(baseClassloader, new ClassWorld()); - } - - @VisibleForTesting - PluginClassloaders(ClassLoader baseClassloader, ClassWorld world) { - this.baseClassloader = baseClassloader; - this.world = world; - } - - public Map init(Collection plugins) { - List children = Lists.newArrayList(); - for (PluginMetadata plugin : plugins) { - if (StringUtils.isBlank(plugin.getBasePlugin())) { - add(plugin); - } else { - children.add(plugin); - } - } - - for (PluginMetadata child : children) { - extend(child); - } - - done(); - - Map pluginsByKey = Maps.newHashMap(); - for (PluginMetadata metadata : plugins) { - pluginsByKey.put(metadata.getKey(), instantiatePlugin(metadata)); - } - return pluginsByKey; - } - - public ClassLoader add(PluginMetadata plugin) { - if (done) { - throw new IllegalStateException("Plugin classloaders are already initialized"); - } - try { - List resources = Lists.newArrayList(); - List others = Lists.newArrayList(); - for (File file : plugin.getDeployedFiles()) { - if (isResource(file)) { - resources.add(file.toURI().toURL()); - } else { - others.add(file.toURI().toURL()); - } - } - ClassLoader parent; - if (resources.isEmpty()) { - parent = baseClassloader; - } else { - parent = new ResourcesClassloader(resources, baseClassloader); - } - ClassRealm realm; - if (plugin.isUseChildFirstClassLoader()) { - ClassRealm parentRealm = world.newRealm(plugin.getKey() + "-parent", parent); - realm = parentRealm.createChildRealm(plugin.getKey()); - } else { - realm = world.newRealm(plugin.getKey(), parent); - } - for (URL url : others) { - realm.addURL(url); - } - return realm; - } catch (UnsupportedClassVersionError e) { - throw new SonarException(String.format("The plugin %s is not supported with Java %s", plugin.getKey(), - SystemUtils.JAVA_VERSION_TRIMMED), e); - - } catch (Exception e) { - throw new SonarException(String.format("Fail to build the classloader of %s", plugin.getKey()), e); - } - } - - public boolean extend(PluginMetadata plugin) { - if (done) { - throw new IllegalStateException("Plugin classloaders are already initialized"); - } - try { - ClassRealm base = world.getRealm(plugin.getBasePlugin()); - if (base == null) { - // Ignored, because base plugin is not installed - LOG.warn(String.format("Plugin %s is ignored because base plugin is not installed: %s", - plugin.getKey(), plugin.getBasePlugin())); - return false; - } - // we create new realm to be able to return it by key without conversion to baseKey - base.createChildRealm(plugin.getKey()); - for (File file : plugin.getDeployedFiles()) { - base.addURL(file.toURI().toURL()); - } - return true; - } catch (UnsupportedClassVersionError e) { - throw new SonarException(String.format("The plugin %s is not supported with Java %s", - plugin.getKey(), SystemUtils.JAVA_VERSION_TRIMMED), e); - - } catch (Exception e) { - throw new SonarException(String.format("Fail to extend the plugin %s for %s", - plugin.getBasePlugin(), plugin.getKey()), e); - } - } - - /** - * Establishes dependencies among ClassLoaders. - */ - public void done() { - if (done) { - throw new IllegalStateException("Plugin classloaders are already initialized"); - } - for (Object o : world.getRealms()) { - ClassRealm realm = (ClassRealm) o; - if (!StringUtils.endsWith(realm.getId(), "-parent")) { - String[] packagesToExport = new String[PREFIXES_TO_EXPORT.length]; - for (int i = 0; i < PREFIXES_TO_EXPORT.length; i++) { - // important to have dot at the end of package name only for classworlds 1.1 - packagesToExport[i] = String.format("%s%s.api", PREFIXES_TO_EXPORT[i], realm.getId()); - } - export(realm, packagesToExport); - } - } - done = true; - } - - /** - * Exports specified packages from given ClassRealm to all others. - */ - private void export(ClassRealm realm, String... packages) { - for (Object o : world.getRealms()) { - ClassRealm dep = (ClassRealm) o; - if (!StringUtils.equals(dep.getId(), realm.getId())) { - try { - for (String packageName : packages) { - dep.importFrom(realm.getId(), packageName); - } - } catch (NoSuchRealmException e) { - // should never happen - throw new SonarException(e); - } - } - } - } - - /** - * Note that this method should be called only after creation of all ClassLoaders - see {@link #done()}. - */ - public ClassLoader get(String key) { - if (!done) { - throw new IllegalStateException("Plugin classloaders are not initialized"); - } - try { - return world.getRealm(key); - } catch (NoSuchRealmException e) { - return null; - } - } - - public Plugin instantiatePlugin(PluginMetadata plugin) { - try { - Class clazz = get(plugin.getKey()).loadClass(plugin.getMainClass()); - return (Plugin) clazz.newInstance(); - - } catch (UnsupportedClassVersionError e) { - throw new SonarException(String.format("The plugin %s is not supported with Java %s", - plugin.getKey(), SystemUtils.JAVA_VERSION_TRIMMED), e); - - } catch (Exception e) { - throw new SonarException(String.format("Fail to load plugin %s", plugin.getKey()), e); - } - } - - private boolean isResource(File file) { - return !StringUtils.endsWithIgnoreCase(file.getName(), ".jar") && !file.isDirectory(); - } - - public void clean() { - for (ClassRealm realm : world.getRealms()) { - try { - world.disposeRealm(realm.getId()); - } catch (Exception e) { - // Ignore - } - } - world = null; - baseClassloader=null; - } -} diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/PluginJarInstaller.java b/sonar-core/src/main/java/org/sonar/core/plugins/PluginJarInstaller.java deleted file mode 100644 index 7c5114a323b..00000000000 --- a/sonar-core/src/main/java/org/sonar/core/plugins/PluginJarInstaller.java +++ /dev/null @@ -1,111 +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.core.plugins; - -import com.google.common.base.Function; -import org.sonar.api.BatchComponent; -import org.sonar.api.ServerComponent; -import org.sonar.api.utils.SonarException; -import org.sonar.updatecenter.common.PluginManifest; - -import javax.annotation.Nonnull; -import javax.annotation.Nullable; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; - -public abstract class PluginJarInstaller implements BatchComponent, ServerComponent { - - protected static final String FAIL_TO_INSTALL_PLUGIN = "Fail to install plugin: "; - - protected void install(DefaultPluginMetadata metadata, @Nullable File pluginBasedir, File deployedPlugin) { - try { - metadata.addDeployedFile(deployedPlugin); - copyDependencies(metadata, deployedPlugin, pluginBasedir); - } catch (IOException e) { - throw new SonarException(FAIL_TO_INSTALL_PLUGIN + metadata, e); - } - } - - private void copyDependencies(DefaultPluginMetadata metadata, File pluginFile, @Nullable File pluginBasedir) throws IOException { - if (!metadata.getPathsToInternalDeps().isEmpty()) { - // needs to unzip the jar - File baseDir = extractPluginDependencies(pluginFile, pluginBasedir); - for (String depPath : metadata.getPathsToInternalDeps()) { - File dependency = new File(baseDir, depPath); - if (!dependency.isFile() || !dependency.exists()) { - throw new IllegalArgumentException("Dependency " + depPath + " can not be found in " + pluginFile.getName()); - } - metadata.addDeployedFile(dependency); - } - } - } - - protected abstract File extractPluginDependencies(File pluginFile, @Nullable File pluginBasedir) throws IOException; - - public DefaultPluginMetadata extractMetadata(File file, boolean isCore) { - try { - PluginManifest manifest = new PluginManifest(file); - DefaultPluginMetadata metadata = DefaultPluginMetadata.create(file); - metadata.setKey(manifest.getKey()); - metadata.setName(manifest.getName()); - metadata.setDescription(manifest.getDescription()); - metadata.setLicense(manifest.getLicense()); - metadata.setOrganization(manifest.getOrganization()); - metadata.setOrganizationUrl(manifest.getOrganizationUrl()); - metadata.setMainClass(manifest.getMainClass()); - metadata.setVersion(manifest.getVersion()); - metadata.setSonarVersion(manifest.getSonarVersion()); - metadata.setHomepage(manifest.getHomepage()); - metadata.setIssueTrackerUrl(manifest.getIssueTrackerUrl()); - metadata.setPathsToInternalDeps(Arrays.asList(manifest.getDependencies())); - metadata.setUseChildFirstClassLoader(manifest.isUseChildFirstClassLoader()); - metadata.setBasePlugin(manifest.getBasePlugin()); - metadata.setImplementationBuild(manifest.getImplementationBuild()); - metadata.setRequiredPlugins(Arrays.asList(manifest.getRequirePlugins())); - metadata.setCore(isCore); - return metadata; - - } catch (IOException e) { - throw new IllegalStateException("Fail to extract plugin metadata from file: " + file, e); - } - } - - public Function fileToPlugin() { - return jarFileToPlugin; - } - - public Function fileToCorePlugin() { - return jarFileToCorePlugin; - } - - private final Function jarFileToCorePlugin = new Function() { - @Override - public DefaultPluginMetadata apply(@Nonnull File file) { - return extractMetadata(file, true); - } - }; - private final Function jarFileToPlugin = new Function() { - @Override - public DefaultPluginMetadata apply(@Nonnull File file) { - return extractMetadata(file, false); - } - }; -} diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java b/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java index 3bc254a3ff1..6fecfe0ae91 100644 --- a/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java +++ b/sonar-core/src/main/java/org/sonar/core/plugins/RemotePlugin.java @@ -20,6 +20,7 @@ package org.sonar.core.plugins; import org.apache.commons.lang.StringUtils; +import org.sonar.core.platform.PluginInfo; import org.sonar.home.cache.FileHashes; import java.io.File; @@ -34,7 +35,7 @@ public class RemotePlugin { this.core = core; } - public static RemotePlugin create(DefaultPluginMetadata metadata) { + public static RemotePlugin create(PluginInfo metadata) { RemotePlugin result = new RemotePlugin(metadata.getKey(), metadata.isCore()); result.setFile(metadata.getFile()); return result; diff --git a/sonar-core/src/main/java/org/sonar/core/plugins/package-info.java b/sonar-core/src/main/java/org/sonar/core/plugins/package-info.java index d3208e7de69..e95a48da83b 100644 --- a/sonar-core/src/main/java/org/sonar/core/plugins/package-info.java +++ b/sonar-core/src/main/java/org/sonar/core/plugins/package-info.java @@ -17,9 +17,7 @@ * 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.core.plugins; diff --git a/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java b/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java index b3acbde594a..110694aff65 100644 --- a/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java +++ b/sonar-core/src/test/java/org/sonar/core/i18n/DefaultI18nTest.java @@ -24,10 +24,10 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.runners.MockitoJUnitRunner; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.platform.PluginRepository; import org.sonar.api.utils.DateUtils; import org.sonar.api.utils.System2; +import org.sonar.core.platform.PluginInfo; +import org.sonar.core.platform.PluginRepository; import java.net.URL; import java.net.URLClassLoader; @@ -51,8 +51,8 @@ public class DefaultI18nTest { @Before public void before() { PluginRepository pluginRepository = mock(PluginRepository.class); - List plugins = Arrays.asList(newPlugin("core"), newPlugin("sqale"), newPlugin("frpack"), newPlugin("checkstyle"), newPlugin("other")); - when(pluginRepository.getMetadata()).thenReturn(plugins); + List plugins = Arrays.asList(newPlugin("core"), newPlugin("sqale"), newPlugin("frpack"), newPlugin("checkstyle"), newPlugin("other")); + when(pluginRepository.getPluginInfos()).thenReturn(plugins); manager = new DefaultI18n(pluginRepository, system2); manager.doStart(getClass().getClassLoader()); @@ -222,8 +222,8 @@ public class DefaultI18nTest { return new URLClassLoader(urls); } - private PluginMetadata newPlugin(String key) { - PluginMetadata plugin = mock(PluginMetadata.class); + private PluginInfo newPlugin(String key) { + PluginInfo plugin = mock(PluginInfo.class); when(plugin.getKey()).thenReturn(key); return plugin; } diff --git a/sonar-core/src/test/java/org/sonar/core/i18n/I18nClassloaderTest.java b/sonar-core/src/test/java/org/sonar/core/i18n/I18nClassloaderTest.java index 971a7573965..19f9add77cc 100644 --- a/sonar-core/src/test/java/org/sonar/core/i18n/I18nClassloaderTest.java +++ b/sonar-core/src/test/java/org/sonar/core/i18n/I18nClassloaderTest.java @@ -24,7 +24,7 @@ import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; -import org.sonar.api.platform.PluginRepository; +import org.sonar.core.platform.PluginRepository; import java.net.URL; import java.net.URLClassLoader; @@ -80,7 +80,7 @@ public class I18nClassloaderTest { private static URLClassLoader newClassLoader(String... resourcePaths) { URL[] urls = new URL[resourcePaths.length]; for (int index = 0; index < resourcePaths.length; index++) { - urls[index] = DefaultI18nTest.class.getResource(resourcePaths[index]); + urls[index] = I18nClassloaderTest.class.getResource(resourcePaths[index]); } return new URLClassLoader(urls); } diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java similarity index 97% rename from sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java rename to sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java index 8cbe1f5d3b4..5d7923d23bd 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentContainerTest.java +++ b/sonar-core/src/test/java/org/sonar/core/platform/ComponentContainerTest.java @@ -17,7 +17,7 @@ * 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.api.platform; +package org.sonar.core.platform; import org.junit.Rule; import org.junit.Test; @@ -31,7 +31,9 @@ import java.util.Arrays; import static junit.framework.Assert.assertTrue; import static junit.framework.Assert.fail; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; public class ComponentContainerTest { @@ -165,7 +167,7 @@ public class ComponentContainerTest { @Test public void shouldDeclareExtensionWithoutAddingIt() { ComponentContainer container = new ComponentContainer(); - PluginMetadata plugin = mock(PluginMetadata.class); + PluginInfo plugin = mock(PluginInfo.class); container.declareExtension(plugin, ComponentWithProperty.class); PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class); @@ -176,7 +178,7 @@ public class ComponentContainerTest { @Test public void shouldDeclareExtensionWhenAdding() { ComponentContainer container = new ComponentContainer(); - PluginMetadata plugin = mock(PluginMetadata.class); + PluginInfo plugin = mock(PluginInfo.class); container.addExtension(plugin, ComponentWithProperty.class); PropertyDefinitions propertyDefinitions = container.getComponentByType(PropertyDefinitions.class); @@ -221,12 +223,12 @@ public class ComponentContainerTest { @Test public void display_plugin_name_when_failing_to_add_extension() { ComponentContainer container = new ComponentContainer(); - PluginMetadata plugin = mock(PluginMetadata.class); + PluginInfo plugin = mock(PluginInfo.class); container.startComponents(); thrown.expect(IllegalStateException.class); - thrown.expectMessage("Unable to register extension org.sonar.api.platform.ComponentContainerTest$UnstartableComponent"); + thrown.expectMessage("Unable to register extension org.sonar.core.platform.ComponentContainerTest$UnstartableComponent"); container.addExtension(plugin, UnstartableComponent.class); diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentKeysTest.java b/sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java similarity index 86% rename from sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentKeysTest.java rename to sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java index f2b36a677fe..431890bc012 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/platform/ComponentKeysTest.java +++ b/sonar-core/src/test/java/org/sonar/core/platform/ComponentKeysTest.java @@ -17,13 +17,16 @@ * 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.api.platform; +package org.sonar.core.platform; import org.junit.Test; import org.sonar.api.utils.log.Logger; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.*; +import static org.mockito.Matchers.startsWith; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; public class ComponentKeysTest { @@ -35,8 +38,8 @@ public class ComponentKeysTest { } @Test - public void generate_key_of_object() { - assertThat(keys.of(new FakeComponent())).isEqualTo("org.sonar.api.platform.ComponentKeysTest.FakeComponent-fake"); + public void generate_key_of_object() throws Exception { + assertThat(keys.of(new FakeComponent())).isEqualTo("org.sonar.core.platform.ComponentKeysTest.FakeComponent-fake"); } @Test diff --git a/sonar-plugin-api/src/test/java/org/sonar/api/platform/PicoUtilsTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java similarity index 99% rename from sonar-plugin-api/src/test/java/org/sonar/api/platform/PicoUtilsTest.java rename to sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java index d5b74341b8b..67b488584a9 100644 --- a/sonar-plugin-api/src/test/java/org/sonar/api/platform/PicoUtilsTest.java +++ b/sonar-core/src/test/java/org/sonar/core/platform/PicoUtilsTest.java @@ -17,7 +17,7 @@ * 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.api.platform; +package org.sonar.core.platform; import org.junit.Test; import org.picocontainer.Characteristics; @@ -29,8 +29,8 @@ import java.io.IOException; import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; - public class PicoUtilsTest { + @Test public void shouldSanitizePicoLifecycleException() { Throwable th = PicoUtils.sanitize(newPicoLifecycleException(false)); diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java new file mode 100644 index 00000000000..2b702748c4b --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginInfoTest.java @@ -0,0 +1,203 @@ +/* + * 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.core.platform; + +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.utils.ManifestUtils; +import org.sonar.updatecenter.common.PluginManifest; +import org.sonar.updatecenter.common.Version; + +import javax.annotation.Nullable; + +import java.io.File; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.jar.Manifest; + +import static com.google.common.collect.Ordering.natural; +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.Assert.fail; + +public class PluginInfoTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void test_RequiredPlugin() throws Exception { + PluginInfo.RequiredPlugin plugin = PluginInfo.RequiredPlugin.parse("java:1.1"); + assertThat(plugin.getKey()).isEqualTo("java"); + assertThat(plugin.getMinimalVersion().getName()).isEqualTo("1.1"); + + try { + PluginInfo.RequiredPlugin.parse("java"); + fail(); + } catch (IllegalArgumentException expected) { + // ok + } + } + + @Test + public void test_compare() { + PluginInfo java1 = new PluginInfo("java").setName("Java").setVersion(Version.create("1.0")); + PluginInfo java2 = new PluginInfo("java").setName("Java").setVersion(Version.create("2.0")); + PluginInfo cobol = new PluginInfo("cobol").setName("Cobol").setVersion(Version.create("1.0")); + List plugins = Arrays.asList(java1, java2, cobol); + Collections.shuffle(plugins); + + List ordered = natural().sortedCopy(plugins); + assertThat(ordered.get(0)).isSameAs(cobol); + assertThat(ordered.get(1)).isSameAs(java1); + assertThat(ordered.get(2)).isSameAs(java2); + } + + @Test + public void test_compatibility_with_sq_version() { + assertThat(withMinSqVersion("1.1").isCompatibleWith("1.1")).isTrue(); + assertThat(withMinSqVersion("1.1").isCompatibleWith("1.1.0")).isTrue(); + assertThat(withMinSqVersion("1.0").isCompatibleWith("1.0.0")).isTrue(); + + assertThat(withMinSqVersion("1.0").isCompatibleWith("1.1")).isTrue(); + assertThat(withMinSqVersion("1.1.1").isCompatibleWith("1.1.2")).isTrue(); + assertThat(withMinSqVersion("2.0").isCompatibleWith("2.1.0")).isTrue(); + assertThat(withMinSqVersion("3.2").isCompatibleWith("3.2-RC1")).isTrue(); + assertThat(withMinSqVersion("3.2").isCompatibleWith("3.2-RC2")).isTrue(); + assertThat(withMinSqVersion("3.2").isCompatibleWith("3.1-RC2")).isFalse(); + + assertThat(withMinSqVersion("1.1").isCompatibleWith("1.0")).isFalse(); + assertThat(withMinSqVersion("2.0.1").isCompatibleWith("2.0.0")).isFalse(); + assertThat(withMinSqVersion("2.10").isCompatibleWith("2.1")).isFalse(); + assertThat(withMinSqVersion("10.10").isCompatibleWith("2.2")).isFalse(); + + assertThat(withMinSqVersion("1.1-SNAPSHOT").isCompatibleWith("1.0")).isFalse(); + assertThat(withMinSqVersion("1.1-SNAPSHOT").isCompatibleWith("1.1")).isTrue(); + assertThat(withMinSqVersion("1.1-SNAPSHOT").isCompatibleWith("1.2")).isTrue(); + assertThat(withMinSqVersion("1.0.1-SNAPSHOT").isCompatibleWith("1.0")).isFalse(); + + assertThat(withMinSqVersion("3.1-RC2").isCompatibleWith("3.2-SNAPSHOT")).isTrue(); + assertThat(withMinSqVersion("3.1-RC1").isCompatibleWith("3.2-RC2")).isTrue(); + assertThat(withMinSqVersion("3.1-RC1").isCompatibleWith("3.1-RC2")).isTrue(); + + assertThat(withMinSqVersion(null).isCompatibleWith("0")).isTrue(); + assertThat(withMinSqVersion(null).isCompatibleWith("3.1")).isTrue(); + } + + @Test + public void create_from_minimal_manifest() throws Exception { + PluginManifest manifest = new PluginManifest(); + manifest.setKey("java"); + manifest.setVersion("1.0"); + manifest.setName("Java"); + manifest.setMainClass("org.foo.FooPlugin"); + + File jarFile = temp.newFile(); + PluginInfo pluginInfo = PluginInfo.create(jarFile, manifest); + + assertThat(pluginInfo.getKey()).isEqualTo("java"); + assertThat(pluginInfo.getName()).isEqualTo("Java"); + assertThat(pluginInfo.getVersion().getName()).isEqualTo("1.0"); + assertThat(pluginInfo.getFile()).isSameAs(jarFile); + assertThat(pluginInfo.getMainClass()).isEqualTo("org.foo.FooPlugin"); + + // optional fields + assertThat(pluginInfo.getBasePlugin()).isNull(); + assertThat(pluginInfo.getDescription()).isNull(); + assertThat(pluginInfo.getHomepageUrl()).isNull(); + assertThat(pluginInfo.getImplementationBuild()).isNull(); + assertThat(pluginInfo.getIssueTrackerUrl()).isNull(); + assertThat(pluginInfo.getLicense()).isNull(); + assertThat(pluginInfo.getOrganizationName()).isNull(); + assertThat(pluginInfo.getOrganizationUrl()).isNull(); + assertThat(pluginInfo.getMinimalSqVersion()).isNull(); + assertThat(pluginInfo.getRequiredPlugins()).isEmpty(); + } + + @Test + public void create_from_complete_manifest() throws Exception { + PluginManifest manifest = new PluginManifest(); + manifest.setKey("fbcontrib"); + manifest.setVersion("2.0"); + manifest.setName("Java"); + manifest.setMainClass("org.fb.FindbugsPlugin"); + manifest.setBasePlugin("findbugs"); + manifest.setSonarVersion("4.5.1"); + manifest.setDescription("the desc"); + manifest.setHomepage("http://fbcontrib.org"); + manifest.setImplementationBuild("SHA1"); + manifest.setLicense("LGPL"); + manifest.setOrganization("SonarSource"); + manifest.setOrganizationUrl("http://sonarsource.com"); + manifest.setIssueTrackerUrl("http://jira.com"); + manifest.setRequirePlugins(new String[]{"java:2.0", "pmd:1.3"}); + + File jarFile = temp.newFile(); + PluginInfo pluginInfo = PluginInfo.create(jarFile, manifest); + + assertThat(pluginInfo.getBasePlugin()).isEqualTo("findbugs"); + assertThat(pluginInfo.getDescription()).isEqualTo("the desc"); + assertThat(pluginInfo.getHomepageUrl()).isEqualTo("http://fbcontrib.org"); + assertThat(pluginInfo.getImplementationBuild()).isEqualTo("SHA1"); + assertThat(pluginInfo.getIssueTrackerUrl()).isEqualTo("http://jira.com"); + assertThat(pluginInfo.getLicense()).isEqualTo("LGPL"); + assertThat(pluginInfo.getOrganizationName()).isEqualTo("SonarSource"); + assertThat(pluginInfo.getOrganizationUrl()).isEqualTo("http://sonarsource.com"); + assertThat(pluginInfo.getMinimalSqVersion().getName()).isEqualTo("4.5.1"); + assertThat(pluginInfo.getRequiredPlugins()).extracting("key").containsExactly("java", "pmd"); + } + + @Test + public void create_from_file() throws Exception { + File checkstyleJar = FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/sonar-checkstyle-plugin-2.8.jar")); + PluginInfo checkstyleInfo = PluginInfo.create(checkstyleJar); + + assertThat(checkstyleInfo.getName()).isEqualTo("Checkstyle"); + assertThat(checkstyleInfo.getMinimalSqVersion()).isEqualTo(Version.create("2.8")); + } + + @Test + public void test_toString() throws Exception { + PluginInfo pluginInfo = new PluginInfo().setKey("java").setVersion(Version.create("1.1")); + assertThat(pluginInfo.toString()).isEqualTo("[java / 1.1]"); + + pluginInfo.setImplementationBuild("SHA1"); + assertThat(pluginInfo.toString()).isEqualTo("[java / 1.1 / SHA1]"); + } + + @Test + public void isCore() throws Exception { + PluginInfo pluginInfo = new PluginInfo(); + assertThat(pluginInfo.isCore()).isFalse(); + + pluginInfo.setCore(true); + assertThat(pluginInfo.isCore()).isTrue(); + } + + static PluginInfo withMinSqVersion(@Nullable String version) { + PluginInfo pluginInfo = new PluginInfo("foo"); + if (version != null) { + pluginInfo.setMinimalSqVersion(Version.create(version)); + } + return pluginInfo; + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java new file mode 100644 index 00000000000..7cb984b1bda --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginLoaderTest.java @@ -0,0 +1,131 @@ +/* + * 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.core.platform; + +import com.google.common.collect.ImmutableMap; +import org.apache.commons.io.FileUtils; +import org.assertj.core.data.MapEntry; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.Plugin; +import org.sonar.api.utils.ZipUtils; + +import java.io.File; +import java.io.IOException; +import java.util.Collections; +import java.util.Map; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.entry; + +public class PluginLoaderTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void complete_test() throws Exception { + File checkstyleJar = FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/sonar-checkstyle-plugin-2.8.jar")); + PluginInfo checkstyleInfo = PluginInfo.create(checkstyleJar); + + PluginLoader loader = new PluginLoader(new TempPluginUnzipper()); + Map instances = loader.load(ImmutableMap.of("checkstyle", checkstyleInfo)); + + assertThat(instances).containsOnlyKeys("checkstyle"); + Plugin checkstyleInstance = instances.get("checkstyle"); + assertThat(checkstyleInstance.getClass().getName()).isEqualTo("org.sonar.plugins.checkstyle.CheckstylePlugin"); + + loader.unload(instances.values()); + // should test that classloaders are closed + } + + @Test + public void define_plugin_classloader__nominal() throws Exception { + PluginInfo info = new PluginInfo("foo") + .setName("Foo") + .setMainClass("org.foo.FooPlugin"); + File jarFile = temp.newFile(); + info.setFile(jarFile); + + PluginLoader loader = new PluginLoader(new BasicPluginUnzipper()); + Map defs = loader.defineClassloaders(ImmutableMap.of("foo", info)); + + assertThat(defs).containsOnlyKeys("foo"); + PluginLoader.ClassloaderDef def = defs.get("foo"); + assertThat(def.basePluginKey).isEqualTo("foo"); + assertThat(def.selfFirstStrategy).isFalse(); + assertThat(def.files).containsOnly(jarFile); + assertThat(def.mainClassesByPluginKey).containsOnly(MapEntry.entry("foo", "org.foo.FooPlugin")); + // TODO test mask - require change in sonar-classloader + } + + @Test + public void define_plugin_classloader__extend_base_plugin() throws Exception { + File baseJarFile = temp.newFile(), extensionJarFile = temp.newFile(); + PluginInfo base = new PluginInfo("foo") + .setName("Foo") + .setMainClass("org.foo.FooPlugin") + .setFile(baseJarFile); + PluginInfo extension = new PluginInfo("fooContrib") + .setName("Foo Contrib") + .setMainClass("org.foo.ContribPlugin") + .setFile(extensionJarFile) + .setBasePlugin("foo") + + // not a base plugin, can't override base metadata -> will be ignored + .setUseChildFirstClassLoader(true); + + PluginLoader loader = new PluginLoader(new BasicPluginUnzipper()); + Map defs = loader.defineClassloaders(ImmutableMap.of("foo", base, "fooContrib", extension)); + + assertThat(defs).containsOnlyKeys("foo"); + PluginLoader.ClassloaderDef def = defs.get("foo"); + assertThat(def.basePluginKey).isEqualTo("foo"); + assertThat(def.selfFirstStrategy).isFalse(); + assertThat(def.files).containsOnly(baseJarFile, extensionJarFile); + assertThat(def.mainClassesByPluginKey).containsOnly(MapEntry.entry("foo", "org.foo.FooPlugin"), entry("fooContrib", "org.foo.ContribPlugin")); + // TODO test mask - require change in sonar-classloader + } + + /** + * Does not unzip jar file. + */ + private class BasicPluginUnzipper extends PluginUnzipper { + @Override + public UnzippedPlugin unzip(PluginInfo info) { + return new UnzippedPlugin(info.getKey(), info.getFile(), Collections.emptyList()); + } + } + + private class TempPluginUnzipper extends PluginUnzipper { + @Override + public UnzippedPlugin unzip(PluginInfo info) { + try { + File tempDir = temp.newFolder(); + ZipUtils.unzip(info.getFile(), tempDir, newLibFilter()); + return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), tempDir); + + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java b/sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java new file mode 100644 index 00000000000..cbdd9c23356 --- /dev/null +++ b/sonar-core/src/test/java/org/sonar/core/platform/PluginUnzipperTest.java @@ -0,0 +1,81 @@ +/* + * 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.core.platform; + +import org.apache.commons.io.FileUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.sonar.api.utils.ZipUtils; + +import java.io.File; + +import static org.assertj.core.api.Assertions.assertThat; + +public class PluginUnzipperTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void unzip_plugin_with_libs() throws Exception { + final File jarFile = getFile("sonar-checkstyle-plugin-2.8.jar"); + final File toDir = temp.newFolder(); + PluginInfo pluginInfo = new PluginInfo().setKey("checkstyle").setFile(jarFile); + + PluginUnzipper unzipper = new PluginUnzipper() { + @Override + public UnzippedPlugin unzip(PluginInfo info) { + try { + ZipUtils.unzip(jarFile, toDir, newLibFilter()); + return UnzippedPlugin.createFromUnzippedDir(info.getKey(), info.getFile(), toDir); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + }; + UnzippedPlugin unzipped = unzipper.unzip(pluginInfo); + assertThat(unzipped.getKey()).isEqualTo("checkstyle"); + assertThat(unzipped.getLibs()).extracting("name").containsOnly("antlr-2.7.6.jar", "checkstyle-5.1.jar", "commons-cli-1.0.jar"); + assertThat(unzipped.getMain()).isSameAs(jarFile); + } + + @Test + public void unzip_plugin_without_libs() throws Exception { + File jarFile = temp.newFile(); + final File toDir = temp.newFolder(); + PluginInfo pluginInfo = new PluginInfo().setFile(jarFile); + + PluginUnzipper unzipper = new PluginUnzipper() { + @Override + public UnzippedPlugin unzip(PluginInfo info) { + return UnzippedPlugin.createFromUnzippedDir("foo", info.getFile(), toDir); + } + }; + UnzippedPlugin unzipped = unzipper.unzip(pluginInfo); + assertThat(unzipped.getKey()).isEqualTo("foo"); + assertThat(unzipped.getLibs()).isEmpty(); + assertThat(unzipped.getMain()).isSameAs(jarFile); + } + + private File getFile(String filename) { + return FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/" + filename)); + } +} diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java deleted file mode 100644 index 98cdf2698b6..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/plugins/DefaultPluginMetadataTest.java +++ /dev/null @@ -1,145 +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.core.plugins; - -import org.junit.Test; -import org.sonar.api.platform.PluginMetadata; - -import java.io.File; -import java.util.Arrays; -import java.util.List; - -import static com.google.common.collect.Lists.newArrayList; -import static com.google.common.collect.Ordering.natural; -import static org.assertj.core.api.Assertions.assertThat; - -public class DefaultPluginMetadataTest { - - @Test - public void testGettersAndSetters() { - DefaultPluginMetadata metadata = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")); - metadata.setKey("checkstyle") - .setLicense("LGPL") - .setDescription("description") - .setHomepage("http://home") - .setIssueTrackerUrl("http://jira.codehuas.org") - .setMainClass("org.Main") - .setOrganization("SonarSource") - .setOrganizationUrl("http://sonarsource.org") - .setVersion("1.1") - .setSonarVersion("3.0") - .setUseChildFirstClassLoader(true) - .setCore(false) - .setImplementationBuild("abcdef"); - - assertThat(metadata.getKey()).isEqualTo("checkstyle"); - assertThat(metadata.getParent()).isNull(); - assertThat(metadata.getLicense()).isEqualTo("LGPL"); - assertThat(metadata.getDescription()).isEqualTo("description"); - assertThat(metadata.getHomepage()).isEqualTo("http://home"); - assertThat(metadata.getIssueTrackerUrl()).isEqualTo("http://jira.codehuas.org"); - assertThat(metadata.getMainClass()).isEqualTo("org.Main"); - assertThat(metadata.getOrganization()).isEqualTo("SonarSource"); - assertThat(metadata.getOrganizationUrl()).isEqualTo("http://sonarsource.org"); - assertThat(metadata.getVersion()).isEqualTo("1.1"); - assertThat(metadata.getSonarVersion()).isEqualTo("3.0"); - assertThat(metadata.isUseChildFirstClassLoader()).isTrue(); - assertThat(metadata.isCore()).isFalse(); - assertThat(metadata.getBasePlugin()).isNull(); - assertThat(metadata.getFile()).isNotNull(); - assertThat(metadata.getDeployedFiles()).isEmpty(); - assertThat(metadata.getImplementationBuild()).isEqualTo("abcdef"); - } - - @Test - public void testDeployedFiles() { - DefaultPluginMetadata metadata = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")) - .addDeployedFile(new File("foo.jar")) - .addDeployedFile(new File("bar.jar")); - - assertThat(metadata.getDeployedFiles()).hasSize(2); - } - - @Test - public void testInternalPathToDependencies() { - DefaultPluginMetadata metadata = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")) - .setPathsToInternalDeps(newArrayList("META-INF/lib/commons-lang.jar", "META-INF/lib/commons-io.jar")); - - assertThat(metadata.getPathsToInternalDeps()).containsOnly("META-INF/lib/commons-lang.jar", "META-INF/lib/commons-io.jar"); - } - - @Test - public void shouldEquals() { - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")).setKey("checkstyle"); - PluginMetadata pmd = DefaultPluginMetadata.create(new File("sonar-pmd-plugin.jar")).setKey("pmd"); - - assertThat(checkstyle).isEqualTo(checkstyle); - assertThat(checkstyle).isEqualTo(DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")).setKey("checkstyle")); - assertThat(checkstyle).isNotEqualTo(pmd); - } - - @Test - public void shouldCompare() { - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create(new File("sonar-checkstyle-plugin.jar")) - .setKey("checkstyle") - .setName("Checkstyle"); - DefaultPluginMetadata pmd = DefaultPluginMetadata.create(new File("sonar-pmd-plugin.jar")) - .setKey("pmd") - .setName("PMD"); - List plugins = Arrays.asList(pmd, checkstyle); - - assertThat(natural().sortedCopy(plugins)).extracting("key").containsExactly("checkstyle", "pmd"); - } - - @Test - public void should_check_compatibility_with_sonar_version() { - assertThat(pluginWithVersion("1.1").isCompatibleWith("1.1")).isTrue(); - assertThat(pluginWithVersion("1.1").isCompatibleWith("1.1.0")).isTrue(); - assertThat(pluginWithVersion("1.0").isCompatibleWith("1.0.0")).isTrue(); - - assertThat(pluginWithVersion("1.0").isCompatibleWith("1.1")).isTrue(); - assertThat(pluginWithVersion("1.1.1").isCompatibleWith("1.1.2")).isTrue(); - assertThat(pluginWithVersion("2.0").isCompatibleWith("2.1.0")).isTrue(); - assertThat(pluginWithVersion("3.2").isCompatibleWith("3.2-RC1")).isTrue(); - assertThat(pluginWithVersion("3.2").isCompatibleWith("3.2-RC2")).isTrue(); - assertThat(pluginWithVersion("3.2").isCompatibleWith("3.1-RC2")).isFalse(); - - assertThat(pluginWithVersion("1.1").isCompatibleWith("1.0")).isFalse(); - assertThat(pluginWithVersion("2.0.1").isCompatibleWith("2.0.0")).isFalse(); - assertThat(pluginWithVersion("2.10").isCompatibleWith("2.1")).isFalse(); - assertThat(pluginWithVersion("10.10").isCompatibleWith("2.2")).isFalse(); - - assertThat(pluginWithVersion("1.1-SNAPSHOT").isCompatibleWith("1.0")).isFalse(); - assertThat(pluginWithVersion("1.1-SNAPSHOT").isCompatibleWith("1.1")).isTrue(); - assertThat(pluginWithVersion("1.1-SNAPSHOT").isCompatibleWith("1.2")).isTrue(); - assertThat(pluginWithVersion("1.0.1-SNAPSHOT").isCompatibleWith("1.0")).isFalse(); - - assertThat(pluginWithVersion("3.1-RC2").isCompatibleWith("3.2-SNAPSHOT")).isTrue(); - assertThat(pluginWithVersion("3.1-RC1").isCompatibleWith("3.2-RC2")).isTrue(); - assertThat(pluginWithVersion("3.1-RC1").isCompatibleWith("3.1-RC2")).isTrue(); - - assertThat(pluginWithVersion(null).isCompatibleWith("0")).isTrue(); - assertThat(pluginWithVersion(null).isCompatibleWith("3.1")).isTrue(); - } - - static DefaultPluginMetadata pluginWithVersion(String version) { - return DefaultPluginMetadata.create("foo").setSonarVersion(version); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/PluginClassloadersTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/PluginClassloadersTest.java deleted file mode 100644 index d9b3ada4254..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/plugins/PluginClassloadersTest.java +++ /dev/null @@ -1,129 +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.core.plugins; - -import org.apache.commons.io.FileUtils; -import org.codehaus.plexus.classworlds.ClassWorld; -import org.codehaus.plexus.classworlds.realm.ClassRealm; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.rules.ExpectedException; -import org.sonar.api.Plugin; -import org.sonar.api.platform.PluginMetadata; -import org.sonar.api.utils.SonarException; - -import java.io.File; -import java.util.Arrays; -import java.util.Map; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Matchers.any; -import static org.mockito.Matchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class PluginClassloadersTest { - - private PluginClassloaders classloaders; - - @Rule - public ExpectedException thrown = ExpectedException.none(); - - @Before - public void before() { - classloaders = new PluginClassloaders(getClass().getClassLoader()); - } - - @After - public void clean() { - if (classloaders != null) { - classloaders.clean(); - } - } - - @Test - public void shouldImport() { - classloaders.add(DefaultPluginMetadata.create("foo").addDeployedFile(getFile("PluginClassloadersTest/foo.jar"))); - classloaders.add(DefaultPluginMetadata.create("bar").addDeployedFile(getFile("PluginClassloadersTest/bar.jar"))); - classloaders.done(); - - String resourceName = "org/sonar/plugins/bar/api/resource.txt"; - assertThat(classloaders.get("bar").getResourceAsStream(resourceName)).isNotNull(); - assertThat(classloaders.get("foo").getResourceAsStream(resourceName)).isNotNull(); - } - - @Test - public void shouldCreateBaseClassloader() { - classloaders = new PluginClassloaders(getClass().getClassLoader()); - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create("checkstyle") - .setMainClass("org.sonar.plugins.checkstyle.CheckstylePlugin") - .addDeployedFile(getFile("sonar-checkstyle-plugin-2.8.jar")); - - Map map = classloaders.init(Arrays.asList(checkstyle)); - - Plugin checkstyleEntryPoint = map.get("checkstyle"); - ClassRealm checkstyleRealm = (ClassRealm) checkstyleEntryPoint.getClass().getClassLoader(); - assertThat(checkstyleRealm.getId()).isEqualTo("checkstyle"); - } - - @Test - public void shouldExtendPlugin() { - classloaders = new PluginClassloaders(getClass().getClassLoader()); - - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create("checkstyle") - .setMainClass("org.sonar.plugins.checkstyle.CheckstylePlugin") - .addDeployedFile(getFile("sonar-checkstyle-plugin-2.8.jar")); - - DefaultPluginMetadata checkstyleExt = DefaultPluginMetadata.create("checkstyle-ext") - .setBasePlugin("checkstyle") - .setMainClass("com.mycompany.sonar.checkstyle.CheckstyleExtensionsPlugin") - .addDeployedFile(getFile("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar")); - - Map map = classloaders.init(Arrays.asList(checkstyle, checkstyleExt)); - - Plugin checkstyleEntryPoint = map.get("checkstyle"); - Plugin checkstyleExtEntryPoint = map.get("checkstyle-ext"); - - assertThat(checkstyleEntryPoint.getClass().getClassLoader().equals(checkstyleExtEntryPoint.getClass().getClassLoader())).isTrue(); - } - - @Test - public void detect_plugins_compiled_for_bad_java_version() throws Exception { - thrown.expect(SonarException.class); - thrown.expectMessage("The plugin checkstyle is not supported with Java 1."); - - ClassWorld world = mock(ClassWorld.class); - when(world.newRealm(anyString(), any(ClassLoader.class))).thenThrow(new UnsupportedClassVersionError()); - - classloaders = new PluginClassloaders(getClass().getClassLoader(), world); - - DefaultPluginMetadata checkstyle = DefaultPluginMetadata.create("checkstyle") - .setMainClass("org.sonar.plugins.checkstyle.CheckstylePlugin") - .addDeployedFile(getFile("sonar-checkstyle-plugin-2.8.jar")); - - classloaders.init(Arrays.asList(checkstyle)); - } - - private File getFile(String filename) { - return FileUtils.toFile(getClass().getResource("/org/sonar/core/plugins/" + filename)); - } -} diff --git a/sonar-core/src/test/java/org/sonar/core/plugins/PluginJarInstallerTest.java b/sonar-core/src/test/java/org/sonar/core/plugins/PluginJarInstallerTest.java deleted file mode 100644 index b66bf864085..00000000000 --- a/sonar-core/src/test/java/org/sonar/core/plugins/PluginJarInstallerTest.java +++ /dev/null @@ -1,99 +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.core.plugins; - -import org.apache.commons.io.FileUtils; -import org.junit.Before; -import org.junit.ClassRule; -import org.junit.Test; -import org.junit.rules.TemporaryFolder; - -import java.io.File; -import java.io.IOException; - -import static org.assertj.core.api.Assertions.assertThat; - -public class PluginJarInstallerTest { - - private PluginJarInstaller extractor; - - @ClassRule - public static TemporaryFolder temporaryFolder = new TemporaryFolder(); - - private File userHome; - - @Before - public void setUp() throws IOException { - userHome = temporaryFolder.newFolder(); - extractor = new PluginJarInstaller() { - @Override - protected File extractPluginDependencies(File pluginFile, File pluginBasedir) throws IOException { - return null; - } - }; - } - - @Test - public void should_extract_metadata() throws IOException { - DefaultPluginMetadata metadata = extractor.extractMetadata(getFileFromCache("sonar-cobertura-plugin-3.1.1.jar"), true); - - assertThat(metadata.getKey()).isEqualTo("cobertura"); - assertThat(metadata.getBasePlugin()).isNull(); - assertThat(metadata.getName()).isEqualTo("Cobertura"); - assertThat(metadata.isCore()).isEqualTo(true); - assertThat(metadata.getFile().getName()).isEqualTo("sonar-cobertura-plugin-3.1.1.jar"); - assertThat(metadata.getVersion()).isEqualTo("3.1.1"); - assertThat(metadata.getImplementationBuild()).isEqualTo("b9283404030db9ce1529b1fadfb98331686b116d"); - assertThat(metadata.getHomepage()).isEqualTo("http://www.sonarsource.org/plugins/sonar-cobertura-plugin"); - assertThat(metadata.getIssueTrackerUrl()).isEqualTo("http://jira.codehaus.org/browse/SONAR"); - } - - @Test - public void should_read_sonar_version() throws IOException { - DefaultPluginMetadata metadata = extractor.extractMetadata(getFileFromCache("sonar-switch-off-violations-plugin-1.1.jar"), false); - - assertThat(metadata.getVersion()).isEqualTo("1.1"); - assertThat(metadata.getSonarVersion()).isEqualTo("2.5"); - } - - @Test - public void should_extract_extension_metadata() throws IOException { - DefaultPluginMetadata metadata = extractor.extractMetadata(getFileFromCache("sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar"), true); - - assertThat(metadata.getKey()).isEqualTo("checkstyleextensions"); - assertThat(metadata.getBasePlugin()).isEqualTo("checkstyle"); - } - - @Test - public void should_extract_requires_plugin_information() throws IOException { - DefaultPluginMetadata metadata = extractor.extractMetadata(getFileFromCache("fake2-plugin-1.1.jar"), true); - - assertThat(metadata.getKey()).isEqualTo("fake2"); - assertThat(metadata.getRequiredPlugins().get(0)).isEqualTo("fake1:1.1"); - } - - File getFileFromCache(String filename) throws IOException { - File src = FileUtils.toFile(PluginJarInstallerTest.class.getResource("/org/sonar/core/plugins/" + filename)); - File destFile = new File(new File(userHome, "" + filename.hashCode()), filename); - FileUtils.copyFile(src, destFile); - return destFile; - } - -} diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/bar.jar deleted file mode 100644 index 343ad65f1331e19b3af22bae7477b7f084d0f0d6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1057 zcmWIWW@Zs#U|`^2Sj1gv3uG`bfe0YrWME*`(P_14uocjo-&AcHH$51tn3dHL#i`d%zu z#lkt`rQ{K5NzDwF>CZHOq^XHN6%QAizT}yhcEvN**O{M+J{B>8E#?AR!NAZ9btRYs zBv^nrzbIWF zVnHU7T}?cYxBw~#12qO2BtO912C@sJuPC)RzqBYhRj;I?Bs7GRfjM~cTOSZEt>9*0 zWO>013?>i};LXS+0*)%|u?@2gLc^mI6!+*_(c>PWRTs#FYeh;4AQQM4utyOn5)oh- zkO_%W+&UoU!1SDe>VQQtZhfFgMt~1MCL(FTf)&}N*diUFUx5*uen=8PxB*)tK$sDN y(+o(GU}TbGhWHjUQAogzX=v=hYYu7>VTB|SjO4+}1~QNX2<3sLz#E{63=9C|PRf=5 diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/foo.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/PluginClassloadersTest/foo.jar deleted file mode 100644 index 505311c008b5c4ade9c187db7382eb16cc53108a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 313 zcmWIWW@Zs#U|`^2Sj1gv3uG`bfe0YrWME*`(P_14uocjo-&AcHH$51tn3dHL#i`d%zu z#lkt`rQ{K5NzDwF>CZHOq^XHN6%QAizT}yhcEvN**O{M+J{B m0Nf!USEFl1cQrzvE|3Y=isXj?Z&o&tDkdPT1=7nv90mXk6h(^w diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/checkstyle-extension.xml b/sonar-core/src/test/resources/org/sonar/core/plugins/checkstyle-extension.xml deleted file mode 100644 index 75a263db3c3..00000000000 --- a/sonar-core/src/test/resources/org/sonar/core/plugins/checkstyle-extension.xml +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/fake2-plugin-1.1.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/fake2-plugin-1.1.jar deleted file mode 100644 index f4b8b79b77622d8abb276d086042d04bb87e52e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2673 zcmWIWW@Zs#-~htbuplP}Bp}Ejz~GjcooW=2Q<|Qcr+$!NYKkO*Qb;PsPBm zj3NxN0SEmKIf%?@y>u&xCD24}ZzH=|)zu2cqO2ayw%!f0++(@-bPwV7cLV{qVYWTej~psi$HLuKaG+Y&O2MebPeN zl|FG(KhHdqe?(`gpZTSY9?O-&r>&pew=W1JICL3TarW>85rgQaR4xk zxxgXIH8}(&j5&c}?CTohsO#zHrVk3+Bfzi)VNlqDv_YbV4^5j0vNm5wKTkK;;1E4u zH(2bPftpY{(cAy9fk4aqiJ#i)AM(lM8cg^?k6Av>oe3WRQw)L9nCHXjsKcD(!TMT9hIkiTb{G65((-fsV z+qu__M=fsls;BbzGPba-aS9HZJIVF?&9wAl!$3H*97S;Qj=@3H?=8S9#K-}zQ;)3MPkIsQ5KO628$O*6Vx9u=yr z3p)6;Qs?JaN%1+~%h`7e+s;Y6J4H`A^TR8ixk|BR3vY?MS@N3MtU6luX*9#awSF%b zhh@m}$?ezO7U5*)qIv0>FZb-1bDu7Jv}uFEk6S5IzA)?mcxjqgUNm?4gxvK8KUhH# z_2`o7QUzcn=`tZlR2y1m;71Pe+{CifJa}}Sfo26nETe1Iz|@?apQ~4#pO;uvoL^d$ zoT`^u0^yQiswoMkrh)RYZUHzWW3>W%5#UU&6$L==6%^$cq!yKAreYKts;3Te9Wvly zxKO=cX~X8!l^jMlJm*y0I4gf*SB{9OWTy3p_o6m-6=!=}d%4aV)Fv#Md(?C>7mp3& zJjUDaM67Zi7S%dre0)*1ZB9tlmlt7|cinw%_v&`K=%zJSm41i(D@jq+6*>K0=gX6V z8*!z+kM@D0S-yqMt^k+^%Yhh?vC`3^*@hAiSLEivGTl^Kd8D-R_m5Uxv{_*%>U8& z>8$n15em2B_a9qtXt7Gi$KA1kYvRSb<=cz0SOwlBnyx%DsVKJI#97ZZwC-e(64wHj z<3XKXLR>S-l$&%9f82N_r@7P2Vdv_i&2mk0&pdoztUPHGrJHv8>ht5KP1Wa_b9{S# z&657(wq9v@jb2XAWb+)pPgkN|XQ=N~EH{k4sMF4~{4`i^1(dXx_Z9NcMRp}XRI=@BY6iJD6hU##Y-A%)DjkS}K$Q-%gV0Msek9WlIPx+h zIS5vuLYxIEQ1Kb0fntysra^cLSK@3iMX_N42{xb=wCL6$Vja0Waz?UlNh320M#$jw z5Q=r^g)VwJfmjYI@{p6G4T|MoD6t$|077g96@UTWtZbkp3~F4y1Sb9>b`TE$oU@`F diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-checkstyle-extensions-plugin-0.1-SNAPSHOT.jar deleted file mode 100644 index 4ae5393cee53b9c47cd5730097a4643c49886c99..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 5597 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

O1_imty#`>A`*0DBByw`#<`0k4yjn diff --git a/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-cobertura-plugin-3.1.1.jar b/sonar-core/src/test/resources/org/sonar/core/plugins/sonar-cobertura-plugin-3.1.1.jar deleted file mode 100644 index 6a74b55d02cc30115b73dd2e68e3348221059aed..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 14560 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 N{Scqt%qf3-{U5R(SC;?) diff --git a/sonar-deprecated/src/main/java/org/sonar/api/charts/package-info.java b/sonar-deprecated/src/main/java/org/sonar/api/charts/package-info.java index e5922bf75f0..358d4bbef40 100644 --- a/sonar-deprecated/src/main/java/org/sonar/api/charts/package-info.java +++ b/sonar-deprecated/src/main/java/org/sonar/api/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.api.charts; diff --git a/sonar-home/src/main/java/org/sonar/home/cache/FileCache.java b/sonar-home/src/main/java/org/sonar/home/cache/FileCache.java index 3588a908b4b..63a1168639d 100644 --- a/sonar-home/src/main/java/org/sonar/home/cache/FileCache.java +++ b/sonar-home/src/main/java/org/sonar/home/cache/FileCache.java @@ -20,15 +20,12 @@ package org.sonar.home.cache; import org.apache.commons.io.FileUtils; -import org.sonar.api.utils.ZipUtils; import org.sonar.home.log.Log; import javax.annotation.CheckForNull; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; -import java.util.zip.ZipEntry; /** * This class is responsible for managing Sonar batch file cache. You can put file into cache and @@ -138,7 +135,7 @@ public class FileCache { } } - private File createTempDir() { + public File createTempDir() { String baseName = System.currentTimeMillis() + "-"; for (int counter = 0; counter < TEMP_DIR_ATTEMPTS; counter++) { @@ -161,43 +158,4 @@ public class FileCache { } return dir; } - - /** - * Unzip a cached file. Unzip is done only the first time. - * @param cachedFile - * @return directory where cachedFile was unzipped - * @throws IOException - */ - public File unzip(File cachedFile) throws IOException { - String filename = cachedFile.getName(); - File destDir = new File(cachedFile.getParentFile(), filename + "_unzip"); - File lockFile = new File(cachedFile.getParentFile(), filename + "_unzip.lock"); - if (!destDir.exists()) { - FileOutputStream out = new FileOutputStream(lockFile); - try { - java.nio.channels.FileLock lock = out.getChannel().lock(); - try { - // Recheck in case of concurrent processes - if (!destDir.exists()) { - File tempDir = createTempDir(); - ZipUtils.unzip(cachedFile, tempDir, new LibFilter()); - FileUtils.moveDirectory(tempDir, destDir); - } - } finally { - lock.release(); - } - } finally { - out.close(); - FileUtils.deleteQuietly(lockFile); - } - } - return destDir; - } - - private static final class LibFilter implements ZipUtils.ZipEntryFilter { - @Override - public boolean accept(ZipEntry entry) { - return entry.getName().startsWith("META-INF/lib"); - } - } } diff --git a/sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java b/sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java index be82711e85e..2d90503598d 100644 --- a/sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java +++ b/sonar-home/src/test/java/org/sonar/home/cache/FileCacheTest.java @@ -28,7 +28,6 @@ import org.sonar.home.log.Slf4jLog; import java.io.File; import java.io.IOException; -import java.net.URISyntaxException; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Matchers.any; @@ -120,32 +119,4 @@ public class FileCacheTest { assertThat(FileUtils.readFileToString(cachedFile)).contains("downloaded by"); } - @Test - public void unzip_from_cache() throws IOException, URISyntaxException { - final File samplePlugin = new File(this.getClass().getResource("/sonar-checkstyle-plugin-2.8.jar").toURI()); - FileHashes hashes = mock(FileHashes.class); - final FileCache cache = new FileCache(tempFolder.newFolder(), log, hashes); - when(hashes.of(any(File.class))).thenReturn("ABCDE"); - - FileCache.Downloader downloader = new FileCache.Downloader() { - public void download(String filename, File toFile) throws IOException { - FileUtils.copyFile(samplePlugin, toFile); - } - }; - final File cachedFile = cache.get("sonar-checkstyle-plugin-2.8.jar", "ABCDE", downloader); - assertThat(cachedFile).isNotNull().exists().isFile(); - assertThat(cachedFile.getName()).isEqualTo("sonar-checkstyle-plugin-2.8.jar"); - - File pluginDepsDir = cache.unzip(cachedFile); - - assertThat(pluginDepsDir.listFiles()).hasSize(1); - File metaDir = new File(pluginDepsDir, "META-INF"); - assertThat(metaDir.listFiles()).hasSize(1); - File libDir = new File(metaDir, "lib"); - assertThat(libDir.listFiles()).hasSize(3); - assertThat(libDir.listFiles()).containsOnly(new File(libDir, "antlr-2.7.6.jar"), new File(libDir, "checkstyle-5.1.jar"), new File(libDir, "commons-cli-1.0.jar")); - - // Unzip again should not do anything as it is already unzipped - cache.unzip(cachedFile); - } } diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/Plugin.java b/sonar-plugin-api/src/main/java/org/sonar/api/Plugin.java index ea7bbc755e9..23f5021d3fb 100644 --- a/sonar-plugin-api/src/main/java/org/sonar/api/Plugin.java +++ b/sonar-plugin-api/src/main/java/org/sonar/api/Plugin.java @@ -24,9 +24,9 @@ import java.util.List; /** * A plugin is a group of extensions. See org.sonar.api.Extension interface to browse * available extension points. - *

*

The manifest property Plugin-Class must declare the name of the implementation class. * It is automatically set by sonar-packaging-maven-plugin when building plugins.

+ *

Implementation must declare a public constructor with no-parameters.

* * @see org.sonar.api.Extension * @since 1.10 diff --git a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginMetadata.java b/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginMetadata.java deleted file mode 100644 index 97ab71bcfc1..00000000000 --- a/sonar-plugin-api/src/main/java/org/sonar/api/platform/PluginMetadata.java +++ /dev/null @@ -1,74 +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.api.platform; - -import javax.annotation.CheckForNull; -import java.io.File; -import java.util.List; - -/** - * @since 2.8 - */ -public interface PluginMetadata { - File getFile(); - - List getDeployedFiles(); - - String getKey(); - - String getName(); - - String getMainClass(); - - String getDescription(); - - String getOrganization(); - - String getOrganizationUrl(); - - String getLicense(); - - String getVersion(); - - String getHomepage(); - - /** - * @since 3.6 - */ - String getIssueTrackerUrl(); - - boolean isUseChildFirstClassLoader(); - - String getBasePlugin(); - - /** - * Always return null since version 5.2 - * @deprecated in 5.2. Concept of parent relationship is removed. See https://jira.codehaus.org/browse/SONAR-6433 - */ - @Deprecated - @CheckForNull - String getParent(); - - List getRequiredPlugins(); - - boolean isCore(); - - String getImplementationBuild(); -} -- 2.39.5